Skip to content

Commit a311c3f

Browse files
author
Alexei Starovoitov
committed
Merge branch 'check-bpf_dummy_struct_ops-program-params-for-test-runs'
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/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
2 parents 638a485 + 6a2d30d commit a311c3f

File tree

3 files changed

+96
-8
lines changed

3 files changed

+96
-8
lines changed

net/bpf/bpf_dummy_struct_ops.c

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,51 @@ static int dummy_ops_call_op(void *image, struct bpf_dummy_ops_test_args *args)
7979
args->args[3], args->args[4]);
8080
}
8181

82+
static const struct bpf_ctx_arg_aux *find_ctx_arg_info(struct bpf_prog_aux *aux, int offset)
83+
{
84+
int i;
85+
86+
for (i = 0; i < aux->ctx_arg_info_size; i++)
87+
if (aux->ctx_arg_info[i].offset == offset)
88+
return &aux->ctx_arg_info[i];
89+
90+
return NULL;
91+
}
92+
93+
/* There is only one check at the moment:
94+
* - zero should not be passed for pointer parameters not marked as nullable.
95+
*/
96+
static int check_test_run_args(struct bpf_prog *prog, struct bpf_dummy_ops_test_args *args)
97+
{
98+
const struct btf_type *func_proto = prog->aux->attach_func_proto;
99+
100+
for (u32 arg_no = 0; arg_no < btf_type_vlen(func_proto) ; ++arg_no) {
101+
const struct btf_param *param = &btf_params(func_proto)[arg_no];
102+
const struct bpf_ctx_arg_aux *info;
103+
const struct btf_type *t;
104+
int offset;
105+
106+
if (args->args[arg_no] != 0)
107+
continue;
108+
109+
/* Program is validated already, so there is no need
110+
* to check if t is NULL.
111+
*/
112+
t = btf_type_skip_modifiers(bpf_dummy_ops_btf, param->type, NULL);
113+
if (!btf_type_is_ptr(t))
114+
continue;
115+
116+
offset = btf_ctx_arg_offset(bpf_dummy_ops_btf, func_proto, arg_no);
117+
info = find_ctx_arg_info(prog->aux, offset);
118+
if (info && (info->reg_type & PTR_MAYBE_NULL))
119+
continue;
120+
121+
return -EINVAL;
122+
}
123+
124+
return 0;
125+
}
126+
82127
extern const struct bpf_link_ops bpf_struct_ops_link_lops;
83128

84129
int bpf_struct_ops_test_run(struct bpf_prog *prog, const union bpf_attr *kattr,
@@ -87,7 +132,7 @@ int bpf_struct_ops_test_run(struct bpf_prog *prog, const union bpf_attr *kattr,
87132
const struct bpf_struct_ops *st_ops = &bpf_bpf_dummy_ops;
88133
const struct btf_type *func_proto;
89134
struct bpf_dummy_ops_test_args *args;
90-
struct bpf_tramp_links *tlinks;
135+
struct bpf_tramp_links *tlinks = NULL;
91136
struct bpf_tramp_link *link = NULL;
92137
void *image = NULL;
93138
unsigned int op_idx;
@@ -109,6 +154,10 @@ int bpf_struct_ops_test_run(struct bpf_prog *prog, const union bpf_attr *kattr,
109154
if (IS_ERR(args))
110155
return PTR_ERR(args);
111156

157+
err = check_test_run_args(prog, args);
158+
if (err)
159+
goto out;
160+
112161
tlinks = kcalloc(BPF_TRAMP_MAX, sizeof(*tlinks), GFP_KERNEL);
113162
if (!tlinks) {
114163
err = -ENOMEM;
@@ -232,7 +281,7 @@ static void bpf_dummy_unreg(void *kdata)
232281
{
233282
}
234283

235-
static int bpf_dummy_test_1(struct bpf_dummy_ops_state *cb)
284+
static int bpf_dummy_ops__test_1(struct bpf_dummy_ops_state *cb__nullable)
236285
{
237286
return 0;
238287
}
@@ -249,7 +298,7 @@ static int bpf_dummy_test_sleepable(struct bpf_dummy_ops_state *cb)
249298
}
250299

251300
static struct bpf_dummy_ops __bpf_bpf_dummy_ops = {
252-
.test_1 = bpf_dummy_test_1,
301+
.test_1 = bpf_dummy_ops__test_1,
253302
.test_2 = bpf_dummy_test_2,
254303
.test_sleepable = bpf_dummy_test_sleepable,
255304
};

tools/testing/selftests/bpf/prog_tests/dummy_st_ops.c

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ static void test_dummy_init_ptr_arg(void)
9898

9999
static void test_dummy_multiple_args(void)
100100
{
101-
__u64 args[5] = {0, -100, 0x8a5f, 'c', 0x1234567887654321ULL};
101+
struct bpf_dummy_ops_state st = { 7 };
102+
__u64 args[5] = {(__u64)&st, -100, 0x8a5f, 'c', 0x1234567887654321ULL};
102103
LIBBPF_OPTS(bpf_test_run_opts, attr,
103104
.ctx_in = args,
104105
.ctx_size_in = sizeof(args),
@@ -115,6 +116,7 @@ static void test_dummy_multiple_args(void)
115116
fd = bpf_program__fd(skel->progs.test_2);
116117
err = bpf_prog_test_run_opts(fd, &attr);
117118
ASSERT_OK(err, "test_run");
119+
args[0] = 7;
118120
for (i = 0; i < ARRAY_SIZE(args); i++) {
119121
snprintf(name, sizeof(name), "arg %zu", i);
120122
ASSERT_EQ(skel->bss->test_2_args[i], args[i], name);
@@ -125,7 +127,8 @@ static void test_dummy_multiple_args(void)
125127

126128
static void test_dummy_sleepable(void)
127129
{
128-
__u64 args[1] = {0};
130+
struct bpf_dummy_ops_state st;
131+
__u64 args[1] = {(__u64)&st};
129132
LIBBPF_OPTS(bpf_test_run_opts, attr,
130133
.ctx_in = args,
131134
.ctx_size_in = sizeof(args),
@@ -144,6 +147,31 @@ static void test_dummy_sleepable(void)
144147
dummy_st_ops_success__destroy(skel);
145148
}
146149

150+
/* dummy_st_ops.test_sleepable() parameter is not marked as nullable,
151+
* thus bpf_prog_test_run_opts() below should be rejected as it tries
152+
* to pass NULL for this parameter.
153+
*/
154+
static void test_dummy_sleepable_reject_null(void)
155+
{
156+
__u64 args[1] = {0};
157+
LIBBPF_OPTS(bpf_test_run_opts, attr,
158+
.ctx_in = args,
159+
.ctx_size_in = sizeof(args),
160+
);
161+
struct dummy_st_ops_success *skel;
162+
int fd, err;
163+
164+
skel = dummy_st_ops_success__open_and_load();
165+
if (!ASSERT_OK_PTR(skel, "dummy_st_ops_load"))
166+
return;
167+
168+
fd = bpf_program__fd(skel->progs.test_sleepable);
169+
err = bpf_prog_test_run_opts(fd, &attr);
170+
ASSERT_EQ(err, -EINVAL, "test_run");
171+
172+
dummy_st_ops_success__destroy(skel);
173+
}
174+
147175
void test_dummy_st_ops(void)
148176
{
149177
if (test__start_subtest("dummy_st_ops_attach"))
@@ -156,6 +184,8 @@ void test_dummy_st_ops(void)
156184
test_dummy_multiple_args();
157185
if (test__start_subtest("dummy_sleepable"))
158186
test_dummy_sleepable();
187+
if (test__start_subtest("dummy_sleepable_reject_null"))
188+
test_dummy_sleepable_reject_null();
159189

160190
RUN_TESTS(dummy_st_ops_fail);
161191
}

tools/testing/selftests/bpf/progs/dummy_st_ops_success.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,17 @@ int BPF_PROG(test_1, struct bpf_dummy_ops_state *state)
1111
{
1212
int ret;
1313

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

1726
ret = state->val;
1827
state->val = 0x5a;
@@ -25,7 +34,7 @@ SEC("struct_ops/test_2")
2534
int BPF_PROG(test_2, struct bpf_dummy_ops_state *state, int a1, unsigned short a2,
2635
char a3, unsigned long a4)
2736
{
28-
test_2_args[0] = (unsigned long)state;
37+
test_2_args[0] = state->val;
2938
test_2_args[1] = a1;
3039
test_2_args[2] = a2;
3140
test_2_args[3] = a3;

0 commit comments

Comments
 (0)