On Tue, Feb 27, 2024 at 12:46 PM Eduard Zingerman <eddyz87@xxxxxxxxx> wrote: > > When loading struct_ops programs kernel requires BTF id of the > struct_ops type and member index for attachment point inside that > type. This makes it not possible to have same BPF program used in > struct_ops maps that have different struct_ops type. > Check if libbpf rejects such BPF objects files. > > Signed-off-by: Eduard Zingerman <eddyz87@xxxxxxxxx> > --- > .../selftests/bpf/bpf_testmod/bpf_testmod.c | 24 +++++++++++ > .../selftests/bpf/bpf_testmod/bpf_testmod.h | 4 ++ > .../selftests/bpf/prog_tests/bad_struct_ops.c | 42 +++++++++++++++++++ > .../selftests/bpf/progs/bad_struct_ops.c | 17 ++++++++ > 4 files changed, 87 insertions(+) > create mode 100644 tools/testing/selftests/bpf/prog_tests/bad_struct_ops.c > create mode 100644 tools/testing/selftests/bpf/progs/bad_struct_ops.c > > diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c > index 0d8437e05f64..69f5eb9ad546 100644 > --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c > +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c > @@ -601,6 +601,29 @@ struct bpf_struct_ops bpf_bpf_testmod_ops = { > .owner = THIS_MODULE, > }; > > +static int bpf_dummy_reg2(void *kdata) > +{ > + struct bpf_testmod_ops2 *ops = kdata; > + > + ops->test_1(); > + return 0; > +} > + > +static struct bpf_testmod_ops2 __bpf_testmod_ops2 = { > + .test_1 = bpf_testmod_test_1, > +}; > + > +struct bpf_struct_ops bpf_testmod_ops2 = { > + .verifier_ops = &bpf_testmod_verifier_ops, > + .init = bpf_testmod_ops_init, > + .init_member = bpf_testmod_ops_init_member, > + .reg = bpf_dummy_reg2, > + .unreg = bpf_dummy_unreg, > + .cfi_stubs = &__bpf_testmod_ops2, > + .name = "bpf_testmod_ops2", > + .owner = THIS_MODULE, > +}; > + > extern int bpf_fentry_test1(int a); > > static int bpf_testmod_init(void) > @@ -612,6 +635,7 @@ static int bpf_testmod_init(void) > ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_testmod_kfunc_set); > ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &bpf_testmod_kfunc_set); > ret = ret ?: register_bpf_struct_ops(&bpf_bpf_testmod_ops, bpf_testmod_ops); > + ret = ret ?: register_bpf_struct_ops(&bpf_testmod_ops2, bpf_testmod_ops2); > if (ret < 0) > return ret; > if (bpf_fentry_test1(0) < 0) > diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.h b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.h > index c3b0cf788f9f..3183fff7f246 100644 > --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.h > +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.h > @@ -37,4 +37,8 @@ struct bpf_testmod_ops { > int (*test_maybe_null)(int dummy, struct task_struct *task); > }; > > +struct bpf_testmod_ops2 { > + int (*test_1)(void); > +}; > + > #endif /* _BPF_TESTMOD_H */ > diff --git a/tools/testing/selftests/bpf/prog_tests/bad_struct_ops.c b/tools/testing/selftests/bpf/prog_tests/bad_struct_ops.c > new file mode 100644 > index 000000000000..9c689db4b05b > --- /dev/null > +++ b/tools/testing/selftests/bpf/prog_tests/bad_struct_ops.c > @@ -0,0 +1,42 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <test_progs.h> > +#include "bad_struct_ops.skel.h" > + > +#define EXPECTED_MSG "libbpf: struct_ops reloc" > + > +static libbpf_print_fn_t old_print_cb; > +static bool msg_found; > + > +static int print_cb(enum libbpf_print_level level, const char *fmt, va_list args) > +{ > + old_print_cb(level, fmt, args); > + if (level == LIBBPF_WARN && strncmp(fmt, EXPECTED_MSG, strlen(EXPECTED_MSG)) == 0) > + msg_found = true; > + > + return 0; > +} > + > +static void test_bad_struct_ops(void) > +{ > + struct bad_struct_ops *skel; > + int err; > + > + old_print_cb = libbpf_set_print(print_cb); > + skel = bad_struct_ops__open_and_load(); we want to check that the load step failed specifically, right? So please split open from load, make sure that open succeeds, but load fails > + err = errno; > + libbpf_set_print(old_print_cb); > + if (!ASSERT_NULL(skel, "bad_struct_ops__open_and_load")) > + return; > + > + ASSERT_EQ(err, EINVAL, "errno should be EINVAL"); > + ASSERT_TRUE(msg_found, "expected message"); > + > + bad_struct_ops__destroy(skel); > +} > + > +void serial_test_bad_struct_ops(void) why does it have to be a serial test? > +{ > + if (test__start_subtest("test_bad_struct_ops")) > + test_bad_struct_ops(); > +} > diff --git a/tools/testing/selftests/bpf/progs/bad_struct_ops.c b/tools/testing/selftests/bpf/progs/bad_struct_ops.c > new file mode 100644 > index 000000000000..9c103afbfdb1 > --- /dev/null > +++ b/tools/testing/selftests/bpf/progs/bad_struct_ops.c > @@ -0,0 +1,17 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <vmlinux.h> > +#include <bpf/bpf_helpers.h> > +#include <bpf/bpf_tracing.h> > +#include "../bpf_testmod/bpf_testmod.h" > + > +char _license[] SEC("license") = "GPL"; > + > +SEC("struct_ops/test_1") > +int BPF_PROG(test_1) { return 0; } > + > +SEC(".struct_ops.link") > +struct bpf_testmod_ops testmod_1 = { .test_1 = (void *)test_1 }; > + > +SEC(".struct_ops.link") > +struct bpf_testmod_ops2 testmod_2 = { .test_1 = (void *)test_1 }; > -- > 2.43.0 >