Skip to content

Commit

Permalink
Merge branch 'check-bpf_dummy_struct_ops-program-params-for-test-runs'
Browse files Browse the repository at this point in the history
Eduard Zingerman says:

====================
check bpf_dummy_struct_ops program params for test runs

When doing BPF_PROG_TEST_RUN for bpf_dummy_struct_ops programs,
execution should be rejected when NULL is passed for non-nullable
params, because for such params verifier assumes that such params are
never NULL and thus might optimize out NULL checks.

This problem was reported by Jose E. Marchesi in off-list discussion.
The code generated by GCC for dummy_st_ops_success/test_1() function
differs from LLVM variant in a way that allows verifier to remove the
NULL check. The test dummy_st_ops/dummy_init_ret_value actually sets
the 'state' parameter to NULL, thus GCC-generated version of the test
triggers NULL pointer dereference when BPF program is executed.

This patch-set addresses the issue in the following steps:
- patch #1 marks bpf_dummy_struct_ops.test_1 parameter as nullable,
  for verifier to have correct assumptions about test_1() programs;
- patch #2 modifies dummy_st_ops/dummy_init_ret_value to trigger NULL
  dereference with both GCC and LLVM (if patch #1 is not applied);
- patch #3 adjusts a few dummy_st_ops test cases to avoid passing NULL
  for 'state' parameter of test_2() and test_sleepable() functions,
  as parameters of these functions are not marked as nullable;
- patch #4 adjusts bpf_dummy_struct_ops to reject test execution of
  programs if NULL is passed for non-nullable parameter;
- patch #5 adds a test to verify logic from patch #4.
====================

Link: https://lore.kernel.org/r/20240424012821.595216-1-eddyz87@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
  • Loading branch information
Alexei Starovoitov committed Apr 25, 2024
2 parents 638a485 + 6a2d30d commit a311c3f
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 8 deletions.
55 changes: 52 additions & 3 deletions net/bpf/bpf_dummy_struct_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,51 @@ static int dummy_ops_call_op(void *image, struct bpf_dummy_ops_test_args *args)
args->args[3], args->args[4]);
}

static const struct bpf_ctx_arg_aux *find_ctx_arg_info(struct bpf_prog_aux *aux, int offset)
{
int i;

for (i = 0; i < aux->ctx_arg_info_size; i++)
if (aux->ctx_arg_info[i].offset == offset)
return &aux->ctx_arg_info[i];

return NULL;
}

/* There is only one check at the moment:
* - zero should not be passed for pointer parameters not marked as nullable.
*/
static int check_test_run_args(struct bpf_prog *prog, struct bpf_dummy_ops_test_args *args)
{
const struct btf_type *func_proto = prog->aux->attach_func_proto;

for (u32 arg_no = 0; arg_no < btf_type_vlen(func_proto) ; ++arg_no) {
const struct btf_param *param = &btf_params(func_proto)[arg_no];
const struct bpf_ctx_arg_aux *info;
const struct btf_type *t;
int offset;

if (args->args[arg_no] != 0)
continue;

/* Program is validated already, so there is no need
* to check if t is NULL.
*/
t = btf_type_skip_modifiers(bpf_dummy_ops_btf, param->type, NULL);
if (!btf_type_is_ptr(t))
continue;

offset = btf_ctx_arg_offset(bpf_dummy_ops_btf, func_proto, arg_no);
info = find_ctx_arg_info(prog->aux, offset);
if (info && (info->reg_type & PTR_MAYBE_NULL))
continue;

return -EINVAL;
}

return 0;
}

extern const struct bpf_link_ops bpf_struct_ops_link_lops;

int bpf_struct_ops_test_run(struct bpf_prog *prog, const union bpf_attr *kattr,
Expand All @@ -87,7 +132,7 @@ int bpf_struct_ops_test_run(struct bpf_prog *prog, const union bpf_attr *kattr,
const struct bpf_struct_ops *st_ops = &bpf_bpf_dummy_ops;
const struct btf_type *func_proto;
struct bpf_dummy_ops_test_args *args;
struct bpf_tramp_links *tlinks;
struct bpf_tramp_links *tlinks = NULL;
struct bpf_tramp_link *link = NULL;
void *image = NULL;
unsigned int op_idx;
Expand All @@ -109,6 +154,10 @@ int bpf_struct_ops_test_run(struct bpf_prog *prog, const union bpf_attr *kattr,
if (IS_ERR(args))
return PTR_ERR(args);

err = check_test_run_args(prog, args);
if (err)
goto out;

tlinks = kcalloc(BPF_TRAMP_MAX, sizeof(*tlinks), GFP_KERNEL);
if (!tlinks) {
err = -ENOMEM;
Expand Down Expand Up @@ -232,7 +281,7 @@ static void bpf_dummy_unreg(void *kdata)
{
}

static int bpf_dummy_test_1(struct bpf_dummy_ops_state *cb)
static int bpf_dummy_ops__test_1(struct bpf_dummy_ops_state *cb__nullable)
{
return 0;
}
Expand All @@ -249,7 +298,7 @@ static int bpf_dummy_test_sleepable(struct bpf_dummy_ops_state *cb)
}

static struct bpf_dummy_ops __bpf_bpf_dummy_ops = {
.test_1 = bpf_dummy_test_1,
.test_1 = bpf_dummy_ops__test_1,
.test_2 = bpf_dummy_test_2,
.test_sleepable = bpf_dummy_test_sleepable,
};
Expand Down
34 changes: 32 additions & 2 deletions tools/testing/selftests/bpf/prog_tests/dummy_st_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ static void test_dummy_init_ptr_arg(void)

static void test_dummy_multiple_args(void)
{
__u64 args[5] = {0, -100, 0x8a5f, 'c', 0x1234567887654321ULL};
struct bpf_dummy_ops_state st = { 7 };
__u64 args[5] = {(__u64)&st, -100, 0x8a5f, 'c', 0x1234567887654321ULL};
LIBBPF_OPTS(bpf_test_run_opts, attr,
.ctx_in = args,
.ctx_size_in = sizeof(args),
Expand All @@ -115,6 +116,7 @@ static void test_dummy_multiple_args(void)
fd = bpf_program__fd(skel->progs.test_2);
err = bpf_prog_test_run_opts(fd, &attr);
ASSERT_OK(err, "test_run");
args[0] = 7;
for (i = 0; i < ARRAY_SIZE(args); i++) {
snprintf(name, sizeof(name), "arg %zu", i);
ASSERT_EQ(skel->bss->test_2_args[i], args[i], name);
Expand All @@ -125,7 +127,8 @@ static void test_dummy_multiple_args(void)

static void test_dummy_sleepable(void)
{
__u64 args[1] = {0};
struct bpf_dummy_ops_state st;
__u64 args[1] = {(__u64)&st};
LIBBPF_OPTS(bpf_test_run_opts, attr,
.ctx_in = args,
.ctx_size_in = sizeof(args),
Expand All @@ -144,6 +147,31 @@ static void test_dummy_sleepable(void)
dummy_st_ops_success__destroy(skel);
}

/* dummy_st_ops.test_sleepable() parameter is not marked as nullable,
* thus bpf_prog_test_run_opts() below should be rejected as it tries
* to pass NULL for this parameter.
*/
static void test_dummy_sleepable_reject_null(void)
{
__u64 args[1] = {0};
LIBBPF_OPTS(bpf_test_run_opts, attr,
.ctx_in = args,
.ctx_size_in = sizeof(args),
);
struct dummy_st_ops_success *skel;
int fd, err;

skel = dummy_st_ops_success__open_and_load();
if (!ASSERT_OK_PTR(skel, "dummy_st_ops_load"))
return;

fd = bpf_program__fd(skel->progs.test_sleepable);
err = bpf_prog_test_run_opts(fd, &attr);
ASSERT_EQ(err, -EINVAL, "test_run");

dummy_st_ops_success__destroy(skel);
}

void test_dummy_st_ops(void)
{
if (test__start_subtest("dummy_st_ops_attach"))
Expand All @@ -156,6 +184,8 @@ void test_dummy_st_ops(void)
test_dummy_multiple_args();
if (test__start_subtest("dummy_sleepable"))
test_dummy_sleepable();
if (test__start_subtest("dummy_sleepable_reject_null"))
test_dummy_sleepable_reject_null();

RUN_TESTS(dummy_st_ops_fail);
}
15 changes: 12 additions & 3 deletions tools/testing/selftests/bpf/progs/dummy_st_ops_success.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ int BPF_PROG(test_1, struct bpf_dummy_ops_state *state)
{
int ret;

if (!state)
return 0xf2f3f4f5;
/* Check that 'state' nullable status is detected correctly.
* If 'state' argument would be assumed non-null by verifier
* the code below would be deleted as dead (which it shouldn't).
* Hide it from the compiler behind 'asm' block to avoid
* unnecessary optimizations.
*/
asm volatile (
"if %[state] != 0 goto +2;"
"r0 = 0xf2f3f4f5;"
"exit;"
::[state]"p"(state));

ret = state->val;
state->val = 0x5a;
Expand All @@ -25,7 +34,7 @@ SEC("struct_ops/test_2")
int BPF_PROG(test_2, struct bpf_dummy_ops_state *state, int a1, unsigned short a2,
char a3, unsigned long a4)
{
test_2_args[0] = (unsigned long)state;
test_2_args[0] = state->val;
test_2_args[1] = a1;
test_2_args[2] = a2;
test_2_args[3] = a3;
Expand Down

0 comments on commit a311c3f

Please sign in to comment.