BPF List
 help / color / mirror / Atom feed
* [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