* [PATCH bpf v2 0/2] bpf: Preserve escaped dynptr slice ancestry
@ 2026-09-10 4:40 Xu Yunxiang
2026-09-10 4:40 ` [PATCH bpf v2 1/2] bpf: Preserve escaped dynptr slice ancestry on subprog return Xu Yunxiang
2026-09-10 4:40 ` [PATCH bpf v2 2/2] selftests/bpf: Test dynptr slices escaping a call frame Xu Yunxiang
0 siblings, 2 replies; 6+ messages in thread
From: Xu Yunxiang @ 2026-09-10 4:40 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Amery Hung, Sashiko
Sashiko reported a verifier lifetime gap for a dynptr slice derived from a
clone in a subprogram. The slice can escape into its caller before the
subprogram returns. Once the clone's frame is gone, the slice still names
the clone id as its parent, but that graph node no longer exists. Releasing
the original dynptr therefore fails to reach the escaped slice, and the
verifier permits a subsequent access to the released ring buffer
reservation.
The same disconnect affects a dynptr constructed from another referenced
object. In particular, an skb dynptr created from a referenced qdisc skb
has the skb reference as its parent even though the skb dynptr type does
not own a reference itself.
Patch 1 reparents surviving slices from every callee-local dynptr that has
a tracked parent before the callee frame is freed. Patch 2 tests both the
ring buffer clone case and an skb slice returned from a subprogram and used
after the qdisc skb is released.
On the exact unpatched base, the new ring buffer stale-access test and the
qdisc stale-access test both loaded. The v1 fix rejected the ring buffer
case, but still allowed the qdisc case. With v2, both stale accesses are
rejected and the legal ring buffer control still loads.
The checks require CAP_BPF. This series is submitted as a verifier
correctness fix.
Please queue the verifier fix for stable after it reaches the BPF tree.
Tests:
- W=1 make O=<build> kernel/bpf/verifier.o
- make O=<build> -j12 bzImage modules
- test_progs -t dynptr -v --workers=1
(Summary: 2/134 PASSED, 0 SKIPPED, 0/0 FAILED)
- test_progs -t ns_bpf_qdisc/invalid_dynptr_returned_slice -v
--workers=1
(Summary: 1/1 PASSED, 0 SKIPPED, 0/0 FAILED)
- baseline/v1/v2 differential checks for the ring buffer and qdisc cases
The full unfiltered BPF selftest suite was not run.
Changes in v2:
- Reparent slices from any dynptr with a tracked parent, covering skb
dynptrs constructed from referenced qdisc skbs.
- Add a qdisc regression test for a returned slice used after skb
release.
- Fix the selftest comment style and mark the expected failing access.
v1: https://lore.kernel.org/r/20260909042858.1734125-1-xyx2021@mail.ustc.edu.cn
Xu Yunxiang (2):
bpf: Preserve escaped dynptr slice ancestry on subprog return
selftests/bpf: Test dynptr slices escaping a call frame
kernel/bpf/verifier.c | 34 +++++++++
.../selftests/bpf/prog_tests/bpf_qdisc.c | 2 +
...disc_fail__invalid_dynptr_returned_slice.c | 76 +++++++++++++++++++
.../testing/selftests/bpf/progs/dynptr_fail.c | 49 ++++++++++++
4 files changed, 161 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_returned_slice.c
base-commit: 15e2565f1c43771af0bc5324971cabaad79ac286
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH bpf v2 1/2] bpf: Preserve escaped dynptr slice ancestry on subprog return
2026-09-10 4:40 [PATCH bpf v2 0/2] bpf: Preserve escaped dynptr slice ancestry Xu Yunxiang
@ 2026-09-10 4:40 ` Xu Yunxiang
2026-09-10 5:01 ` sashiko-bot
2026-09-10 17:22 ` Amery Hung
2026-09-10 4:40 ` [PATCH bpf v2 2/2] selftests/bpf: Test dynptr slices escaping a call frame Xu Yunxiang
1 sibling, 2 replies; 6+ messages in thread
From: Xu Yunxiang @ 2026-09-10 4:40 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Amery Hung, Sashiko
Dynptr slices use parent_id to link their lifetime to the dynptr that
produced them. bpf_dynptr_clone() gives each clone a unique id while
retaining the shared reference parent_id.
Before the object relationship refactor, a slice also carried the shared
reference id directly. The refactor instead made the slice a child of its
source dynptr's unique id. If a callee derives a slice from a local clone
and returns the slice to its caller, freeing the callee removes that clone
id from the verifier state. A later release of the original dynptr can no
longer reach the escaped slice, so the verifier keeps accepting accesses
after the ring buffer reservation has been released.
A similar disconnect affects dynptrs constructed from referenced objects.
For example, a qdisc skb dynptr records the skb reference as its parent,
but the skb dynptr type itself does not own a reference.
Before freeing a callee, reparent surviving PTR_TO_MEM slices of every
dynptr with a parent to that parent. This preserves the lifetime link after
the local dynptr disappears for both reference-owning dynptrs and dynptrs
constructed from referenced objects.
Fixes: 308c7a0ae885 ("bpf: Refactor object relationship tracking and fix dynptr UAF bug")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/r/20260829040712.B1B511F000E9@smtp.kernel.org
Assisted-by: LLM
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
kernel/bpf/verifier.c | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 72a3f5998dd27..ee41b7386f752 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -800,6 +800,39 @@ static int dynptr_ref_cnt(struct bpf_verifier_env *env, int v_parent_id)
return ref_cnt;
}
+static void reparent_dynptr_slices_on_func_exit(struct bpf_verifier_env *env,
+ struct bpf_func_state *callee)
+{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ struct bpf_func_state *state;
+ struct bpf_reg_state *reg;
+ int i;
+
+ for (i = 0; i < callee->allocated_stack / BPF_REG_SIZE; i++) {
+ struct bpf_stack_state *slot = &callee->stack[i];
+ struct bpf_reg_state *dynptr = &slot->spilled_ptr;
+
+ if (slot->slot_type[0] != STACK_DYNPTR ||
+ !dynptr->dynptr.first_slot ||
+ !dynptr->parent_id)
+ continue;
+
+ /*
+ * A callee can spill a slice derived from its local dynptr into
+ * the caller's stack. The slice then outlives the dynptr id that
+ * links it to the rest of the object tree. Preserve that link by
+ * making escaped slices children of the dynptr's parent before
+ * the callee frame is freed.
+ */
+ bpf_for_each_reg_in_vstate(vstate, state, reg, ({
+ if (state == callee || reg->parent_id != dynptr->id ||
+ base_type(reg->type) != PTR_TO_MEM)
+ continue;
+ reg->parent_id = dynptr->parent_id;
+ }));
+ }
+}
+
static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
struct bpf_func_state *state, int spi)
{
@@ -10399,6 +10432,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
print_verifier_state(env, state, caller->frameno, true);
}
account_processed_insns(env, callee, caller);
+ reparent_dynptr_slices_on_func_exit(env, callee);
/* clear everything in the callee. In case of exceptional exits using
* bpf_throw, this will be done by copy_verifier_state for extra frames. */
free_func_state(callee);
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH bpf v2 2/2] selftests/bpf: Test dynptr slices escaping a call frame
2026-09-10 4:40 [PATCH bpf v2 0/2] bpf: Preserve escaped dynptr slice ancestry Xu Yunxiang
2026-09-10 4:40 ` [PATCH bpf v2 1/2] bpf: Preserve escaped dynptr slice ancestry on subprog return Xu Yunxiang
@ 2026-09-10 4:40 ` Xu Yunxiang
1 sibling, 0 replies; 6+ messages in thread
From: Xu Yunxiang @ 2026-09-10 4:40 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Amery Hung, Sashiko
Add ring buffer tests where a subprogram derives a slice from a local
dynptr clone and passes the slice back through the caller's stack. Verify
that the slice remains usable while the shared reservation is live and is
invalidated after the reservation is submitted.
Also add a qdisc test where a subprogram creates an skb dynptr from the
referenced skb argument and returns a data slice. Releasing the skb in the
caller must invalidate the returned slice.
Assisted-by: LLM
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
.../selftests/bpf/prog_tests/bpf_qdisc.c | 2 +
...disc_fail__invalid_dynptr_returned_slice.c | 76 +++++++++++++++++++
.../testing/selftests/bpf/progs/dynptr_fail.c | 49 ++++++++++++
3 files changed, 127 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_returned_slice.c
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_qdisc.c b/tools/testing/selftests/bpf/prog_tests/bpf_qdisc.c
index 6dbd1487343c0..122ecb7e98e2a 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_qdisc.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_qdisc.c
@@ -11,6 +11,7 @@
#include "bpf_qdisc_fail__invalid_dynptr.skel.h"
#include "bpf_qdisc_fail__invalid_dynptr_slice.skel.h"
#include "bpf_qdisc_fail__invalid_dynptr_cross_frame.skel.h"
+#include "bpf_qdisc_fail__invalid_dynptr_returned_slice.skel.h"
#include "bpf_qdisc_fail__untrusted_write.skel.h"
#include "bpf_qdisc_dynptr_use_after_invalidate_clone.skel.h"
@@ -230,6 +231,7 @@ void test_ns_bpf_qdisc(void)
test_incompl_ops();
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr);
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_cross_frame);
+ RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_returned_slice);
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_slice);
RUN_TESTS(bpf_qdisc_fail__untrusted_write);
RUN_TESTS(bpf_qdisc_dynptr_use_after_invalidate_clone);
diff --git a/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_returned_slice.c b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_returned_slice.c
new file mode 100644
index 0000000000000..940aba7d8abfe
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_returned_slice.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include "bpf_experimental.h"
+#include "bpf_qdisc_common.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+int proto;
+
+static __noinline struct ethhdr *slice_in_subprog(struct sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
+ return bpf_dynptr_slice(&ptr, 0, NULL, sizeof(struct ethhdr));
+}
+
+SEC("struct_ops")
+__failure __msg("invalid mem access 'scalar'")
+int BPF_PROG(invalid_dynptr_returned_slice, struct sk_buff *skb,
+ struct Qdisc *sch, struct bpf_sk_buff_ptr *to_free)
+{
+ struct ethhdr *hdr;
+
+ hdr = slice_in_subprog(skb);
+ if (!hdr) {
+ bpf_qdisc_skb_drop(skb, to_free);
+ return NET_XMIT_DROP;
+ }
+
+ bpf_qdisc_skb_drop(skb, to_free);
+
+ /* this should fail */
+ proto = hdr->h_proto;
+
+ return NET_XMIT_DROP;
+}
+
+SEC("struct_ops")
+__auxiliary
+struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
+{
+ return NULL;
+}
+
+SEC("struct_ops")
+__auxiliary
+int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
+ struct netlink_ext_ack *extack)
+{
+ return 0;
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
+{
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
+{
+}
+
+SEC(".struct_ops")
+struct Qdisc_ops test = {
+ .enqueue = (void *)invalid_dynptr_returned_slice,
+ .dequeue = (void *)bpf_qdisc_test_dequeue,
+ .init = (void *)bpf_qdisc_test_init,
+ .reset = (void *)bpf_qdisc_test_reset,
+ .destroy = (void *)bpf_qdisc_test_destroy,
+ .id = "bpf_qdisc_test",
+};
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index 1cd61d72c166f..92f9f9ea05321 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -1892,6 +1892,55 @@ int clone_invalidate4(void *ctx)
return 0;
}
+static __noinline void clone_slice_in_subprog(struct bpf_dynptr *ptr, int **data)
+{
+ struct bpf_dynptr clone;
+
+ bpf_dynptr_clone(ptr, &clone);
+ *data = bpf_dynptr_data(&clone, 0, sizeof(val));
+}
+
+/*
+ * A slice that escapes the clone's call frame remains valid while the
+ * shared ringbuf reservation is live.
+ */
+SEC("?raw_tp")
+__success
+int clone_slice_returned_frame_valid(void *ctx)
+{
+ struct bpf_dynptr ptr;
+ int *data = NULL;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
+ clone_slice_in_subprog(&ptr, &data);
+ if (data)
+ *data = 123;
+ bpf_ringbuf_submit_dynptr(&ptr, 0);
+
+ return 0;
+}
+
+/*
+ * Releasing the shared reservation must invalidate a slice that escaped
+ * from a clone's call frame.
+ */
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int clone_slice_returned_frame_invalid(void *ctx)
+{
+ struct bpf_dynptr ptr;
+ int *data = NULL;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
+ clone_slice_in_subprog(&ptr, &data);
+ bpf_ringbuf_submit_dynptr(&ptr, 0);
+ if (data)
+ /* this should fail */
+ *data = 123;
+
+ return 0;
+}
+
/* Invalidating a dynptr should invalidate any data slices
* of its parent
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Preserve escaped dynptr slice ancestry on subprog return
2026-09-10 4:40 ` [PATCH bpf v2 1/2] bpf: Preserve escaped dynptr slice ancestry on subprog return Xu Yunxiang
@ 2026-09-10 5:01 ` sashiko-bot
2026-09-10 8:07 ` Xu Yunxiang
2026-09-10 17:22 ` Amery Hung
1 sibling, 1 reply; 6+ messages in thread
From: sashiko-bot @ 2026-09-10 5:01 UTC (permalink / raw)
To: Xu Yunxiang; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [Critical] A subprogram can return or spill a memory slice (PTR_TO_MEM) derived from a local dynptr backed by its own stack frame, leading to a stack Use-After-Free in the caller.
- [Critical] Creating a local dynptr from a referenced dynptr slice via bpf_dynptr_from_mem strips lifetime tracking, allowing use-after-free.
--
commit 670ebab06aaf35b7713f2c4efe3dba56988a2162
Author: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
bpf: Preserve escaped dynptr slice ancestry on subprog return
Before freeing a callee, reparent surviving PTR_TO_MEM slices of every
dynptr with a parent to that parent. This preserves the lifetime link after
the local dynptr disappears for both reference-owning dynptrs and dynptrs
constructed from referenced objects.
Link: https://lore.kernel.org/r/20260829040712.B1B511F000E9@smtp.kernel.org
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 72a3f5998dd27..ee41b7386f752 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -800,6 +800,39 @@ static int dynptr_ref_cnt(struct bpf_verifier_env *env, int v_parent_id)
> return ref_cnt;
> }
>
> +static void reparent_dynptr_slices_on_func_exit(struct bpf_verifier_env *env,
> + struct bpf_func_state *callee)
> +{
> + struct bpf_verifier_state *vstate = env->cur_state;
> + struct bpf_func_state *state;
> + struct bpf_reg_state *reg;
> + int i;
> +
> + for (i = 0; i < callee->allocated_stack / BPF_REG_SIZE; i++) {
> + struct bpf_stack_state *slot = &callee->stack[i];
> + struct bpf_reg_state *dynptr = &slot->spilled_ptr;
> +
> + if (slot->slot_type[0] != STACK_DYNPTR ||
> + !dynptr->dynptr.first_slot ||
> + !dynptr->parent_id)
> + continue;
[Severity: Critical]
This is a pre-existing issue, but does this logic leave a gap for local
dynptrs backed by a callee's stack frame?
Because local dynptrs have parent_id = 0, they are skipped here. This seems
to allow a subprogram to return or spill a PTR_TO_MEM slice derived from a
local dynptr into the caller's state.
Since prepare_func_exit() only prevents returning PTR_TO_STACK in R0, could a
PTR_TO_MEM alias created via bpf_dynptr_data() escape and allow a stack
use-after-free in the caller?
[ ... ]
[Severity: Critical]
This is also a pre-existing issue, but does bpf_dynptr_from_mem() strip
lifetime tracking when used with referenced dynptr slices?
If a program reserves a ringbuf dynptr, gets a PTR_TO_MEM slice via
bpf_dynptr_data(), and then passes that slice to bpf_dynptr_from_mem():
kernel/bpf/helpers.c:bpf_dynptr_from_mem_proto() {
...
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
...
}
kernel/bpf/verifier.c:mark_stack_slots_dynptr() {
...
parent_id = ref_obj->id;
...
}
The new dynptr's parent_id is incorrectly assigned 0 because the slice's
parent_id is not propagated. Could the resulting local dynptr be accessed
after the original ringbuf reservation is discarded, leading to a
use-after-free of referenced kernel objects?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260910044006.2279547-1-xyx2021@mail.ustc.edu.cn?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Preserve escaped dynptr slice ancestry on subprog return
2026-09-10 5:01 ` sashiko-bot
@ 2026-09-10 8:07 ` Xu Yunxiang
0 siblings, 0 replies; 6+ messages in thread
From: Xu Yunxiang @ 2026-09-10 8:07 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
Thanks for checking this. Both scenarios are rejected by existing
bpf_dynptr_from_mem() validation before the new reparenting logic becomes
relevant.
Although the helper prototype uses ARG_PTR_TO_UNINIT_MEM, check_helper_call()
has a BPF_FUNC_dynptr_from_mem special case that accepts R1 only when its type
is exactly PTR_TO_MAP_VALUE:
if (regs[BPF_REG_1].type != PTR_TO_MAP_VALUE)
return -EACCES;
Consequently, a callee stack address is rejected as type "fp", so a LOCAL
dynptr cannot be backed by that callee's stack. A result from
bpf_dynptr_data() or bpf_dynptr_slice() is PTR_TO_MEM and is rejected as type
"mem", so it cannot be wrapped in another LOCAL dynptr either.
The existing verifier tests pin both restrictions:
- dynptr_from_mem_invalid_api rejects a stack pointer with
"Unsupported reg type fp for bpf_dynptr_from_mem data";
- dynptr_from_dynptr_data rejects a dynptr data pointer with
"Unsupported reg type mem for bpf_dynptr_from_mem data";
- dynptr_from_dynptr_slice rejects a dynptr slice with the same "mem"
diagnostic.
All three reported OK in the 134-subtest dynptr run cited in the cover.
Therefore parent_id == 0 LOCAL dynptrs that can be constructed here are backed
by permitted map-value memory, rather than a callee stack or a referenced
dynptr slice. No code change is needed for these two scenarios.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Preserve escaped dynptr slice ancestry on subprog return
2026-09-10 4:40 ` [PATCH bpf v2 1/2] bpf: Preserve escaped dynptr slice ancestry on subprog return Xu Yunxiang
2026-09-10 5:01 ` sashiko-bot
@ 2026-09-10 17:22 ` Amery Hung
1 sibling, 0 replies; 6+ messages in thread
From: Amery Hung @ 2026-09-10 17:22 UTC (permalink / raw)
To: Xu Yunxiang
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Sashiko
On Wed, Sep 9, 2026 at 9:40 PM Xu Yunxiang <xyx2021@mail.ustc.edu.cn> wrote:
>
> Dynptr slices use parent_id to link their lifetime to the dynptr that
> produced them. bpf_dynptr_clone() gives each clone a unique id while
> retaining the shared reference parent_id.
>
> Before the object relationship refactor, a slice also carried the shared
> reference id directly. The refactor instead made the slice a child of its
> source dynptr's unique id. If a callee derives a slice from a local clone
> and returns the slice to its caller, freeing the callee removes that clone
> id from the verifier state. A later release of the original dynptr can no
> longer reach the escaped slice, so the verifier keeps accepting accesses
> after the ring buffer reservation has been released.
>
> A similar disconnect affects dynptrs constructed from referenced objects.
> For example, a qdisc skb dynptr records the skb reference as its parent,
> but the skb dynptr type itself does not own a reference.
>
> Before freeing a callee, reparent surviving PTR_TO_MEM slices of every
> dynptr with a parent to that parent. This preserves the lifetime link after
> the local dynptr disappears for both reference-owning dynptrs and dynptrs
> constructed from referenced objects.
Summarizing the problem: On subprogram return, free_func_state()
discards stack-based objects without applying their normal destruction
semantics. Surviving children may retain parent_ids to removed
objects, breaking later cascading invalidation.
The correct fix is to apply dynptr destruction semantics before
exiting the callee: scan its stack slots and reuse the teardown logic,
destroy_if_dynptr_stack_slot(). This rejects losing the last
release-capable dynptr and invalidates slices derived from local
clones.
Reparenting the slice is incorrect because it allows the slice to
outlive the specific parent dynptr. Consequently, the proposed success
test should be a failure test.
>
> Fixes: 308c7a0ae885 ("bpf: Refactor object relationship tracking and fix dynptr UAF bug")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Closes: https://lore.kernel.org/r/20260829040712.B1B511F000E9@smtp.kernel.org
> Assisted-by: LLM
> Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
> ---
> kernel/bpf/verifier.c | 34 ++++++++++++++++++++++++++++++++++
> 1 file changed, 34 insertions(+)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 72a3f5998dd27..ee41b7386f752 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -800,6 +800,39 @@ static int dynptr_ref_cnt(struct bpf_verifier_env *env, int v_parent_id)
> return ref_cnt;
> }
>
> +static void reparent_dynptr_slices_on_func_exit(struct bpf_verifier_env *env,
> + struct bpf_func_state *callee)
> +{
> + struct bpf_verifier_state *vstate = env->cur_state;
> + struct bpf_func_state *state;
> + struct bpf_reg_state *reg;
> + int i;
> +
> + for (i = 0; i < callee->allocated_stack / BPF_REG_SIZE; i++) {
> + struct bpf_stack_state *slot = &callee->stack[i];
> + struct bpf_reg_state *dynptr = &slot->spilled_ptr;
> +
> + if (slot->slot_type[0] != STACK_DYNPTR ||
> + !dynptr->dynptr.first_slot ||
> + !dynptr->parent_id)
> + continue;
> +
> + /*
> + * A callee can spill a slice derived from its local dynptr into
> + * the caller's stack. The slice then outlives the dynptr id that
> + * links it to the rest of the object tree. Preserve that link by
> + * making escaped slices children of the dynptr's parent before
> + * the callee frame is freed.
> + */
> + bpf_for_each_reg_in_vstate(vstate, state, reg, ({
> + if (state == callee || reg->parent_id != dynptr->id ||
> + base_type(reg->type) != PTR_TO_MEM)
> + continue;
> + reg->parent_id = dynptr->parent_id;
> + }));
> + }
> +}
> +
> static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
> struct bpf_func_state *state, int spi)
> {
> @@ -10399,6 +10432,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
> print_verifier_state(env, state, caller->frameno, true);
> }
> account_processed_insns(env, callee, caller);
> + reparent_dynptr_slices_on_func_exit(env, callee);
> /* clear everything in the callee. In case of exceptional exits using
> * bpf_throw, this will be done by copy_verifier_state for extra frames. */
> free_func_state(callee);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-10 17:23 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-10 4:40 [PATCH bpf v2 0/2] bpf: Preserve escaped dynptr slice ancestry Xu Yunxiang
2026-09-10 4:40 ` [PATCH bpf v2 1/2] bpf: Preserve escaped dynptr slice ancestry on subprog return Xu Yunxiang
2026-09-10 5:01 ` sashiko-bot
2026-09-10 8:07 ` Xu Yunxiang
2026-09-10 17:22 ` Amery Hung
2026-09-10 4:40 ` [PATCH bpf v2 2/2] selftests/bpf: Test dynptr slices escaping a call frame Xu Yunxiang
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.