* [PATCH 6.6.y v3 0/3] bpf: fix dynptr release handling
@ 2026-08-31 0:15 Xu Yunxiang
2026-08-31 0:15 ` [PATCH 6.6.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release Xu Yunxiang
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Xu Yunxiang @ 2026-08-31 0:15 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong, sashal
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.6.y, which still represents the relationships with dynptr_id and
ref_obj_id.
This series supplies three stable-sized equivalents using the existing
6.6.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.
These patches are stable-specific implementations authored for the old
verifier, not cherry-picks of Amery Hung's upstream diff. Each commit
keeps 308c7a0ae885 as the upstream provenance for the equivalent fix
and documents how its implementation differs.
Changes in v3:
- make Xu Yunxiang the author of all three stable-specific
implementations;
- remove the authorship, sign-off, and ack trailers copied from
308c7a0ae885 because those developers did not write or review these
rewritten stable patches;
- remove the backporter notes and retain the upstream commit only as
provenance for the equivalent fixes;
- leave all code and tests byte-for-byte unchanged from v2.
v2: https://lore.kernel.org/r/20260829102618.105116-1-xyx2021@mail.ustc.edu.cn
Changes in v2:
- 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.
The code and tests are unchanged from v2. The following results were
obtained on that identical code tree with Linux 6.6.155 and
panic_on_warn=1:
- test_progs -t dynptr -v --workers=1: 2/92 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.
Xu Yunxiang (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: a4a971135a2ff64382ae4235b3ae60503bb1036a
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 6.6.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release
2026-08-31 0:15 [PATCH 6.6.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
@ 2026-08-31 0:15 ` Xu Yunxiang
2026-08-31 0:15 ` [PATCH 6.6.y v3 2/3] bpf: Propagate referenced dynptr release errors Xu Yunxiang
2026-08-31 0:15 ` [PATCH 6.6.y v3 3/3] bpf: Invalidate dynptr clones in every call frame on release Xu Yunxiang
2 siblings, 0 replies; 5+ messages in thread
From: Xu Yunxiang @ 2026-08-31 0:15 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong, sashal
[ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ]
This is a 6.6.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.6.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: 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/20260829033645.2365631-1-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 ad679bc42427..ccd5c86706b1 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -990,6 +990,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);
@@ -1000,6 +1003,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;
}
@@ -1013,8 +1017,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++) {
@@ -1029,8 +1039,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;
@@ -1047,6 +1059,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 66a60bfb5867..6ac09d6c34f2 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -315,6 +315,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] 5+ messages in thread
* [PATCH 6.6.y v3 2/3] bpf: Propagate referenced dynptr release errors
2026-08-31 0:15 [PATCH 6.6.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
2026-08-31 0:15 ` [PATCH 6.6.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release Xu Yunxiang
@ 2026-08-31 0:15 ` Xu Yunxiang
2026-08-31 0:37 ` sashiko-bot
2026-08-31 0:15 ` [PATCH 6.6.y v3 3/3] bpf: Invalidate dynptr clones in every call frame on release Xu Yunxiang
2 siblings, 1 reply; 5+ messages in thread
From: Xu Yunxiang @ 2026-08-31 0:15 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong, sashal
[ 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: 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 ccd5c86706b1..c9e3856bb59f 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -996,7 +996,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)
@@ -1023,7 +1023,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 6ac09d6c34f2..3d431e583c49 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -133,6 +133,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] 5+ messages in thread
* [PATCH 6.6.y v3 3/3] bpf: Invalidate dynptr clones in every call frame on release
2026-08-31 0:15 [PATCH 6.6.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
2026-08-31 0:15 ` [PATCH 6.6.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release Xu Yunxiang
2026-08-31 0:15 ` [PATCH 6.6.y v3 2/3] bpf: Propagate referenced dynptr release errors Xu Yunxiang
@ 2026-08-31 0:15 ` Xu Yunxiang
2 siblings, 0 replies; 5+ messages in thread
From: Xu Yunxiang @ 2026-08-31 0:15 UTC (permalink / raw)
To: stable
Cc: gregkh, bpf, ast, daniel, andrii, eddyz87, martin.lau, ameryhung,
joannelkoong, sashal
[ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ]
This is a 6.6.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.6 STACK_DYNPTR
slot loop instead of the parent_id DFS.
Fixes: 361f129f3cc1 ("bpf: Add bpf_dynptr_clone")
Reported-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
Closes: https://lore.kernel.org/r/20260829033701.2365648-1-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 c9e3856bb59f..a2df5bbf9ab8 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -996,7 +996,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)
@@ -1028,22 +1028,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 3d431e583c49..99a791ddfefc 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -1509,6 +1509,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 #3")
int clone_invalidate1(void *ctx)
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH 6.6.y v3 2/3] bpf: Propagate referenced dynptr release errors
2026-08-31 0:15 ` [PATCH 6.6.y v3 2/3] bpf: Propagate referenced dynptr release errors Xu Yunxiang
@ 2026-08-31 0:37 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-08-31 0:37 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 fixes a kernel panic caused by callbacks releasing caller-acquired dynptrs, but leaves identical WARN_ON_ONCE vulnerabilities for iterators and PTR_MAYBE_NULL registers.
--
commit fab416bf47d5e1f31c36e5cf7824ece03db8c1c5
Author: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
bpf: Propagate referenced dynptr release errors
This commit fixes a kernel panic vulnerability where a BPF callback releasing
a caller-acquired referenced dynptr triggers a verifier WARN_ON_ONCE. It
handles the failure from release_reference gracefully instead of warning.
Closes: https://lore.kernel.org/r/20260829040110.D6A991F000E9@smtp.kernel.org
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index ccd5c86706b18..c9e3856bb59fd 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -1023,7 +1023,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 patch fixes the kernel panic for
dynptrs, doesn't the exact same vulnerability exist for iterators and
PTR_MAYBE_NULL registers?
If a BPF program acquires an iterator (e.g., via bpf_iter_num_new) or a
refcounted PTR_MAYBE_NULL pointer (e.g., via bpf_sk_lookup_tcp) in a caller
frame, and passes it via the stack as context to a callback like bpf_loop,
the callback can attempt to destroy it.
Inside the callback, if the program calls a destroy kfunc on the iterator
or performs a NULL check on the restored PTR_MAYBE_NULL pointer, the
verifier attempts to release the reference state in the callee's frame.
Since the reference was acquired in the caller, it resides in the caller's
state->refs array, causing release_reference or release_reference_state to
return -EINVAL.
This triggers identical unpatched WARN_ON_ONCE macros in kernel/bpf/verifier.c:
- In unmark_stack_slots_iter:
WARN_ON_ONCE(release_reference(env, st->ref_obj_id));
- In mark_ptr_or_null_regs:
WARN_ON_ONCE(release_reference_state(state, id));
Should these locations also be updated to propagate the error instead of
warning to prevent userspace from crashing the kernel when panic_on_warn is
enabled?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831001524.3223134-1-xyx2021@mail.ustc.edu.cn?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-31 0:37 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31 0:15 [PATCH 6.6.y v3 0/3] bpf: fix dynptr release handling Xu Yunxiang
2026-08-31 0:15 ` [PATCH 6.6.y v3 1/3] bpf: Invalidate dynptr slices by dynptr_id on release Xu Yunxiang
2026-08-31 0:15 ` [PATCH 6.6.y v3 2/3] bpf: Propagate referenced dynptr release errors Xu Yunxiang
2026-08-31 0:37 ` sashiko-bot
2026-08-31 0:15 ` [PATCH 6.6.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