The dynptr passed to a bpf_user_ringbuf_drain() callback, and values derived from it, must not be used after that callback returns. Exercise escapes through the callback context, slices returned by bpf_dynptr_data() and bpf_dynptr_slice(), and clones stored in the caller's frame. Also cover an inner callback's dynptr escaping into an outer callback. A program that leaves either the outer or the inner dynptr in the same slot and only tests it for NULL must still load. This covers the frame id pairing in join_func(). Signed-off-by: Ihor Solodrai --- .../selftests/bpf/progs/user_ringbuf_fail.c | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c index c0d0422b8030..6606e6c56329 100644 --- a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c +++ b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */ +#include #include #include #include "bpf_misc.h" +#include "bpf_kfuncs.h" char _license[] SEC("license") = "GPL"; @@ -243,3 +245,162 @@ int user_ringbuf_callback_const_ptr_to_dynptr_reg_off(void *ctx) callback_adjust_bpf_dynptr_reg_off, NULL, 0); return 0; } + +/* The sample goes back to the producer as soon as the callback returns. */ +struct dynptr_ctx { + struct bpf_dynptr *saved; +}; + +static long callback_park_dynptr(struct bpf_dynptr *dynptr, void *context) +{ + struct dynptr_ctx *c = context; + + c->saved = dynptr; + return 0; +} + +SEC("?raw_tp") +__failure __msg("R3 type=scalar expected=fp, dynptr_ptr") +int user_ringbuf_callback_park_dynptr(void *ctx) +{ + struct dynptr_ctx c = {}; + char buf[8] = {}; + + bpf_user_ringbuf_drain(&user_ringbuf, callback_park_dynptr, &c, 0); + if (c.saved) + bpf_dynptr_read(buf, sizeof(buf), c.saved, 0, 0); + return buf[0]; +} + +struct slice_ctx { + char *p; +}; + +static long callback_park_data_slice(struct bpf_dynptr *dynptr, void *context) +{ + struct slice_ctx *c = context; + + c->p = bpf_dynptr_data(dynptr, 0, 8); + return 0; +} + +SEC("?raw_tp") +__failure __msg("invalid mem access 'scalar'") +int user_ringbuf_callback_park_data_slice(void *ctx) +{ + struct slice_ctx c = {}; + + bpf_user_ringbuf_drain(&user_ringbuf, callback_park_data_slice, &c, 0); + if (c.p) + return c.p[0]; + return 0; +} + +static long callback_park_kfunc_slice(struct bpf_dynptr *dynptr, void *context) +{ + struct slice_ctx *c = context; + + c->p = bpf_dynptr_slice(dynptr, 0, NULL, 8); + return 0; +} + +SEC("?raw_tp") +__failure __msg("invalid mem access 'scalar'") +int user_ringbuf_callback_park_kfunc_slice(void *ctx) +{ + struct slice_ctx c = {}; + + bpf_user_ringbuf_drain(&user_ringbuf, callback_park_kfunc_slice, &c, 0); + if (c.p) + return c.p[0]; + return 0; +} + +struct clone_ctx { + struct bpf_dynptr clone; + __u64 armed; +}; + +static long callback_park_clone(struct bpf_dynptr *dynptr, void *context) +{ + struct clone_ctx *c = context; + + bpf_dynptr_clone(dynptr, &c->clone); + c->armed = 1; + return 0; +} + +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as R3") +int user_ringbuf_callback_park_clone(void *ctx) +{ + struct clone_ctx c = {}; + char buf[8] = {}; + + bpf_user_ringbuf_drain(&user_ringbuf, callback_park_clone, &c, 0); + if (c.armed) + bpf_dynptr_read(buf, sizeof(buf), &c.clone, 0, 0); + return buf[0]; +} + +SEC("?raw_tp") +__failure __msg("Expected an initialized dynptr as R1") +int user_ringbuf_callback_park_clone_then_slice(void *ctx) +{ + struct clone_ctx c = {}; + char *p; + + bpf_user_ringbuf_drain(&user_ringbuf, callback_park_clone, &c, 0); + if (c.armed) { + p = bpf_dynptr_data(&c.clone, 0, 8); + if (p) + return p[0]; + } + return 0; +} + +/* An inner drain's dynptr must not escape into the outer callback either. */ +static long callback_park_outer(struct bpf_dynptr *dynptr, void *context) +{ + struct dynptr_ctx inner = {}; + char buf[8] = {}; + + bpf_user_ringbuf_drain(&user_ringbuf, callback_park_dynptr, &inner, 0); + if (inner.saved) + bpf_dynptr_read(buf, sizeof(buf), inner.saved, 0, 0); + return buf[0] ? 1 : 0; +} + +SEC("?raw_tp") +__failure __msg("R3 type=scalar expected=fp, dynptr_ptr") +int user_ringbuf_callback_nested_park_inner(void *ctx) +{ + bpf_user_ringbuf_drain(&user_ringbuf, callback_park_outer, NULL, 0); + return 0; +} + +static long callback_maybe_park(struct bpf_dynptr *dynptr, void *context) +{ + struct dynptr_ctx *c = context; + + if (bpf_get_prandom_u32() & 1) + c->saved = dynptr; + return 0; +} + +/* inner.saved ends up holding either the outer or the inner dynptr. */ +static long callback_maybe_park_outer(struct bpf_dynptr *dynptr, void *context) +{ + struct dynptr_ctx inner = { .saved = dynptr }; + + bpf_user_ringbuf_drain(&user_ringbuf, callback_maybe_park, &inner, 0); + return inner.saved ? 1 : 0; +} + +SEC("?raw_tp") +__success +int user_ringbuf_callback_nested_maybe_park(void *ctx) +{ + bpf_user_ringbuf_drain(&user_ringbuf, callback_maybe_park_outer, NULL, 0); + return 0; +} -- 2.56.0