Merge branch 'Fix reference state management for synchronous callbacks'
Kumar Kartikeya Dwivedi says: ==================== This is patch 1, 2 + their individual tests split into a separate series from the RFC, so that these can be taken in, while we continue working towards a fix for handling stack access inside the callback. Changelog: ---------- v1 -> v2: v1: https://lore.kernel.org/bpf/20220822131923.21476-1-memxor@gmail.com * Fix error for test_progs-no_alu32 due to distinct alloc_insn in errstr RFC v1 -> v1: RFC v1: https://lore.kernel.org/bpf/20220815051540.18791-1-memxor@gmail.com * Fix up commit log to add more explanation (Alexei) * Split reference state fix out into a separate series ==================== Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
096830808c
@ -212,6 +212,17 @@ struct bpf_reference_state {
|
||||
* is used purely to inform the user of a reference leak.
|
||||
*/
|
||||
int insn_idx;
|
||||
/* There can be a case like:
|
||||
* main (frame 0)
|
||||
* cb (frame 1)
|
||||
* func (frame 3)
|
||||
* cb (frame 4)
|
||||
* Hence for frame 4, if callback_ref just stored boolean, it would be
|
||||
* impossible to distinguish nested callback refs. Hence store the
|
||||
* frameno and compare that to callback_ref in check_reference_leak when
|
||||
* exiting a callback function.
|
||||
*/
|
||||
int callback_ref;
|
||||
};
|
||||
|
||||
/* state of the program:
|
||||
|
@ -1613,10 +1613,6 @@ bpf_base_func_proto(enum bpf_func_id func_id)
|
||||
return &bpf_ringbuf_submit_dynptr_proto;
|
||||
case BPF_FUNC_ringbuf_discard_dynptr:
|
||||
return &bpf_ringbuf_discard_dynptr_proto;
|
||||
case BPF_FUNC_for_each_map_elem:
|
||||
return &bpf_for_each_map_elem_proto;
|
||||
case BPF_FUNC_loop:
|
||||
return &bpf_loop_proto;
|
||||
case BPF_FUNC_strncmp:
|
||||
return &bpf_strncmp_proto;
|
||||
case BPF_FUNC_strtol:
|
||||
@ -1659,6 +1655,10 @@ bpf_base_func_proto(enum bpf_func_id func_id)
|
||||
return &bpf_timer_cancel_proto;
|
||||
case BPF_FUNC_kptr_xchg:
|
||||
return &bpf_kptr_xchg_proto;
|
||||
case BPF_FUNC_for_each_map_elem:
|
||||
return &bpf_for_each_map_elem_proto;
|
||||
case BPF_FUNC_loop:
|
||||
return &bpf_loop_proto;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1092,6 +1092,7 @@ static int acquire_reference_state(struct bpf_verifier_env *env, int insn_idx)
|
||||
id = ++env->id_gen;
|
||||
state->refs[new_ofs].id = id;
|
||||
state->refs[new_ofs].insn_idx = insn_idx;
|
||||
state->refs[new_ofs].callback_ref = state->in_callback_fn ? state->frameno : 0;
|
||||
|
||||
return id;
|
||||
}
|
||||
@ -1104,6 +1105,9 @@ static int release_reference_state(struct bpf_func_state *state, int ptr_id)
|
||||
last_idx = state->acquired_refs - 1;
|
||||
for (i = 0; i < state->acquired_refs; i++) {
|
||||
if (state->refs[i].id == ptr_id) {
|
||||
/* Cannot release caller references in callbacks */
|
||||
if (state->in_callback_fn && state->refs[i].callback_ref != state->frameno)
|
||||
return -EINVAL;
|
||||
if (last_idx && i != last_idx)
|
||||
memcpy(&state->refs[i], &state->refs[last_idx],
|
||||
sizeof(*state->refs));
|
||||
@ -6915,10 +6919,17 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
|
||||
caller->regs[BPF_REG_0] = *r0;
|
||||
}
|
||||
|
||||
/* Transfer references to the caller */
|
||||
err = copy_reference_state(caller, callee);
|
||||
if (err)
|
||||
return err;
|
||||
/* callback_fn frame should have released its own additions to parent's
|
||||
* reference state at this point, or check_reference_leak would
|
||||
* complain, hence it must be the same as the caller. There is no need
|
||||
* to copy it back.
|
||||
*/
|
||||
if (!callee->in_callback_fn) {
|
||||
/* Transfer references to the caller */
|
||||
err = copy_reference_state(caller, callee);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
*insn_idx = callee->callsite + 1;
|
||||
if (env->log.level & BPF_LOG_LEVEL) {
|
||||
@ -7042,13 +7053,20 @@ record_func_key(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
|
||||
static int check_reference_leak(struct bpf_verifier_env *env)
|
||||
{
|
||||
struct bpf_func_state *state = cur_func(env);
|
||||
bool refs_lingering = false;
|
||||
int i;
|
||||
|
||||
if (state->frameno && !state->in_callback_fn)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < state->acquired_refs; i++) {
|
||||
if (state->in_callback_fn && state->refs[i].callback_ref != state->frameno)
|
||||
continue;
|
||||
verbose(env, "Unreleased reference id=%d alloc_insn=%d\n",
|
||||
state->refs[i].id, state->refs[i].insn_idx);
|
||||
refs_lingering = true;
|
||||
}
|
||||
return state->acquired_refs ? -EINVAL : 0;
|
||||
return refs_lingering ? -EINVAL : 0;
|
||||
}
|
||||
|
||||
static int check_bpf_snprintf_call(struct bpf_verifier_env *env,
|
||||
@ -12337,6 +12355,16 @@ static int do_check(struct bpf_verifier_env *env)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* We must do check_reference_leak here before
|
||||
* prepare_func_exit to handle the case when
|
||||
* state->curframe > 0, it may be a callback
|
||||
* function, for which reference_state must
|
||||
* match caller reference state when it exits.
|
||||
*/
|
||||
err = check_reference_leak(env);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (state->curframe) {
|
||||
/* exit from nested function */
|
||||
err = prepare_func_exit(env, &env->insn_idx);
|
||||
@ -12346,10 +12374,6 @@ static int do_check(struct bpf_verifier_env *env)
|
||||
continue;
|
||||
}
|
||||
|
||||
err = check_reference_leak(env);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = check_return_code(env);
|
||||
if (err)
|
||||
return err;
|
||||
|
48
tools/testing/selftests/bpf/prog_tests/cb_refs.c
Normal file
48
tools/testing/selftests/bpf/prog_tests/cb_refs.c
Normal file
@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "bpf/libbpf.h"
|
||||
#include <test_progs.h>
|
||||
#include <network_helpers.h>
|
||||
|
||||
#include "cb_refs.skel.h"
|
||||
|
||||
static char log_buf[1024 * 1024];
|
||||
|
||||
struct {
|
||||
const char *prog_name;
|
||||
const char *err_msg;
|
||||
} cb_refs_tests[] = {
|
||||
{ "underflow_prog", "reference has not been acquired before" },
|
||||
{ "leak_prog", "Unreleased reference" },
|
||||
{ "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */
|
||||
{ "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */
|
||||
};
|
||||
|
||||
void test_cb_refs(void)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_object_open_opts, opts, .kernel_log_buf = log_buf,
|
||||
.kernel_log_size = sizeof(log_buf),
|
||||
.kernel_log_level = 1);
|
||||
struct bpf_program *prog;
|
||||
struct cb_refs *skel;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(cb_refs_tests); i++) {
|
||||
LIBBPF_OPTS(bpf_test_run_opts, run_opts,
|
||||
.data_in = &pkt_v4,
|
||||
.data_size_in = sizeof(pkt_v4),
|
||||
.repeat = 1,
|
||||
);
|
||||
skel = cb_refs__open_opts(&opts);
|
||||
if (!ASSERT_OK_PTR(skel, "cb_refs__open_and_load"))
|
||||
return;
|
||||
prog = bpf_object__find_program_by_name(skel->obj, cb_refs_tests[i].prog_name);
|
||||
bpf_program__set_autoload(prog, true);
|
||||
if (!ASSERT_ERR(cb_refs__load(skel), "cb_refs__load"))
|
||||
bpf_prog_test_run_opts(bpf_program__fd(prog), &run_opts);
|
||||
if (!ASSERT_OK_PTR(strstr(log_buf, cb_refs_tests[i].err_msg), "expected error message")) {
|
||||
fprintf(stderr, "Expected: %s\n", cb_refs_tests[i].err_msg);
|
||||
fprintf(stderr, "Verifier: %s\n", log_buf);
|
||||
}
|
||||
cb_refs__destroy(skel);
|
||||
}
|
||||
}
|
116
tools/testing/selftests/bpf/progs/cb_refs.c
Normal file
116
tools/testing/selftests/bpf/progs/cb_refs.c
Normal file
@ -0,0 +1,116 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
struct map_value {
|
||||
struct prog_test_ref_kfunc __kptr_ref *ptr;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__type(key, int);
|
||||
__type(value, struct map_value);
|
||||
__uint(max_entries, 16);
|
||||
} array_map SEC(".maps");
|
||||
|
||||
extern struct prog_test_ref_kfunc *bpf_kfunc_call_test_acquire(unsigned long *sp) __ksym;
|
||||
extern void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym;
|
||||
|
||||
static __noinline int cb1(void *map, void *key, void *value, void *ctx)
|
||||
{
|
||||
void *p = *(void **)ctx;
|
||||
bpf_kfunc_call_test_release(p);
|
||||
/* Without the fix this would cause underflow */
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?tc")
|
||||
int underflow_prog(void *ctx)
|
||||
{
|
||||
struct prog_test_ref_kfunc *p;
|
||||
unsigned long sl = 0;
|
||||
|
||||
p = bpf_kfunc_call_test_acquire(&sl);
|
||||
if (!p)
|
||||
return 0;
|
||||
bpf_for_each_map_elem(&array_map, cb1, &p, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __always_inline int cb2(void *map, void *key, void *value, void *ctx)
|
||||
{
|
||||
unsigned long sl = 0;
|
||||
|
||||
*(void **)ctx = bpf_kfunc_call_test_acquire(&sl);
|
||||
/* Without the fix this would leak memory */
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?tc")
|
||||
int leak_prog(void *ctx)
|
||||
{
|
||||
struct prog_test_ref_kfunc *p;
|
||||
struct map_value *v;
|
||||
unsigned long sl;
|
||||
|
||||
v = bpf_map_lookup_elem(&array_map, &(int){0});
|
||||
if (!v)
|
||||
return 0;
|
||||
|
||||
p = NULL;
|
||||
bpf_for_each_map_elem(&array_map, cb2, &p, 0);
|
||||
p = bpf_kptr_xchg(&v->ptr, p);
|
||||
if (p)
|
||||
bpf_kfunc_call_test_release(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __always_inline int cb(void *map, void *key, void *value, void *ctx)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __always_inline int cb3(void *map, void *key, void *value, void *ctx)
|
||||
{
|
||||
unsigned long sl = 0;
|
||||
void *p;
|
||||
|
||||
bpf_kfunc_call_test_acquire(&sl);
|
||||
bpf_for_each_map_elem(&array_map, cb, &p, 0);
|
||||
/* It should only complain here, not in cb. This is why we need
|
||||
* callback_ref to be set to frameno.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?tc")
|
||||
int nested_cb(void *ctx)
|
||||
{
|
||||
struct prog_test_ref_kfunc *p;
|
||||
unsigned long sl = 0;
|
||||
int sp = 0;
|
||||
|
||||
p = bpf_kfunc_call_test_acquire(&sl);
|
||||
if (!p)
|
||||
return 0;
|
||||
bpf_for_each_map_elem(&array_map, cb3, &sp, 0);
|
||||
bpf_kfunc_call_test_release(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?tc")
|
||||
int non_cb_transfer_ref(void *ctx)
|
||||
{
|
||||
struct prog_test_ref_kfunc *p;
|
||||
unsigned long sl = 0;
|
||||
|
||||
p = bpf_kfunc_call_test_acquire(&sl);
|
||||
if (!p)
|
||||
return 0;
|
||||
cb1(NULL, NULL, NULL, &p);
|
||||
bpf_kfunc_call_test_acquire(&sl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
Loading…
x
Reference in New Issue
Block a user