lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date:   Tue, 6 Jul 2021 20:51:41 -0700
From:   Andrii Nakryiko <andrii.nakryiko@...il.com>
To:     Alan Maguire <alan.maguire@...cle.com>
Cc:     Alexei Starovoitov <ast@...nel.org>,
        Daniel Borkmann <daniel@...earbox.net>,
        Andrii Nakryiko <andrii@...nel.org>, Martin Lau <kafai@...com>,
        Song Liu <songliubraving@...com>, Yonghong Song <yhs@...com>,
        john fastabend <john.fastabend@...il.com>,
        KP Singh <kpsingh@...nel.org>,
        Bill Wendling <morbo@...gle.com>,
        Shuah Khan <shuah@...nel.org>, bpf <bpf@...r.kernel.org>,
        Networking <netdev@...r.kernel.org>,
        "open list:KERNEL SELFTEST FRAMEWORK" 
        <linux-kselftest@...r.kernel.org>,
        open list <linux-kernel@...r.kernel.org>
Subject: Re: [PATCH v5 bpf-next 3/3] selftests/bpf: add dump type data tests
 to btf dump tests

On Sat, Jun 19, 2021 at 1:56 AM Alan Maguire <alan.maguire@...cle.com> wrote:
>
> Test various type data dumping operations by comparing expected
> format with the dumped string; an snprintf-style printf function
> is used to record the string dumped.  Also verify overflow handling
> where the data passed does not cover the full size of a type,
> such as would occur if a tracer has a portion of the 8k
> "struct task_struct".
>
> Signed-off-by: Alan Maguire <alan.maguire@...cle.com>
> ---
>  tools/testing/selftests/bpf/prog_tests/btf_dump.c | 644 ++++++++++++++++++++++
>  1 file changed, 644 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dump.c b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
> index 1b90e68..c894201 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf_dump.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
> @@ -232,7 +232,621 @@ void test_btf_dump_incremental(void)
>         btf__free(btf);
>  }
>
> +#define STRSIZE                                4096
> +
> +static void btf_dump_snprintf(void *ctx, const char *fmt, va_list args)
> +{
> +       char *s = ctx, new[STRSIZE];
> +
> +       vsnprintf(new, STRSIZE, fmt, args);
> +       strncat(s, new, STRSIZE);

this can cause stack corruption, because strncat doesn't take into
account the length of string in s and might copy all STRSIZE bytes
from new. What you want here is actually strlcat() variant, but I'm
not sure it's available in Linux glibc. Instead, you have to pass
STRSIZE - strlen(s) - 1 to strncat.

> +}
> +
> +/* skip "enum "/"struct " prefixes */
> +#define SKIP_PREFIX(_typestr, _prefix)                                 \
> +       do {                                                            \
> +               if (strncmp(_typestr, _prefix, strlen(_prefix)) == 0)   \
> +                       _typestr += strlen(_prefix) + 1;                \

If you expect "enum " or "struct " (not, say, "enum/"), then the test
should just pass that in explicitly instead of SKIP_PREFIX silently
ignoring an extra *any* character.


> +       } while (0)
> +

[...]

> +/* overflow test; pass typesize < expected type size, ensure E2BIG returned */
> +#define TEST_BTF_DUMP_DATA_OVER(_b, _d, _str, _type, _type_sz, _expected, ...)\
> +       do {                                                            \
> +               char __ptrtype[64] = #_type;                            \
> +               char *_ptrtype = (char *)__ptrtype;                     \
> +               _type _ptrdata = __VA_ARGS__;                           \
> +               void *_ptr = &_ptrdata;                                 \
> +               int _err;                                               \
> +                                                                       \
> +               _err = btf_dump_data(_b, _d, _ptrtype, 0, _ptr,         \
> +                                    _type_sz, _str, _expected);        \
> +               if (_err < 0)                                           \
> +                       return;                                         \

don't return, let all the validation run. It's better to see all the
failures than fix one by one, recompile, rerun, then fix another one.
Same for TEST_BTF_DUMP_DATA above.

> +       } while (0)
> +
> +#define TEST_BTF_DUMP_VAR(_b, _d, _str, _var, _type, _flags, _expected, ...) \
> +       do {                                                            \
> +               _type _ptrdata = __VA_ARGS__;                           \
> +               void *_ptr = &_ptrdata;                                 \
> +               int _err;                                               \
> +                                                                       \
> +               _err = btf_dump_data(_b, _d, _var, _flags, _ptr,        \
> +                                    sizeof(_type), _str, _expected);   \
> +               if (_err < 0)                                           \
> +                       return;                                         \

same, don't return early

> +       } while (0)
> +
> +static void test_btf_dump_int_data(struct btf *btf, struct btf_dump *d,
> +                                  char *str)
> +{
> +       /* simple int */
> +       TEST_BTF_DUMP_DATA_C(btf, d, str, int, BTF_F_COMPACT, 1234);
> +       TEST_BTF_DUMP_DATA(btf, d, str, int, BTF_F_COMPACT | BTF_F_NONAME,
> +                          "1234", 1234);
> +       TEST_BTF_DUMP_DATA(btf, d, str, int, 0, "(int)1234", 1234);
> +
> +       /* zero value should be printed at toplevel */
> +       TEST_BTF_DUMP_DATA(btf, d, str, int, BTF_F_COMPACT, "(int)0", 0);
> +       TEST_BTF_DUMP_DATA(btf, d, str, int, BTF_F_COMPACT | BTF_F_NONAME,
> +                          "0", 0);
> +       TEST_BTF_DUMP_DATA(btf, d, str, int, BTF_F_COMPACT | BTF_F_ZERO,
> +                          "(int)0", 0);
> +       TEST_BTF_DUMP_DATA(btf, d, str, int,
> +                          BTF_F_COMPACT | BTF_F_NONAME | BTF_F_ZERO,
> +                          "0", 0);
> +       TEST_BTF_DUMP_DATA_C(btf, d, str, int, BTF_F_COMPACT, -4567);
> +       TEST_BTF_DUMP_DATA(btf, d, str, int, BTF_F_COMPACT | BTF_F_NONAME,
> +                          "-4567", -4567);
> +       TEST_BTF_DUMP_DATA(btf, d, str, int, 0, "(int)-4567", -4567);
> +
> +       TEST_BTF_DUMP_DATA_OVER(btf, d, str, int, sizeof(int)-1, "", 1);

all of these validations are independent of each other, so there is no
need to return early if any one fails (see above)

> +}
> +
> +static void test_btf_dump_float_data(struct btf *btf, struct btf_dump *d,
> +                                    char *str)
> +{
> +       float t1 = 1.234567;
> +       float t2 = -1.234567;
> +       float t3 = 0.0;
> +       double t4 = 5.678912;
> +       double t5 = -5.678912;
> +       double t6 = 0.0;
> +       long double t7 = 9.876543;
> +       long double t8 = -9.876543;
> +       long double t9 = 0.0;
> +
> +       /* since the kernel does not likely have any float types in its BTF, we
> +        * will need to add some of various sizes.
> +        */
> +
> +       if (!ASSERT_GT(btf__add_float(btf, "test_float", 4), 0, "add float"))
> +               return;
> +       if (!ASSERT_OK(btf_dump_data(btf, d, "test_float", 0, &t1, 4, str,
> +                                    "(test_float)1.234567"), "dump float"))
> +               return;
> +
> +       if (!ASSERT_OK(btf_dump_data(btf, d, "test_float", 0, &t2, 4, str,
> +                                    "(test_float)-1.234567"), "dump float"))
> +               return;
> +       if (!ASSERT_OK(btf_dump_data(btf, d, "test_float", 0, &t3, 4, str,
> +                                    "(test_float)0.000000"), "dump float"))
> +               return;
> +
> +       if (!ASSERT_GT(btf__add_float(btf, "test_double", 8), 0, "add_double"))
> +               return;
> +       if (!ASSERT_OK(btf_dump_data(btf, d, "test_double", 0, &t4, 8, str,
> +                                    "(test_double)5.678912"), "dump double"))
> +               return;
> +       if (!ASSERT_OK(btf_dump_data(btf, d, "test_double", 0, &t5, 8, str,
> +                                    "(test_double)-5.678912"), "dump double"))
> +               return;
> +       if (!ASSERT_OK(btf_dump_data(btf, d, "test_double", 0, &t6, 8, str,
> +                                    "(test_double)0.000000"), "dump double"))
> +               return;
> +
> +       if (!ASSERT_GT(btf__add_float(btf, "test_long_double", 16), 0,
> +                      "add_long_double"))
> +               return;
> +       if (!ASSERT_OK(btf_dump_data(btf, d, "test_long_double", 0, &t7, 16,
> +                                    str, "(test_long_double)9.876543"),
> +                                    "dump long_double"))
> +               return;
> +       if (!ASSERT_OK(btf_dump_data(btf, d, "test_long_double", 0, &t8, 16,
> +                                    str, "(test_long_double)-9.876543"),
> +                                    "dump long_double"))
> +               return;
> +       ASSERT_OK(btf_dump_data(btf, d, "test_long_double", 0, &t9, 16,
> +                               str, "(test_long_double)0.000000"),
> +                               "dump long_double");

same, don't return, just have a list of assertions

> +}
> +

[...]

> +                          { .next = (struct list_head *)1 });
> +       /* NULL pointer should not be displayed */
> +       TEST_BTF_DUMP_DATA(btf, d, str, struct list_head, BTF_F_COMPACT,
> +                          "(struct list_head){}",
> +                          { .next = (struct list_head *)0 });
> +       TEST_BTF_DUMP_DATA(btf, d, str, struct list_head, 0,
> +"(struct list_head){\n"
> +"}",
> +                          { .next = (struct list_head *)0 });
> +
> +       /* struct with function pointers */
> +       type_id = btf__find_by_name(btf, "file_operations");
> +       if (CHECK(type_id <= 0, "find type id",

some more CHECK leftovers, please switch all CHECKs to ASSERT_xxx

> +                 "no 'struct file_operations' in BTF: %d\n", type_id))
> +               return;
> +       type_sz = btf__resolve_size(btf, type_id);
> +       str[0] = '\0';
> +
> +       ret = btf_dump__dump_type_data(d, type_id, fops, type_sz, &opts);
> +       if (CHECK(ret != type_sz,
> +                 "dump file_operations is successful",
> +                 "unexpected return value dumping file_operations '%s': %d\n",
> +                 str, ret))
> +               return;
> +
> +       cmpstr =
> +"(struct file_operations){\n"
> +"      .owner = (struct module *)0xffffffffffffffff,\n"
> +"      .llseek = (loff_t (*)(struct file *, loff_t, int))0xffffffffffffffff,";
> +
> +       if (!ASSERT_STRNEQ(str, cmpstr, strlen(cmpstr), "file_operations"))
> +               return;

same as above, even if this fails, we can still run all the other
validations safely

> +
> +       /* struct with char array */
> +       TEST_BTF_DUMP_DATA(btf, d, str, struct bpf_prog_info, BTF_F_COMPACT,
> +                          "(struct bpf_prog_info){.name = (char[16])['f','o','o',],}",
> +                          { .name = "foo",});
> +       TEST_BTF_DUMP_DATA(btf, d, str, struct bpf_prog_info,
> +                          BTF_F_COMPACT | BTF_F_NONAME,
> +                          "{['f','o','o',],}",
> +                          {.name = "foo",});

[...]

>  void test_btf_dump() {
> +       char str[STRSIZE];
> +       struct btf_dump_opts opts = { .ctx = str };
> +       struct btf_dump *d;
> +       struct btf *btf;
>         int i;
>
>         for (i = 0; i < ARRAY_SIZE(btf_dump_test_cases); i++) {
> @@ -245,4 +859,34 @@ void test_btf_dump() {
>         }
>         if (test__start_subtest("btf_dump: incremental"))
>                 test_btf_dump_incremental();
> +
> +       btf = libbpf_find_kernel_btf();
> +       if (CHECK(!btf, "get kernel BTF", "no kernel BTF found"))
> +               return;
> +
> +       d = btf_dump__new(btf, NULL, &opts, btf_dump_snprintf);
> +
> +       if (CHECK(!d, "new dump", "could not create BTF dump"))
> +               return;

goto clean and free dumper and btf?

> +
> +       /* Verify type display for various types. */
> +       if (test__start_subtest("btf_dump: int_data"))
> +               test_btf_dump_int_data(btf, d, str);
> +       if (test__start_subtest("btf_dump: float_data"))
> +               test_btf_dump_float_data(btf, d, str);
> +       if (test__start_subtest("btf_dump: char_data"))
> +               test_btf_dump_char_data(btf, d, str);
> +       if (test__start_subtest("btf_dump: typedef_data"))
> +               test_btf_dump_typedef_data(btf, d, str);
> +       if (test__start_subtest("btf_dump: enum_data"))
> +               test_btf_dump_enum_data(btf, d, str);
> +       if (test__start_subtest("btf_dump: struct_data"))
> +               test_btf_dump_struct_data(btf, d, str);
> +       if (test__start_subtest("btf_dump: var_data"))
> +               test_btf_dump_var_data(btf, d, str);
> +       btf_dump__free(d);
> +       btf__free(btf);
> +
> +       if (test__start_subtest("btf_dump: datasec_data"))
> +               test_btf_dump_datasec_data(str);
>  }
> --
> 1.8.3.1
>

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ