* [PATCH 6.12.y v2 0/2] bpf: fix dynptr lifetime invalidation on release
@ 2026-08-29 3:37 Xu Yunxiang
2026-08-29 3:37 ` [PATCH 6.12.y v2 1/2] bpf: Invalidate dynptr slices by dynptr_id " Xu Yunxiang
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Xu Yunxiang @ 2026-08-29 3:37 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong
Upstream commit 308c7a0ae885 ("bpf: Refactor object relationship
tracking and fix dynptr UAF bug") fixes these lifetime bugs as part of
an 11-file parent_id refactor. That refactor cannot be applied to
6.12.y, which still represents the relationships with dynptr_id and
ref_obj_id.
This series supplies two stable-sized equivalents using the existing
6.12.y representation. Patch 1 invalidates slice registers with the
released dynptr_id. Patch 2 scans every call frame when invalidating
cloned dynptr stack slots. They are split because the two bugs have
different introducing commits and independently testable effects.
Both patches preserve Amery's upstream authorship and complete trailer
chain. Each commit message explicitly documents the deviations from
308c7a0ae885 and the reason for the stable-only implementation.
Changes in v2:
- restore Amery as the author and retain the complete upstream trailer
chain;
- add bracketed notes documenting the stable-only implementation;
- remove the exact PTR_TO_MEM type filter so slices carrying
DYNPTR_TYPE_* flags are invalidated by dynptr_id;
- add the applicable dynptr slice/clone Fixes tags and identify the
reporter;
- add a cross-frame clone regression test and tighten the slice test.
Tested on top of Linux 6.12.107 (f717995cb7dc):
- built kernel/bpf/verifier.o and bzImage;
- built the updated dynptr_fail.bpf.o selftest object;
- booted the patched kernel under QEMU and reran fresh reproducers;
- the slice-after-submit case is rejected with
"R7 invalid mem access 'scalar'";
- the cross-frame control case loads and executes successfully, while
the double-submit and submit/discard cases are rejected with
"arg 1 is an unacquired reference".
Amery Hung (2):
bpf: Invalidate dynptr slices by dynptr_id on release
bpf: Invalidate dynptr clones in every call frame on release
kernel/bpf/verifier.c | 60 ++++++++++++++-----
.../testing/selftests/bpf/progs/dynptr_fail.c | 42 +++++++++++++
2 files changed, 88 insertions(+), 14 deletions(-)
base-commit: f717995cb7dcd8998ab15516b8006aea09cfde0d
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 6.12.y v2 1/2] bpf: Invalidate dynptr slices by dynptr_id on release
2026-08-29 3:37 [PATCH 6.12.y v2 0/2] bpf: fix dynptr lifetime invalidation on release Xu Yunxiang
@ 2026-08-29 3:37 ` Xu Yunxiang
2026-08-29 3:56 ` sashiko-bot
2026-08-29 3:37 ` [PATCH 6.12.y v2 2/2] bpf: Invalidate dynptr clones in every call frame " Xu Yunxiang
2026-08-29 10:26 ` [PATCH 6.12.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
2 siblings, 1 reply; 10+ messages in thread
From: Xu Yunxiang @ 2026-08-29 3:37 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong
From: Amery Hung <ameryhung@gmail.com>
[ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ]
This is a 6.12.y-sized equivalent, not a cherry-pick of the upstream
diff.
unmark_stack_slots_dynptr() comments say it invalidates slices of the
released dynptr, but it only calls release_reference(ref_obj_id).
bpf_dynptr_slice() and bpf_dynptr_slice_rdwr() stamp dynptr_id and
leave ref_obj_id at 0, so LDX/STX through those slices remains allowed
after bpf_ringbuf_submit_dynptr() / bpf_ringbuf_discard_dynptr().
bpf_dynptr_data() slices already carry ref_obj_id.
Walk registers with a matching dynptr_id, the same way
destroy_if_dynptr_stack_slot() already does when a dynptr stack slot is
overwritten. Do this for the released dynptr, for each clone, and for
the non-refcounted path.
The upstream commit is Amery Hung's parent_id refactor (Fixes:
870c28588afa, qdisc kfuncs). It cannot be applied here:
- It is an 11-file bpf-next change, well over the 100-line stable
limit, and it removes dynptr_id / ref_obj_id.
- 6.12.y still uses dynptr_id and does not have the qdisc kfuncs in
that Fixes: tag, so AUTOSEL never pulled it.
Deviations from 308c7a0ae8859: keep dynptr_id and ref_obj_id; do not
introduce parent_id; only add slice invalidation on
unmark_stack_slots_dynptr().
Fixes: 870c28588afa ("bpf: net_sched: Add basic bpf qdisc kfuncs")
Signed-off-by: Amery Hung <ameryhung@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
[ Xu Yunxiang: Split the upstream parent_id refactor into two
stable-sized equivalent fixes and rewrite the subject and changelog
to describe each one. This first patch keeps dynptr_id/ref_obj_id and
invalidates slices from unmark_stack_slots_dynptr(). Add a regression
test for a bpf_dynptr_slice_rdwr() pointer used after submit. ]
Fixes: 66e3a13e7c2c ("bpf: Add bpf_dynptr_slice and bpf_dynptr_slice_rdwr")
Reported-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
Assisted-by: Pi:GLM-5.3
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
kernel/bpf/verifier.c | 27 +++++++++++++++++--
.../testing/selftests/bpf/progs/dynptr_fail.c | 20 ++++++++++++++
2 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 99b3f539e431fe..5d0874a09a8db0 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -805,6 +805,9 @@ static void invalidate_dynptr(struct bpf_verifier_env *env, struct bpf_func_stat
state->stack[spi - 1].spilled_ptr.live |= REG_LIVE_WRITTEN;
}
+static void mark_reg_invalid(const struct bpf_verifier_env *env, struct bpf_reg_state *reg);
+static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr_id);
+
static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
struct bpf_func_state *state = func(env, reg);
@@ -815,6 +818,7 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
return spi;
if (!dynptr_type_refcounted(state->stack[spi].spilled_ptr.dynptr.type)) {
+ invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id);
invalidate_dynptr(env, state, spi);
return 0;
}
@@ -828,8 +832,14 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
* 2) Any slices derived from this dynptr.
*/
- /* Invalidate any slices associated with this dynptr */
+ /* Invalidate any slices associated with this dynptr.
+ * release_reference() only walks registers that carry ref_obj_id;
+ * slices are PTR_TO_MEM with dynptr_id set and ref_obj_id left 0.
+ * Mirror destroy_if_dynptr_stack_slot(), which already invalidates
+ * slices by dynptr_id.
+ */
WARN_ON_ONCE(release_reference(env, ref_obj_id));
+ invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id);
/* Invalidate any dynptr clones */
for (i = 1; i < state->allocated_stack / BPF_REG_SIZE; i++) {
@@ -844,8 +854,10 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
verbose(env, "verifier internal error: misconfigured ref_obj_id\n");
return -EFAULT;
}
- if (state->stack[i].spilled_ptr.dynptr.first_slot)
+ if (state->stack[i].spilled_ptr.dynptr.first_slot) {
+ invalidate_slices_of_dynptr(env, state->stack[i].spilled_ptr.id);
invalidate_dynptr(env, state, i);
+ }
}
return 0;
@@ -862,6 +874,17 @@ static void mark_reg_invalid(const struct bpf_verifier_env *env, struct bpf_reg_
__mark_reg_unknown(env, reg);
}
+static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr_id)
+{
+ struct bpf_func_state *fstate;
+ struct bpf_reg_state *dreg;
+
+ bpf_for_each_reg_in_vstate(env->cur_state, fstate, dreg, ({
+ if (dreg->dynptr_id == dynptr_id)
+ mark_reg_invalid(env, dreg);
+ }));
+}
+
static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
struct bpf_func_state *state, int spi)
{
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index dfd817d0348c47..a896701855a4b0 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -316,6 +316,26 @@ int data_slice_use_after_release1(void *ctx)
return 0;
}
+/* Releasing a dynptr must invalidate slices that only carry dynptr_id. */
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int slice_kfunc_use_after_submit(void *ctx)
+{
+ struct bpf_dynptr ptr;
+ struct sample *sample;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr);
+ sample = bpf_dynptr_slice_rdwr(&ptr, 0, NULL, sizeof(*sample));
+ if (!sample) {
+ bpf_ringbuf_discard_dynptr(&ptr, 0);
+ return 0;
+ }
+ bpf_ringbuf_submit_dynptr(&ptr, 0);
+ /* this should fail */
+ val = sample->pid;
+ return 0;
+}
+
/* A data slice can't be used after it has been released.
*
* This tests the case where the data slice tracks a dynptr (ptr2)
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 6.12.y v2 2/2] bpf: Invalidate dynptr clones in every call frame on release
2026-08-29 3:37 [PATCH 6.12.y v2 0/2] bpf: fix dynptr lifetime invalidation on release Xu Yunxiang
2026-08-29 3:37 ` [PATCH 6.12.y v2 1/2] bpf: Invalidate dynptr slices by dynptr_id " Xu Yunxiang
@ 2026-08-29 3:37 ` Xu Yunxiang
2026-08-29 4:01 ` sashiko-bot
2026-08-29 10:26 ` [PATCH 6.12.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
2 siblings, 1 reply; 10+ messages in thread
From: Xu Yunxiang @ 2026-08-29 3:37 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong
From: Amery Hung <ameryhung@gmail.com>
[ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ]
This is a 6.12.y-sized equivalent, not a cherry-pick of the upstream
diff.
unmark_stack_slots_dynptr() walked clones only in the submitting
pointer's frame. A program that clones a ringbuf dynptr in a subprog,
submits the clone, then submits the original in the caller is
accepted: the caller's STACK_DYNPTR slot is never cleared. The second
bpf_ringbuf_commit() flips BUSY on an already-committed header and a
userspace consumer stalls.
Walk every frame and invalidate matching STACK_DYNPTR slots. Leave
WARN_ON_ONCE(release_reference()) as a swallow: a hard -EINVAL would
reject legitimate programs that release once in a different frame
than they acquired.
Depends on "bpf: Invalidate dynptr slices by dynptr_id on release"
only for the slice helper call inside the new loop.
The upstream commit is Amery Hung's parent_id refactor. It cannot be
applied here (see the previous patch). Deviations from 308c7a0ae8859:
keep dynptr_id / ref_obj_id; walk frames with the 6.12 STACK_DYNPTR
slot loop instead of the parent_id DFS.
Fixes: 870c28588afa ("bpf: net_sched: Add basic bpf qdisc kfuncs")
Signed-off-by: Amery Hung <ameryhung@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
[ Xu Yunxiang: Split the upstream parent_id refactor into two
stable-sized equivalent fixes and rewrite the subject and changelog
to describe each one. This second patch keeps dynptr_id/ref_obj_id
and scans every call frame instead of using the upstream parent_id
DFS. Add a cross-frame clone regression test. ]
Fixes: 361f129f3cc1 ("bpf: Add bpf_dynptr_clone")
Reported-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
Assisted-by: Pi:GLM-5.3
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
kernel/bpf/verifier.c | 39 ++++++++++++-------
.../testing/selftests/bpf/progs/dynptr_fail.c | 22 +++++++++++
2 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 5d0874a09a8db0..9f314360551f8e 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -811,7 +811,7 @@ static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr
static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
struct bpf_func_state *state = func(env, reg);
- int spi, ref_obj_id, i;
+ int spi, ref_obj_id, i, frm;
spi = dynptr_get_spi(env, reg);
if (spi < 0)
@@ -841,22 +841,31 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
WARN_ON_ONCE(release_reference(env, ref_obj_id));
invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id);
- /* Invalidate any dynptr clones */
- for (i = 1; i < state->allocated_stack / BPF_REG_SIZE; i++) {
- if (state->stack[i].spilled_ptr.ref_obj_id != ref_obj_id)
+ /* Invalidate any dynptr clones, including those in other frames.
+ * Do not turn a failing release_reference() into -EINVAL: that
+ * rejects legitimate cross-frame single releases on 6.6/6.12/6.13.
+ */
+ for (frm = 0; frm <= env->cur_state->curframe; frm++) {
+ struct bpf_func_state *f = env->cur_state->frame[frm];
+
+ if (!f)
continue;
+ for (i = 1; i < f->allocated_stack / BPF_REG_SIZE; i++) {
+ if (f->stack[i].spilled_ptr.ref_obj_id != ref_obj_id)
+ continue;
- /* it should always be the case that if the ref obj id
- * matches then the stack slot also belongs to a
- * dynptr
- */
- if (state->stack[i].slot_type[0] != STACK_DYNPTR) {
- verbose(env, "verifier internal error: misconfigured ref_obj_id\n");
- return -EFAULT;
- }
- if (state->stack[i].spilled_ptr.dynptr.first_slot) {
- invalidate_slices_of_dynptr(env, state->stack[i].spilled_ptr.id);
- invalidate_dynptr(env, state, i);
+ /* it should always be the case that if the ref obj id
+ * matches then the stack slot also belongs to a
+ * dynptr
+ */
+ if (f->stack[i].slot_type[0] != STACK_DYNPTR) {
+ verbose(env, "verifier internal error: misconfigured ref_obj_id\n");
+ return -EFAULT;
+ }
+ if (f->stack[i].spilled_ptr.dynptr.first_slot) {
+ invalidate_slices_of_dynptr(env, f->stack[i].spilled_ptr.id);
+ invalidate_dynptr(env, f, i);
+ }
}
}
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index a896701855a4b0..1787289a5218f9 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -1512,6 +1512,28 @@ int clone_invalid2(struct xdp_md *xdp)
}
/* Invalidating a dynptr should invalidate its clones */
+static __noinline void submit_dynptr_clone(struct bpf_dynptr *ptr)
+{
+ struct bpf_dynptr clone;
+
+ bpf_dynptr_clone(ptr, &clone);
+ bpf_ringbuf_submit_dynptr(&clone, 0);
+}
+
+SEC("?raw_tp")
+__failure __msg("is an unacquired reference")
+int clone_invalidate_cross_frame(void *ctx)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(struct sample), 0, &ptr);
+ submit_dynptr_clone(&ptr);
+
+ /* The clone release in the callee must invalidate the caller's ptr. */
+ bpf_ringbuf_submit_dynptr(&ptr, 0);
+ return 0;
+}
+
SEC("?raw_tp")
__failure __msg("Expected an initialized dynptr as arg #2")
int clone_invalidate1(void *ctx)
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 6.12.y v2 1/2] bpf: Invalidate dynptr slices by dynptr_id on release
2026-08-29 3:37 ` [PATCH 6.12.y v2 1/2] bpf: Invalidate dynptr slices by dynptr_id " Xu Yunxiang
@ 2026-08-29 3:56 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-08-29 3:56 UTC (permalink / raw)
To: Xu Yunxiang; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The newly added `invalidate_slices_of_dynptr` function reads the `dynptr_id` union field without verifying the register's type, causing it to erroneously invalidate unrelated registers due to memory aliasing.
--
commit e9eb0688f5ef0c81c32aeb93616da3b5719b04da
Author: Amery Hung <ameryhung@gmail.com>
bpf: Invalidate dynptr slices by dynptr_id on release
This patch invalidates dynptr slices when their parent dynptr is released by
walking registers with a matching dynptr_id in unmark_stack_slots_dynptr().
This addresses an issue where LDX/STX through slices remains allowed after
submitting or discarding the dynptr.
Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 99b3f539e431f..5d0874a09a8db 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -862,6 +874,17 @@ static void mark_reg_invalid(const struct bpf_verifier_env *env, struct bpf_reg_
> __mark_reg_unknown(env, reg);
> }
>
> +static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr_id)
> +{
> + struct bpf_func_state *fstate;
> + struct bpf_reg_state *dreg;
> +
> + bpf_for_each_reg_in_vstate(env->cur_state, fstate, dreg, ({
> + if (dreg->dynptr_id == dynptr_id)
^^^^
[Severity: High]
Does this code risk misinterpreting union fields in unrelated registers
due to memory aliasing in invalidate_slices_of_dynptr()?
Because dynptr_id shares a union with other fields in struct bpf_reg_state
(such as btf_id on 32-bit architectures, or first_slot for dynptrs), if the
register is not explicitly checked for type PTR_TO_MEM, this comparison
might match unrelated registers.
For instance, a program that acquires a dynptr reference (assigning it
id_gen = 1) could use a register where the union happens to alias to 1
(like a CONST_PTR_TO_DYNPTR callback argument where first_slot = true, or a
PTR_TO_BTF_ID where the BTF ID is 1). Releasing the dynptr triggers
invalidate_slices_of_dynptr(env, 1), which unconditionally checks
dreg->dynptr_id == 1 and misinterprets the aliased union field as a match.
This could incorrectly invalidate valid registers, leading to spurious
verification failures.
Could we add a check for dreg->type before accessing dreg->dynptr_id to
prevent this?
> + mark_reg_invalid(env, dreg);
> + }));
> +}
> +
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260829033725.2365697-1-xyx2021@mail.ustc.edu.cn?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 6.12.y v2 2/2] bpf: Invalidate dynptr clones in every call frame on release
2026-08-29 3:37 ` [PATCH 6.12.y v2 2/2] bpf: Invalidate dynptr clones in every call frame " Xu Yunxiang
@ 2026-08-29 4:01 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-08-29 4:01 UTC (permalink / raw)
To: Xu Yunxiang; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] User-triggerable WARN_ON_ONCE in verifier via cross-frame dynptr release in callbacks, leading to local DoS.
--
commit 47e1f4599f52a8453ee0613fbb7c12af9d40d361
Author: Amery Hung <ameryhung@gmail.com>
bpf: Invalidate dynptr clones in every call frame on release
This patch modifies dynptr release handling to invalidate clones across all
call frames instead of only the submitting pointer's frame. It fixes an
issue where a caller's dynptr slot would not be cleared if a clone was
released in a subprogram.
Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 5d0874a09a8db..9f314360551f8 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -841,22 +841,31 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
> WARN_ON_ONCE(release_reference(env, ref_obj_id));
[Severity: High]
This is a pre-existing issue, but can an unprivileged user trigger this
warning intentionally by releasing a caller's reference from within a
callback?
If a program acquires a refcounted dynptr in a caller frame, passes a
pointer to it into a synchronous callback (such as via a bpf_loop context),
and calls a release helper like bpf_ringbuf_submit_dynptr() from inside
the callback, the initial helper validation succeeds since the dynptr is
valid on the caller's stack. This subsequently invokes
unmark_stack_slots_dynptr().
However, release_reference() calls release_reference_state(), which explicitly
checks if it is inside a callback trying to release a caller's reference.
Since callbacks cannot release caller references, it correctly fails and
returns -EINVAL.
This predictable -EINVAL return unconditionally triggers the WARN_ON_ONCE.
If panic_on_warn is enabled, could this allow a local user to intentionally
crash the kernel during program verification?
> invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id);
>
> - /* Invalidate any dynptr clones */
> - for (i = 1; i < state->allocated_stack / BPF_REG_SIZE; i++) {
> - if (state->stack[i].spilled_ptr.ref_obj_id != ref_obj_id)
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260829033725.2365697-1-xyx2021@mail.ustc.edu.cn?part=2
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 6.12.y v3 0/3] bpf: fix dynptr release handling
2026-08-29 3:37 [PATCH 6.12.y v2 0/2] bpf: fix dynptr lifetime invalidation on release Xu Yunxiang
2026-08-29 3:37 ` [PATCH 6.12.y v2 1/2] bpf: Invalidate dynptr slices by dynptr_id " Xu Yunxiang
2026-08-29 3:37 ` [PATCH 6.12.y v2 2/2] bpf: Invalidate dynptr clones in every call frame " Xu Yunxiang
@ 2026-08-29 10:26 ` Xu Yunxiang
2026-08-29 10:26 ` [PATCH 6.12.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release Xu Yunxiang
` (2 more replies)
2 siblings, 3 replies; 10+ messages in thread
From: Xu Yunxiang @ 2026-08-29 10:26 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong
Upstream commit 308c7a0ae885 ("bpf: Refactor object relationship
tracking and fix dynptr UAF bug") fixes these lifetime bugs as part of
an 11-file parent_id refactor. That refactor cannot be applied to
6.12.y, which still represents the relationships with dynptr_id and
ref_obj_id.
This series supplies three stable-sized equivalents using the existing
6.12.y representation. Patch 1 invalidates slice registers with the
released dynptr_id. Patch 2 propagates a referenced-dynptr release
error instead of turning a callback rejection into a kernel warning.
Patch 3 scans every active call frame when invalidating cloned dynptr
stack slots. They are split because the bugs have different
introducing commits and independently testable effects.
All three patches preserve Amery's upstream authorship and complete
trailer chain. Each commit message explicitly documents the deviations
from 308c7a0ae885 and the reason for the stable-only implementation.
Changes in v3:
- check that a register has base type PTR_TO_MEM before reading its
dynptr_id union member, while retaining DYNPTR_TYPE_* flags;
- add a bpf_dynptr_slice() use-after-submit regression that reaches the
dynptr_id-only slice path on the target stable verifier;
- add a separate patch that propagates release_reference() errors from
referenced dynptr release and a callback regression test. This avoids
the verifier WARN, and the panic it causes with panic_on_warn=1, when a
callback tries to release its caller's dynptr;
- make the cross-frame clone regression use bpf_dynptr_data() after the
callee releases a clone, so it uniquely tests caller-slot invalidation
instead of a repeated release already rejected by patch 2;
- check STACK_DYNPTR before reading dynptr metadata while scanning call
frames, so a partially overwritten ordinary spill with stale metadata
is ignored, and add a success regression test;
- investigate the separately reported slice escape through a returned
subprogram frame. A minimal reproducer is also accepted by current
mainline after 308c7a0ae885, so that issue is not folded into this
stable-only series and needs a separate mainline fix.
Changes in v2:
- restore Amery as the author and retain the complete upstream trailer
chain;
- add bracketed notes documenting the stable-only implementation;
- remove the exact PTR_TO_MEM type filter so slices carrying
DYNPTR_TYPE_* flags are invalidated by dynptr_id;
- add the applicable dynptr slice/clone Fixes tags and identify the
reporter;
- add a cross-frame clone regression test and tighten the slice test.
Tested on Linux 6.12.107 with panic_on_warn=1:
- test_progs -t dynptr -v --workers=1: 2/96 passed, 0 failed;
- the slice-after-submit and cross-frame stale-use programs were
rejected, while their control programs loaded successfully;
- the callback-release reproducer was rejected without a verifier
warning;
- the independent submit/discard, stale-spill, and cross-frame
reproducers produced the expected results.
Amery Hung (3):
bpf: Invalidate dynptr slices by dynptr_id on release
bpf: Propagate referenced dynptr release errors
bpf: Invalidate dynptr clones in every call frame on release
kernel/bpf/verifier.c | 55 +++++++++----
.../testing/selftests/bpf/progs/dynptr_fail.c | 79 +++++++++++++++++++
2 files changed, 119 insertions(+), 15 deletions(-)
base-commit: f717995cb7dcd8998ab15516b8006aea09cfde0d
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 6.12.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release
2026-08-29 10:26 ` [PATCH 6.12.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
@ 2026-08-29 10:26 ` Xu Yunxiang
2026-08-29 10:26 ` [PATCH 6.12.y v3 2/3] bpf: Propagate referenced dynptr release errors Xu Yunxiang
2026-08-29 10:26 ` [PATCH 6.12.y v3 3/3] bpf: Invalidate dynptr clones in every call frame on release Xu Yunxiang
2 siblings, 0 replies; 10+ messages in thread
From: Xu Yunxiang @ 2026-08-29 10:26 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong
From: Amery Hung <ameryhung@gmail.com>
[ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ]
This is a 6.12.y-sized equivalent, not a cherry-pick of the upstream
diff.
unmark_stack_slots_dynptr() comments say it invalidates slices of the
released dynptr, but it only calls release_reference(ref_obj_id).
bpf_dynptr_slice() and bpf_dynptr_slice_rdwr() stamp dynptr_id and
leave ref_obj_id at 0, so LDX/STX through those slices remains allowed
after bpf_ringbuf_submit_dynptr() / bpf_ringbuf_discard_dynptr().
bpf_dynptr_data() slices already carry ref_obj_id.
Walk registers with a matching dynptr_id, the same way
destroy_if_dynptr_stack_slot() already does when a dynptr stack slot is
overwritten. Do this for the released dynptr, for each clone, and for
the non-refcounted path.
Check the register base type before reading dynptr_id because the field
shares a union with other register metadata. Use base_type() rather than
an exact type match so slices carrying DYNPTR_TYPE_* flags remain covered.
The upstream commit is Amery Hung's parent_id refactor (Fixes:
870c28588afa, qdisc kfuncs). It cannot be applied here:
- It is an 11-file bpf-next change, well over the 100-line stable
limit, and it removes dynptr_id / ref_obj_id.
- 6.12.y still uses dynptr_id and does not have the qdisc kfuncs in
that Fixes: tag, so AUTOSEL never pulled it.
Deviations from 308c7a0ae8859: keep dynptr_id and ref_obj_id; do not
introduce parent_id; only add slice invalidation on
unmark_stack_slots_dynptr().
Fixes: 870c28588afa ("bpf: net_sched: Add basic bpf qdisc kfuncs")
Signed-off-by: Amery Hung <ameryhung@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
[ Xu Yunxiang: Backport the slice-invalidation part of the upstream
parent_id refactor and rewrite the subject and changelog.
This patch keeps dynptr_id/ref_obj_id and
invalidates slices from unmark_stack_slots_dynptr(). Add a regression
test for a bpf_dynptr_slice() pointer used after submit. ]
Fixes: 66e3a13e7c2c ("bpf: Add bpf_dynptr_slice and bpf_dynptr_slice_rdwr")
Reported-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
Closes: https://lore.kernel.org/r/20260829033725.2365697-2-xyx2021@mail.ustc.edu.cn
Assisted-by: Pi:GLM-5.3
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
kernel/bpf/verifier.c | 27 +++++++++++++++++--
.../testing/selftests/bpf/progs/dynptr_fail.c | 20 ++++++++++++++
2 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 99b3f539e431fe..dbc9cfcffcd60d 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -805,6 +805,9 @@ static void invalidate_dynptr(struct bpf_verifier_env *env, struct bpf_func_stat
state->stack[spi - 1].spilled_ptr.live |= REG_LIVE_WRITTEN;
}
+static void mark_reg_invalid(const struct bpf_verifier_env *env, struct bpf_reg_state *reg);
+static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr_id);
+
static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
struct bpf_func_state *state = func(env, reg);
@@ -815,6 +818,7 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
return spi;
if (!dynptr_type_refcounted(state->stack[spi].spilled_ptr.dynptr.type)) {
+ invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id);
invalidate_dynptr(env, state, spi);
return 0;
}
@@ -828,8 +832,14 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
* 2) Any slices derived from this dynptr.
*/
- /* Invalidate any slices associated with this dynptr */
+ /* Invalidate any slices associated with this dynptr.
+ * release_reference() only walks registers that carry ref_obj_id;
+ * slices are PTR_TO_MEM with dynptr_id set and ref_obj_id left 0.
+ * Mirror destroy_if_dynptr_stack_slot(), which already invalidates
+ * slices by dynptr_id.
+ */
WARN_ON_ONCE(release_reference(env, ref_obj_id));
+ invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id);
/* Invalidate any dynptr clones */
for (i = 1; i < state->allocated_stack / BPF_REG_SIZE; i++) {
@@ -844,8 +854,10 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
verbose(env, "verifier internal error: misconfigured ref_obj_id\n");
return -EFAULT;
}
- if (state->stack[i].spilled_ptr.dynptr.first_slot)
+ if (state->stack[i].spilled_ptr.dynptr.first_slot) {
+ invalidate_slices_of_dynptr(env, state->stack[i].spilled_ptr.id);
invalidate_dynptr(env, state, i);
+ }
}
return 0;
@@ -862,6 +874,17 @@ static void mark_reg_invalid(const struct bpf_verifier_env *env, struct bpf_reg_
__mark_reg_unknown(env, reg);
}
+static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr_id)
+{
+ struct bpf_func_state *fstate;
+ struct bpf_reg_state *dreg;
+
+ bpf_for_each_reg_in_vstate(env->cur_state, fstate, dreg, ({
+ if (base_type(dreg->type) == PTR_TO_MEM && dreg->dynptr_id == dynptr_id)
+ mark_reg_invalid(env, dreg);
+ }));
+}
+
static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
struct bpf_func_state *state, int spi)
{
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index dfd817d0348c47..61bd3f7c946f63 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -316,6 +316,26 @@ int data_slice_use_after_release1(void *ctx)
return 0;
}
+/* Releasing a dynptr must invalidate slices that only carry dynptr_id. */
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int slice_kfunc_use_after_submit(void *ctx)
+{
+ struct bpf_dynptr ptr;
+ struct sample *sample;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr);
+ sample = bpf_dynptr_slice(&ptr, 0, NULL, sizeof(*sample));
+ if (!sample) {
+ bpf_ringbuf_discard_dynptr(&ptr, 0);
+ return 0;
+ }
+ bpf_ringbuf_submit_dynptr(&ptr, 0);
+ /* this should fail */
+ val = sample->pid;
+ return 0;
+}
+
/* A data slice can't be used after it has been released.
*
* This tests the case where the data slice tracks a dynptr (ptr2)
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 6.12.y v3 2/3] bpf: Propagate referenced dynptr release errors
2026-08-29 10:26 ` [PATCH 6.12.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
2026-08-29 10:26 ` [PATCH 6.12.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release Xu Yunxiang
@ 2026-08-29 10:26 ` Xu Yunxiang
2026-08-29 10:48 ` sashiko-bot
2026-08-29 10:26 ` [PATCH 6.12.y v3 3/3] bpf: Invalidate dynptr clones in every call frame on release Xu Yunxiang
2 siblings, 1 reply; 10+ messages in thread
From: Xu Yunxiang @ 2026-08-29 10:26 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong
From: Amery Hung <ameryhung@gmail.com>
[ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ]
This is a stable-sized equivalent, not a cherry-pick of the upstream
diff.
release_reference_state() rejects an attempt from a BPF callback to
release a reference acquired by its caller. For referenced dynptrs,
unmark_stack_slots_dynptr() instead wraps release_reference() in
WARN_ON_ONCE() and then returns success.
Consequently, a callback that submits or discards its caller's ringbuf
dynptr can turn this normal verifier rejection into a kernel warning. A
kernel configured with panic_on_warn=1 panics while verifying the program.
Propagate the release_reference() error to check_helper_call(), which
already reports the invalid release and rejects the program. Add a
regression test using a non-constant bpf_loop() iteration count so the
callback is verified in a separate frame.
The upstream parent_id refactor returns release_reference() directly from
unmark_stack_slots_dynptr(). Extract that behavior here while retaining
the stable kernels' ref_obj_id representation.
Fixes: 870c28588afa ("bpf: net_sched: Add basic bpf qdisc kfuncs")
Signed-off-by: Amery Hung <ameryhung@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
[ Xu Yunxiang: Extract only the referenced-dynptr error propagation from
the upstream object relationship refactor for the old stable verifier.
Add a callback regression test. ]
Fixes: 270605317366 ("bpf: Rework process_dynptr_func")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/r/20260829040110.D6A991F000E9@smtp.kernel.org
Assisted-by: Codex:GPT-5
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
kernel/bpf/verifier.c | 6 +++--
.../testing/selftests/bpf/progs/dynptr_fail.c | 22 +++++++++++++++++++
2 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index dbc9cfcffcd60d..65ee9afaa5ff07 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -811,7 +811,7 @@ static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr
static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
struct bpf_func_state *state = func(env, reg);
- int spi, ref_obj_id, i;
+ int spi, ref_obj_id, i, err;
spi = dynptr_get_spi(env, reg);
if (spi < 0)
@@ -838,7 +838,9 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
* Mirror destroy_if_dynptr_stack_slot(), which already invalidates
* slices by dynptr_id.
*/
- WARN_ON_ONCE(release_reference(env, ref_obj_id));
+ err = release_reference(env, ref_obj_id);
+ if (err)
+ return err;
invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id);
/* Invalidate any dynptr clones */
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index 61bd3f7c946f63..52bd7f8871c579 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -134,6 +134,28 @@ int ringbuf_missing_release_callback(void *ctx)
return 0;
}
+static int release_caller_dynptr_callback_fn(__u32 index, void *data)
+{
+ struct bpf_dynptr *ptr = data;
+
+ bpf_ringbuf_submit_dynptr(ptr, 0);
+ return 0;
+}
+
+/* A callback cannot release a dynptr reference acquired by its caller. */
+SEC("?raw_tp")
+__failure __msg("reference has not been acquired before")
+int ringbuf_release_caller_dynptr_callback(void *ctx)
+{
+ struct bpf_dynptr ptr;
+
+ if (bpf_ringbuf_reserve_dynptr(&ringbuf, 8, 0, &ptr))
+ return 0;
+ bpf_loop(val & 1, release_caller_dynptr_callback_fn, &ptr, 0);
+ bpf_ringbuf_discard_dynptr(&ptr, 0);
+ return 0;
+}
+
/* Can't call bpf_ringbuf_submit/discard_dynptr on a non-initialized dynptr */
SEC("?raw_tp")
__failure __msg("arg 1 is an unacquired reference")
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 6.12.y v3 3/3] bpf: Invalidate dynptr clones in every call frame on release
2026-08-29 10:26 ` [PATCH 6.12.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
2026-08-29 10:26 ` [PATCH 6.12.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release Xu Yunxiang
2026-08-29 10:26 ` [PATCH 6.12.y v3 2/3] bpf: Propagate referenced dynptr release errors Xu Yunxiang
@ 2026-08-29 10:26 ` Xu Yunxiang
2 siblings, 0 replies; 10+ messages in thread
From: Xu Yunxiang @ 2026-08-29 10:26 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong
From: Amery Hung <ameryhung@gmail.com>
[ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ]
This is a 6.12.y-sized equivalent, not a cherry-pick of the upstream
diff.
unmark_stack_slots_dynptr() walked clones only in the submitting
pointer's frame. A program that clones a ringbuf dynptr in a subprog
and submits the clone leaves the original STACK_DYNPTR slot active in
the caller. The caller can then pass that stale original to
bpf_dynptr_data(), even though the shared ringbuf reference has already
been released.
Walk every frame and invalidate matching STACK_DYNPTR slots. Check the
slot type before reading dynptr metadata: a partial stack overwrite can
turn a pointer spill into ordinary stack data while stale union fields
remain, and those fields must not be interpreted as a dynptr ref_obj_id.
The preceding patch makes a repeated release an ordinary verifier
rejection, but it does not prevent non-release uses of a stale dynptr.
Depends on "bpf: Invalidate dynptr slices by dynptr_id on release"
only for the slice helper call inside the new loop.
The upstream commit is Amery Hung's parent_id refactor. It cannot be
applied here (see the first patch). Deviations from 308c7a0ae8859:
keep dynptr_id / ref_obj_id; walk frames with the 6.12 STACK_DYNPTR
slot loop instead of the parent_id DFS.
Fixes: 870c28588afa ("bpf: net_sched: Add basic bpf qdisc kfuncs")
Signed-off-by: Amery Hung <ameryhung@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
[ Xu Yunxiang: Backport the cross-frame clone-invalidation part of the
upstream parent_id refactor and rewrite the subject and changelog.
This patch keeps dynptr_id/ref_obj_id
and scans every call frame instead of using the upstream parent_id
DFS. Add cross-frame stale-use and stale-spill regression tests. ]
Fixes: 361f129f3cc1 ("bpf: Add bpf_dynptr_clone")
Reported-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
Closes: https://lore.kernel.org/r/20260829033725.2365697-3-xyx2021@mail.ustc.edu.cn
Assisted-by: Pi:GLM-5.3
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
kernel/bpf/verifier.c | 30 +++++++--------
.../testing/selftests/bpf/progs/dynptr_fail.c | 37 +++++++++++++++++++
2 files changed, 52 insertions(+), 15 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 65ee9afaa5ff07..572437d48b7df2 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -811,7 +811,7 @@ static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr
static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
struct bpf_func_state *state = func(env, reg);
- int spi, ref_obj_id, i, err;
+ int spi, ref_obj_id, i, err, frm;
spi = dynptr_get_spi(env, reg);
if (spi < 0)
@@ -843,22 +843,22 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
return err;
invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id);
- /* Invalidate any dynptr clones */
- for (i = 1; i < state->allocated_stack / BPF_REG_SIZE; i++) {
- if (state->stack[i].spilled_ptr.ref_obj_id != ref_obj_id)
+ /* Invalidate any dynptr clones, including those in other frames. */
+ for (frm = 0; frm <= env->cur_state->curframe; frm++) {
+ struct bpf_func_state *f = env->cur_state->frame[frm];
+
+ if (!f)
continue;
+ for (i = 1; i < f->allocated_stack / BPF_REG_SIZE; i++) {
+ if (f->stack[i].slot_type[0] != STACK_DYNPTR)
+ continue;
+ if (f->stack[i].spilled_ptr.ref_obj_id != ref_obj_id)
+ continue;
- /* it should always be the case that if the ref obj id
- * matches then the stack slot also belongs to a
- * dynptr
- */
- if (state->stack[i].slot_type[0] != STACK_DYNPTR) {
- verbose(env, "verifier internal error: misconfigured ref_obj_id\n");
- return -EFAULT;
- }
- if (state->stack[i].spilled_ptr.dynptr.first_slot) {
- invalidate_slices_of_dynptr(env, state->stack[i].spilled_ptr.id);
- invalidate_dynptr(env, state, i);
+ if (f->stack[i].spilled_ptr.dynptr.first_slot) {
+ invalidate_slices_of_dynptr(env, f->stack[i].spilled_ptr.id);
+ invalidate_dynptr(env, f, i);
+ }
}
}
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index 52bd7f8871c579..02defdb63ddd1c 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -1534,6 +1534,43 @@ int clone_invalid2(struct xdp_md *xdp)
}
/* Invalidating a dynptr should invalidate its clones */
+static __noinline void submit_dynptr_clone(struct bpf_dynptr *ptr)
+{
+ struct bpf_dynptr clone;
+
+ bpf_dynptr_clone(ptr, &clone);
+ bpf_ringbuf_submit_dynptr(&clone, 0);
+}
+
+SEC("?raw_tp")
+__failure __msg("Expected an initialized dynptr")
+int clone_invalidate_cross_frame(void *ctx)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(struct sample), 0, &ptr);
+ submit_dynptr_clone(&ptr);
+ bpf_dynptr_data(&ptr, 0, sizeof(struct sample));
+ return 0;
+}
+
+SEC("?raw_tp")
+__success
+int clone_invalidate_cross_frame_stale_spill(void *ctx)
+{
+ struct bpf_dynptr ptr;
+ void *data;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(struct sample), 0, &ptr);
+ data = bpf_dynptr_data(&ptr, 0, sizeof(struct sample));
+ asm volatile("*(u64 *)(r10 - 24) = %[data];"
+ "*(u32 *)(r10 - 20) = 0x12345678;"
+ :: [data] "r"(data) : "memory");
+ /* A dead, scrubbed spill must not be mistaken for a dynptr slot. */
+ submit_dynptr_clone(&ptr);
+ return 0;
+}
+
SEC("?raw_tp")
__failure __msg("Expected an initialized dynptr as arg #2")
int clone_invalidate1(void *ctx)
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 6.12.y v3 2/3] bpf: Propagate referenced dynptr release errors
2026-08-29 10:26 ` [PATCH 6.12.y v3 2/3] bpf: Propagate referenced dynptr release errors Xu Yunxiang
@ 2026-08-29 10:48 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-08-29 10:48 UTC (permalink / raw)
To: Xu Yunxiang; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The patch successfully fixes a kernel panic by propagating the error from `release_reference()` for dynptrs, but leaves identical vulnerabilities in `unmark_stack_slots_iter()` and `mark_ptr_or_null_regs()`, where `WARN_ON_ONCE` is still used and will trigger panics when callbacks interact with caller references.
--
commit aa0864ea661b640ba7fd188bf0b1fcebb309d1b3
Author: Amery Hung <ameryhung@gmail.com>
bpf: Propagate referenced dynptr release errors
This commit propagates the error from release_reference() for dynptrs to
avoid a kernel panic when a BPF callback attempts to release a caller's
reference. It extracts the upstream behavior for the old stable verifier.
Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index dbc9cfcffcd60..65ee9afaa5ff0 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -838,7 +838,9 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
> * Mirror destroy_if_dynptr_stack_slot(), which already invalidates
> * slices by dynptr_id.
> */
> - WARN_ON_ONCE(release_reference(env, ref_obj_id));
> + err = release_reference(env, ref_obj_id);
> + if (err)
> + return err;
[Severity: High]
This is a pre-existing issue, but while this successfully fixes the
kernel panic for dynptrs, does it leave identical issues in
unmark_stack_slots_iter() and mark_ptr_or_null_regs()?
For iterators, if a BPF program acquires an iterator (e.g., bpf_iter_num_new) on
the caller's stack, passes it as the ctx argument to bpf_loop(), and calls
bpf_iter_num_destroy() on it from within the callback, unmark_stack_slots_iter()
will still trigger a panic because of WARN_ON_ONCE:
kernel/bpf/verifier.c:unmark_stack_slots_iter() {
...
for (i = 0; i < nr_slots; i++) {
...
if (i == 0)
WARN_ON_ONCE(release_reference(env, st->ref_obj_id));
...
}
Similarly, if a BPF program acquires a reference that can be NULL (e.g.,
bpf_sk_lookup_tcp()), spills it to the caller's stack, passes it as the ctx
argument to bpf_loop(), and performs a NULL check on it inside the callback,
mark_ptr_or_null_regs() also triggers a panic:
kernel/bpf/verifier.c:mark_ptr_or_null_regs() {
...
if (ref_obj_id && ref_obj_id == id && is_null)
/* regs[regno] is in the " == NULL" branch.
* No one could have freed the reference state before
* doing the NULL check.
*/
WARN_ON_ONCE(release_reference_state(state, id));
...
}
Should these two locations also have their errors correctly propagated instead
of triggering a warning?
> invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id);
>
> /* Invalidate any dynptr clones */
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260829102638.105170-1-xyx2021@mail.ustc.edu.cn?part=2
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-08-29 10:48 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-29 3:37 [PATCH 6.12.y v2 0/2] bpf: fix dynptr lifetime invalidation on release Xu Yunxiang
2026-08-29 3:37 ` [PATCH 6.12.y v2 1/2] bpf: Invalidate dynptr slices by dynptr_id " Xu Yunxiang
2026-08-29 3:56 ` sashiko-bot
2026-08-29 3:37 ` [PATCH 6.12.y v2 2/2] bpf: Invalidate dynptr clones in every call frame " Xu Yunxiang
2026-08-29 4:01 ` sashiko-bot
2026-08-29 10:26 ` [PATCH 6.12.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
2026-08-29 10:26 ` [PATCH 6.12.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release Xu Yunxiang
2026-08-29 10:26 ` [PATCH 6.12.y v3 2/3] bpf: Propagate referenced dynptr release errors Xu Yunxiang
2026-08-29 10:48 ` sashiko-bot
2026-08-29 10:26 ` [PATCH 6.12.y v3 3/3] bpf: Invalidate dynptr clones in every call frame on release Xu Yunxiang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox