All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf v4 00/11] Misc bug fixes - part 5
@ 2026-09-14 22:24 Kumar Kartikeya Dwivedi
  2026-09-14 22:24 ` [PATCH bpf v4 01/11] bpf: Make post-verification instruction rewrites killable Kumar Kartikeya Dwivedi
                   ` (10 more replies)
  0 siblings, 11 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:24 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, Nicholas Carlini, kkd,
	kernel-team

A set of miscellaneous fixes for bugs reported by Nicholas. See commit
logs for details.

Changelog:
----------
v3 -> v4
v3: https://lore.kernel.org/bpf/20260914131701.2529725-1-memxor@gmail.com

 * Return interrupted main-program JIT compilation through ERR_PTR()
   instead of an output parameter. (Eduard)
 * Apply in-kernel CO-RE relocations before subprogram discovery and
   validation, while keeping func_info and line_info validation after
   layout discovery. (Andrii, Alexei)
 * Keep relocation-target hardening as a separate patch and diagnose
   invalid register-source ALU targets. (Alexei, Eduard, BPF CI)
 * Extract CO-RE poisoning into a returning helper so validated
   instruction cases can propagate its status directly. (Andrii)
 * Restore the existing inner-map UID comment wording. (Eduard)
 * Add bounds checking and selftests for truncated ldimm64 CO-RE
   relocations. (Sashiko)

v2 -> v3
v2: https://lore.kernel.org/bpf/20260905083418.3723623-1-memxor@gmail.com

 * Propagate cancellation from constant blinding through both JIT fallback
   paths instead of rechecking fatal signals in bpf_check(). (Eduard)
 * Preserve packet-pointer displacement by comparing range bases, without
   extending the generic ID map. Veristat showed identical verdicts and
   successful-program instruction/state counts across 2773 loads. (Eduard,
   Alexei)
 * Reduce the packet pruning regression to 20 instructions and force state
   checkpoints. (Alexei, BPF CI)
 * Reject unsupported CO-RE poisoning targets in the shared relocation
   code instead of adding a CFG fall-through check. (Alexei)
 * Cover unsupported poison targets and supported relocations in dead code,
   including both halves of ldimm64.
 * Assign callback value IDs unconditionally and compare inner-map lookup
   IDs through check_ids(); explain the bug with a small program. (Eduard)
 * Move map_uid beside the other IDs and shrink frameno to preserve the
   register state size, keeping the existing memcmp() ranges.
 * Consolidate callback tests into the existing spinlock tests and reuse
   their map fixtures. Retain one-element and nested locking controls, and
   check nonzero IDs in timer, workqueue, and task-work callbacks. Clarify
   the inner-map lookup test description. (BPF CI)

v1 -> v2
v1: https://lore.kernel.org/bpf/20260905070003.3193366-1-memxor@gmail.com

 * Address inner map corner case for callback map value patch.
 * Drop patch 2 since the test can be flaky.

Kumar Kartikeya Dwivedi (11):
  bpf: Make post-verification instruction rewrites killable
  bpf: Preserve packet pointer class displacement in regsafe()
  selftests/bpf: Test packet pointer class displacement pruning
  bpf: Apply CO-RE relocations before subprogram validation
  selftests/bpf: Test early in-kernel CO-RE relocation
  bpf: Restrict CO-RE poisoning to relocatable instructions
  selftests/bpf: Test CO-RE instruction poisoning restrictions
  bpf: Assign lock identity to callback map values
  selftests/bpf: Check callback map value lock identity
  libbpf: Reject truncated ldimm64 CO-RE relocations
  selftests/bpf: Add truncated CO-RE ldimm64 test

 include/linux/bpf.h                           |   2 +-
 include/linux/bpf_verifier.h                  |  26 +--
 kernel/bpf/btf.c                              |   6 +-
 kernel/bpf/check_btf.c                        |  14 +-
 kernel/bpf/core.c                             |  21 +-
 kernel/bpf/fixups.c                           |  24 ++-
 kernel/bpf/states.c                           |   9 +-
 kernel/bpf/verifier.c                         |  20 +-
 tools/lib/bpf/libbpf.c                        |   3 +-
 tools/lib/bpf/relo_core.c                     |  69 ++++---
 tools/lib/bpf/relo_core.h                     |   5 +-
 .../selftests/bpf/prog_tests/cb_refs.c        |   2 +-
 .../selftests/bpf/prog_tests/core_reloc_raw.c | 192 ++++++++++++++++++
 .../prog_tests/core_reloc_truncated_ldimm64.c |  82 ++++++++
 .../selftests/bpf/prog_tests/spin_lock.c      |  33 ++-
 .../bpf/progs/core_reloc_truncated_ldimm64.c  |  27 +++
 .../selftests/bpf/progs/test_spin_lock_fail.c | 167 ++++++++++++++-
 .../progs/verifier_xdp_direct_packet_access.c |  35 ++++
 18 files changed, 657 insertions(+), 80 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/core_reloc_truncated_ldimm64.c
 create mode 100644 tools/testing/selftests/bpf/progs/core_reloc_truncated_ldimm64.c


base-commit: ee363e055895364039bf28348ff13c615afad4ba
-- 
2.53.0


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 01/11] bpf: Make post-verification instruction rewrites killable
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
@ 2026-09-14 22:24 ` Kumar Kartikeya Dwivedi
  2026-09-14 22:24 ` [PATCH bpf v4 02/11] bpf: Preserve packet pointer class displacement in regsafe() Kumar Kartikeya Dwivedi
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:24 UTC (permalink / raw)
  To: bpf
  Cc: Nicholas Carlini, Eduard Zingerman, Alexei Starovoitov,
	Andrii Nakryiko, Daniel Borkmann, Emil Tsalapatis, kkd,
	kernel-team

After do_check() returns, the verifier runs several instruction rewrite
passes. Some of them patch or remove one instruction at a time. Each
operation moves the remaining instruction and auxiliary-data arrays and
adjusts all branch offsets, making the overall work quadratic in the
program length.

A privileged loader can submit 131072 unconditional jumps by zero followed
by a valid return. Verification finishes quickly, but bpf_opt_remove_nops()
then spends a long time removing each jump separately. Since this
post-verification work neither checks for signals nor reschedules, a pending
SIGKILL cannot terminate the task until the rewrite finishes.

Make bpf_patch_insn_data() and verifier_remove_insns() common cancellation
and rescheduling points. These helpers run from BPF_PROG_LOAD process
context, and bpf_patch_insn_data() can already sleep while reallocating
auxiliary data.

Report interrupted constant blinding as -EINTR and propagate it through
both JIT paths, including kernels that permit interpreter fallback.
Other blinding failures retain the existing fallback behavior.

This does not reduce the quadratic cost of the rewrite passes, but it makes
the work preemptible and allows a killed loader to be torn down promptly.

Fixes: 52875a04f4b2 ("bpf: verifier: remove dead code")
Reported-by: Nicholas Carlini <npc@anthropic.com>
Suggested-by: Nicholas Carlini <npc@anthropic.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/core.c   | 21 +++++++++++++++++----
 kernel/bpf/fixups.c | 24 ++++++++++++++++++++++--
 2 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 8b294dfc1ad4..2e3bf8113ae9 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -19,6 +19,7 @@
 
 #include <uapi/linux/btf.h>
 #include <linux/filter.h>
+#include <linux/sched/signal.h>
 #include <linux/skbuff.h>
 #include <linux/static_call.h>
 #include <linux/vmalloc.h>
@@ -1619,6 +1620,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
 			 * fix it up here on error.
 			 */
 			bpf_jit_prog_release_other(prog, clone);
+			if (env && fatal_signal_pending(current))
+				return ERR_PTR(-EINTR);
 			return IS_ERR(tmp) ? tmp : ERR_PTR(-ENOMEM);
 		}
 
@@ -2636,11 +2639,14 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc
 	orig_prog = prog;
 	prog = bpf_jit_blind_constants(env, prog);
 	/*
-	 * If blinding was requested and we failed during blinding, we must fall
-	 * back to the interpreter.
+	 * Fall back to the interpreter after blinding failures, except when
+	 * the loader was killed.
 	 */
-	if (IS_ERR(prog))
+	if (IS_ERR(prog)) {
+		if (PTR_ERR(prog) == -EINTR)
+			return prog;
 		goto out_restore;
+	}
 
 	prog = bpf_int_jit_compile(env, prog);
 	if (prog->jited) {
@@ -2659,6 +2665,8 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc
 struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp,
 					   int *err)
 {
+	struct bpf_prog *jit_prog;
+
 	/* In case of BPF to BPF calls, verifier did all the prep
 	 * work with regards to JITing, etc.
 	 */
@@ -2681,7 +2689,12 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct
 		if (*err)
 			return fp;
 
-		fp = bpf_prog_jit_compile(env, fp);
+		jit_prog = bpf_prog_jit_compile(env, fp);
+		if (IS_ERR(jit_prog)) {
+			*err = PTR_ERR(jit_prog);
+			return fp;
+		}
+		fp = jit_prog;
 		bpf_prog_jit_attempt_done(fp);
 		if (!fp->jited && jit_needed) {
 			*err = -ENOTSUPP;
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 52d3cec33672..d6f83521fc78 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -8,6 +8,7 @@
 #include <linux/bsearch.h>
 #include <linux/sort.h>
 #include <linux/perf_event.h>
+#include <linux/sched/signal.h>
 #include <net/xdp.h>
 #include "disasm.h"
 
@@ -306,12 +307,28 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len)
 	}
 }
 
+/*
+ * Some post-verification instruction rewriting passes require an
+ * O(prog->len) operation per instruction. Keep their shared primitives
+ * killable and preemptible.
+ */
+static bool bpf_rewrite_must_abort(void)
+{
+	if (fatal_signal_pending(current))
+		return true;
+	cond_resched();
+	return false;
+}
+
 struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
 				     const struct bpf_insn *patch, u32 len)
 {
 	struct bpf_prog *new_prog;
 	struct bpf_insn_aux_data *new_data = NULL;
 
+	if (bpf_rewrite_must_abort())
+		return NULL;
+
 	if (len > 1) {
 		new_data = vrealloc(env->insn_aux_data,
 				    array_size(env->prog->len + len - 1,
@@ -523,6 +540,9 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt)
 	unsigned int orig_prog_len = env->prog->len;
 	int err;
 
+	if (bpf_rewrite_must_abort())
+		return -EINTR;
+
 	if (bpf_prog_is_offloaded(env->prog->aux))
 		bpf_prog_offload_remove_insns(env, off, cnt);
 
@@ -1356,7 +1376,7 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env)
 		}
 		prog = bpf_jit_blind_constants(env, prog);
 		if (IS_ERR(prog)) {
-			err = -ENOMEM;
+			err = PTR_ERR(prog);
 			prog = orig_prog;
 			goto out_restore;
 		}
@@ -1433,7 +1453,7 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env)
 		err = bpf_jit_subprogs(env);
 		if (err == 0)
 			return 0;
-		if (err == -EFAULT)
+		if (err == -EFAULT || err == -EINTR)
 			return err;
 	}
 #ifndef CONFIG_BPF_JIT_ALWAYS_ON
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 02/11] bpf: Preserve packet pointer class displacement in regsafe()
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
  2026-09-14 22:24 ` [PATCH bpf v4 01/11] bpf: Make post-verification instruction rewrites killable Kumar Kartikeya Dwivedi
@ 2026-09-14 22:24 ` Kumar Kartikeya Dwivedi
  2026-09-14 22:25 ` [PATCH bpf v4 03/11] selftests/bpf: Test packet pointer class displacement pruning Kumar Kartikeya Dwivedi
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:24 UTC (permalink / raw)
  To: bpf
  Cc: Nicholas Carlini, Eduard Zingerman, Alexei Starovoitov,
	Andrii Nakryiko, Daniel Borkmann, Emil Tsalapatis, kkd,
	kernel-team

regsafe() maps packet pointer IDs between states and checks that each
current register range is a subset of the corresponding explored
register range. It does not, however, preserve the displacement between
registers that share a packet pointer ID.

This is unsound because packet range is shared by ID. A bounds check on
one class member updates every member, and a later access can consume the
range through another member. Commit 022ac0750883 ("bpf: use reg->var_off
instead of reg->off for pointers") folded the fixed pointer offset into
r64 and removed the old off equality check, so two individually narrower
registers can prune even when their displacement has changed. The
explored path can then license an out-of-bounds packet access on the
pruned path.

Require matching range bases for packet pointers with an ID. Together
with the existing ID mapping, this preserves the displacement between
members of each packet-pointer class without adding per-ID state.
Packet pointers without an ID remain unaffected.

Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers")
Reported-by: Nicholas Carlini <npc@anthropic.com>
Suggested-by: Nicholas Carlini <npc@anthropic.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/states.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 66fb11b6c6a7..6c88ad95b63b 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -635,6 +635,9 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
 		/* id relations must be preserved */
 		if (!check_ids(rold->id, rcur->id, idmap))
 			return false;
+		/* Preserve displacements between pointers sharing an ID. */
+		if (rold->id && rold->r64.base != rcur->r64.base)
+			return false;
 		/* new val must satisfy old val knowledge */
 		return range_within(rold, rcur) &&
 		       tnum_in(rold->var_off, rcur->var_off);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 03/11] selftests/bpf: Test packet pointer class displacement pruning
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
  2026-09-14 22:24 ` [PATCH bpf v4 01/11] bpf: Make post-verification instruction rewrites killable Kumar Kartikeya Dwivedi
  2026-09-14 22:24 ` [PATCH bpf v4 02/11] bpf: Preserve packet pointer class displacement in regsafe() Kumar Kartikeya Dwivedi
@ 2026-09-14 22:25 ` Kumar Kartikeya Dwivedi
  2026-09-14 22:25 ` [PATCH bpf v4 04/11] bpf: Apply CO-RE relocations before subprogram validation Kumar Kartikeya Dwivedi
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:25 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, Nicholas Carlini, kkd,
	kernel-team

Add two paths whose packet pointer ranges are individually compatible at
a join but whose members have different relative displacements. The first
path proves an eight-byte access through one member. On the second path,
the same guard only proves that the access starts before data_end.

An affected verifier prunes the second path and accepts the program. With
packet pointer class displacement preserved, it explores that path and
rejects the out-of-bounds access.

Read the unknown offset and branch selector directly from XDP context
fields, and force state checkpoints so the pruning attempt does not
depend on the verifier checkpoint heuristics.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 .../progs/verifier_xdp_direct_packet_access.c | 35 +++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
index 0b86d95a4133..9866bc154194 100644
--- a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
@@ -1719,4 +1719,39 @@ l0_%=:	r0 = 0;						\
 	: __clobber_all);
 }
 
+SEC("xdp")
+__description("XDP pkt regsafe preserves packet pointer class displacement")
+__failure __msg("R2 min value is outside of the allowed memory range")
+__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ)
+__naked void pkt_regsafe_class_displacement(void)
+{
+	asm volatile ("					\
+	r8 = *(u32 *)(r1 + %[xdp_md_data_end]);		\
+	r9 = *(u32 *)(r1 + %[xdp_md_data]);		\
+	r4 = *(u32 *)(r1 + %[xdp_md_rx_queue_index]);	\
+	r4 &= 15;					\
+	r0 = *(u32 *)(r1 + %[xdp_md_ingress_ifindex]);	\
+	if r0 != 0 goto l0_%=;				\
+	r2 = r9;					\
+	r2 += r4;					\
+	r3 = r2;					\
+	r3 += 8;					\
+	goto l1_%=;					\
+l0_%=:	r4 &= 3;					\
+	r4 += 8;					\
+	r2 = r9;					\
+	r2 += r4;					\
+	r3 = r2;					\
+l1_%=:	if r3 > r8 goto l2_%=;				\
+	r0 = *(u64 *)(r2 + 0);				\
+l2_%=:	r0 = 0;						\
+	exit;						\
+"	:
+	: __imm_const(xdp_md_data, offsetof(struct xdp_md, data)),
+	  __imm_const(xdp_md_data_end, offsetof(struct xdp_md, data_end)),
+	  __imm_const(xdp_md_rx_queue_index, offsetof(struct xdp_md, rx_queue_index)),
+	  __imm_const(xdp_md_ingress_ifindex, offsetof(struct xdp_md, ingress_ifindex))
+	: __clobber_all);
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 04/11] bpf: Apply CO-RE relocations before subprogram validation
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
                   ` (2 preceding siblings ...)
  2026-09-14 22:25 ` [PATCH bpf v4 03/11] selftests/bpf: Test packet pointer class displacement pruning Kumar Kartikeya Dwivedi
@ 2026-09-14 22:25 ` Kumar Kartikeya Dwivedi
  2026-09-14 23:15   ` bot+bpf-ci
  2026-09-16 18:22   ` Eduard Zingerman
  2026-09-14 22:25 ` [PATCH bpf v4 05/11] selftests/bpf: Test early in-kernel CO-RE relocation Kumar Kartikeya Dwivedi
                   ` (6 subsequent siblings)
  10 siblings, 2 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:25 UTC (permalink / raw)
  To: bpf
  Cc: Andrii Nakryiko, Alexei Starovoitov, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, Nicholas Carlini, kkd,
	kernel-team

check_subprogs() verifies that each subprogram ends in an exit or an
unconditional jump before in-kernel CO-RE relocations are applied. An
unresolved relocation can then replace that terminal instruction with an
invalid helper call. The resulting fall-through into another subprogram
breaks the CFG invariant used by postorder and stack liveness analysis,
which can write past their per-subprogram arrays.

Apply CO-RE relocations immediately after preparing the program BTF, before
subprogram discovery and validation. Keep func_info and line_info validation
after subprogram discovery because those records depend on the complete
subprogram layout.

Include core_relo_cnt when deciding whether to prepare program BTF. A load
that supplied only CO-RE relocation metadata previously skipped both BTF
setup and relocation processing.

Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Suggested-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 include/linux/bpf_verifier.h |  2 ++
 kernel/bpf/check_btf.c       | 12 ++++--------
 kernel/bpf/verifier.c        |  7 ++++++-
 3 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 36b65797877d..bba5a727c651 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1197,6 +1197,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id)
 
 int bpf_prepare_btf_info(struct bpf_verifier_env *env,
 			 const union bpf_attr *attr, bpfptr_t uattr);
+int bpf_check_core_relo(struct bpf_verifier_env *env,
+			const union bpf_attr *attr, bpfptr_t uattr);
 int bpf_check_btf_info(struct bpf_verifier_env *env,
 		       const union bpf_attr *attr, bpfptr_t uattr);
 
diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c
index 0e8b3ccc7a5b..4c1ed842f661 100644
--- a/kernel/bpf/check_btf.c
+++ b/kernel/bpf/check_btf.c
@@ -338,9 +338,9 @@ static int check_btf_line(struct bpf_verifier_env *env,
 #define MIN_CORE_RELO_SIZE	sizeof(struct bpf_core_relo)
 #define MAX_CORE_RELO_SIZE	MAX_FUNCINFO_REC_SIZE
 
-static int check_core_relo(struct bpf_verifier_env *env,
-			   const union bpf_attr *attr,
-			   bpfptr_t uattr)
+int bpf_check_core_relo(struct bpf_verifier_env *env,
+			const union bpf_attr *attr,
+			bpfptr_t uattr)
 {
 	u32 i, nr_core_relo, ncopy, expected_size, rec_size;
 	struct bpf_core_relo core_relo = {};
@@ -414,7 +414,7 @@ int bpf_prepare_btf_info(struct bpf_verifier_env *env,
 	struct btf *btf;
 	int err;
 
-	if (!attr->func_info_cnt && !attr->line_info_cnt) {
+	if (!attr->func_info_cnt && !attr->line_info_cnt && !attr->core_relo_cnt) {
 		if (check_abnormal_return(env))
 			return -EINVAL;
 		return 0;
@@ -455,9 +455,5 @@ int bpf_check_btf_info(struct bpf_verifier_env *env,
 	if (err)
 		return err;
 
-	err = check_core_relo(env, attr, uattr);
-	if (err)
-		return err;
-
 	return 0;
 }
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index ddba53eaa333..68d1b67b8c1a 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -21271,6 +21271,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
 	if (ret < 0)
 		goto skip_full_check;
 
+	/* Apply CO-RE before validating the program's instruction layout. */
+	ret = bpf_check_core_relo(env, attr, uattr);
+	if (ret < 0)
+		goto skip_full_check;
+
 	/* Discover all subprograms before validating their layout and BTF. */
 	ret = add_subprogs(env);
 	if (ret < 0)
@@ -21280,7 +21285,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
 	if (ret < 0)
 		goto skip_full_check;
 
-	/* Validate BTF against the complete subprogram layout and apply CO-RE. */
+	/* Validate BTF against the complete subprogram layout. */
 	ret = bpf_check_btf_info(env, attr, uattr);
 	if (ret < 0)
 		goto skip_full_check;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 05/11] selftests/bpf: Test early in-kernel CO-RE relocation
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
                   ` (3 preceding siblings ...)
  2026-09-14 22:25 ` [PATCH bpf v4 04/11] bpf: Apply CO-RE relocations before subprogram validation Kumar Kartikeya Dwivedi
@ 2026-09-14 22:25 ` Kumar Kartikeya Dwivedi
  2026-09-14 23:28   ` bot+bpf-ci
  2026-09-16 18:23   ` Eduard Zingerman
  2026-09-14 22:25 ` [PATCH bpf v4 06/11] bpf: Restrict CO-RE poisoning to relocatable instructions Kumar Kartikeya Dwivedi
                   ` (5 subsequent siblings)
  10 siblings, 2 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:25 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, Nicholas Carlini, kkd,
	kernel-team

Add a raw program load with CO-RE relocation metadata but no func_info or
line_info. Place the relocation in dead code and require the poisoning log,
proving that the kernel processes standalone CO-RE metadata instead of
silently skipping it.

Also poison a subprogram's terminal exit and require check_subprogs() to
reject the resulting fall-through. This confirms that in-kernel CO-RE runs
before the verifier validates the subprogram layout and before later CFG
consumers rely on it.

Load both instruction streams without relocation metadata first to ensure
that CO-RE processing causes the rejection and diagnostic.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 .../selftests/bpf/prog_tests/core_reloc_raw.c | 128 ++++++++++++++++++
 1 file changed, 128 insertions(+)

diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
index a18d3680fb16..6fdeba6d7eaa 100644
--- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
+++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
@@ -14,6 +14,133 @@
 
 static char log[16 * 1024];
 
+static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt,
+				struct bpf_func_info *funcs, int func_cnt,
+				int enum_id, int access_str_off, int insn_idx,
+				bool relocate)
+{
+	struct bpf_core_relo relo = {
+		.insn_off = insn_idx * sizeof(struct bpf_insn),
+		.type_id = enum_id,
+		.access_str_off = access_str_off,
+		.kind = BPF_CORE_ENUMVAL_VALUE,
+	};
+	union bpf_attr attr = {
+		.prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
+		.insn_cnt = insn_cnt,
+		.insns = (__u64)insns,
+		.license = (__u64)"GPL",
+		.log_buf = (__u64)log,
+		.log_size = sizeof(log),
+		.log_level = 2,
+		.prog_btf_fd = btf_fd,
+		.func_info_rec_size = sizeof(struct bpf_func_info),
+		.func_info = (__u64)funcs,
+		.func_info_cnt = func_cnt,
+	};
+
+	if (relocate) {
+		attr.core_relo_cnt = 1;
+		attr.core_relos = (__u64)&relo;
+		attr.core_relo_rec_size = sizeof(relo);
+	}
+	memset(log, 0, sizeof(log));
+	return sys_bpf_prog_load(&attr, sizeof(attr), 1);
+}
+
+static void test_early_core_relo(void)
+{
+	struct bpf_insn core_only[] = {
+		BPF_MOV64_IMM(BPF_REG_0, 0),
+		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1),
+		BPF_MOV64_IMM(BPF_REG_0, 0),
+		BPF_EXIT_INSN(),
+	};
+	struct bpf_insn subprog[] = {
+		BPF_CALL_REL(2),
+		BPF_MOV64_IMM(BPF_REG_0, 0),
+		BPF_EXIT_INSN(),
+		BPF_MOV64_IMM(BPF_REG_0, 0),
+		BPF_EXIT_INSN(),
+	};
+	struct bpf_func_info funcs[2];
+	const void *raw_btf;
+	struct btf *btf;
+	__u32 raw_btf_size;
+	int access_str_off, btf_fd = -1, enum_id;
+	int int_id, main_id, prog_fd = -1, proto_id, sub_id;
+
+	btf = btf__new_empty();
+	if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
+		return;
+	int_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+	if (!ASSERT_GT(int_id, 0, "add_int"))
+		goto cleanup;
+	proto_id = btf__add_func_proto(btf, int_id);
+	if (!ASSERT_GT(proto_id, 0, "add_func_proto"))
+		goto cleanup;
+	main_id = btf__add_func(btf, "main_fn", BTF_FUNC_STATIC, proto_id);
+	if (!ASSERT_GT(main_id, 0, "add_main_func"))
+		goto cleanup;
+	sub_id = btf__add_func(btf, "sub_fn", BTF_FUNC_STATIC, proto_id);
+	if (!ASSERT_GT(sub_id, 0, "add_sub_func"))
+		goto cleanup;
+	enum_id = btf__add_enum(btf, "core_relo_poison_missing", 4);
+	if (!ASSERT_GT(enum_id, 0, "add_enum") ||
+	    !ASSERT_OK(btf__add_enum_value(btf, "value", 0), "add_enum_value"))
+		goto cleanup;
+	access_str_off = btf__add_str(btf, "0");
+	if (!ASSERT_GT(access_str_off, 0, "add_access_str"))
+		goto cleanup;
+
+	raw_btf = btf__raw_data(btf, &raw_btf_size);
+	if (!ASSERT_OK_PTR(raw_btf, "raw_btf"))
+		goto cleanup;
+	btf_fd = bpf_btf_load(raw_btf, raw_btf_size, NULL);
+	if (!ASSERT_GE(btf_fd, 0, "btf_load"))
+		goto cleanup;
+	funcs[0] = (struct bpf_func_info) { .insn_off = 0, .type_id = main_id };
+	funcs[1] = (struct bpf_func_info) { .insn_off = 3, .type_id = sub_id };
+
+	if (test__start_subtest("without_func_info")) {
+		prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0,
+					       enum_id, access_str_off, 2, false);
+		if (!ASSERT_GE(prog_fd, 0, "control_load"))
+			goto cleanup;
+		close(prog_fd);
+		prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0,
+					       enum_id, access_str_off, 2, true);
+		if (!ASSERT_GE(prog_fd, 0, "poisoned_load"))
+			goto cleanup;
+		ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log");
+		close(prog_fd);
+		prog_fd = -1;
+	}
+
+	if (test__start_subtest("poisoned_subprog_terminator")) {
+		prog_fd = load_core_relo_insns(btf_fd, subprog, ARRAY_SIZE(subprog), funcs, 2,
+					       enum_id, access_str_off, 2, false);
+		if (!ASSERT_GE(prog_fd, 0, "control_load"))
+			goto cleanup;
+		close(prog_fd);
+		prog_fd = load_core_relo_insns(btf_fd, subprog, ARRAY_SIZE(subprog), funcs, 2,
+					       enum_id, access_str_off, 2, true);
+		if (!ASSERT_LT(prog_fd, 0, "poisoned_load"))
+			goto cleanup;
+		ASSERT_HAS_SUBSTR(log, "last insn is not an exit or jmp", "poisoned_load_log");
+	}
+
+cleanup:
+	if (env.verbosity > VERBOSE_NORMAL && log[0]) {
+		printf("-------- program load log start --------\n");
+		printf("%s", log);
+		printf("-------- program load log end ----------\n");
+	}
+	close(prog_fd);
+	close(btf_fd);
+	btf__free(btf);
+}
+
 /* Check that verifier rejects BPF program containing relocation
  * pointing to non-existent BTF type.
  */
@@ -120,6 +247,7 @@ static void test_bad_local_id(void)
 
 void test_core_reloc_raw(void)
 {
+	test_early_core_relo();
 	if (test__start_subtest("bad_local_id"))
 		test_bad_local_id();
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 06/11] bpf: Restrict CO-RE poisoning to relocatable instructions
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
                   ` (4 preceding siblings ...)
  2026-09-14 22:25 ` [PATCH bpf v4 05/11] selftests/bpf: Test early in-kernel CO-RE relocation Kumar Kartikeya Dwivedi
@ 2026-09-14 22:25 ` Kumar Kartikeya Dwivedi
  2026-09-14 23:28   ` bot+bpf-ci
  2026-09-14 22:25 ` [PATCH bpf v4 07/11] selftests/bpf: Test CO-RE instruction poisoning restrictions Kumar Kartikeya Dwivedi
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:25 UTC (permalink / raw)
  To: bpf
  Cc: Nicholas Carlini, Eduard Zingerman, Alexei Starovoitov,
	Andrii Nakryiko, Daniel Borkmann, Emil Tsalapatis, kkd,
	kernel-team

CO-RE relocation records can name any instruction offset. When a
relocation cannot be resolved, bpf_core_patch_insn() currently poisons its
target before checking whether that instruction is a valid relocation
target. Malformed metadata can therefore replace jumps, calls, exits,
register-source arithmetic, or non-immediate loads instead of failing at
the relocation step.

Handle poisoning only after the instruction has passed the same class and
operand-form checks used for a resolved relocation. Route invalid forms
through the existing diagnostic and return a hard error. Keep poisoning
supported instructions, including both halves of a plain ldimm64, so an
unresolved relocation in dead code remains valid.

Move poisoning into a helper that handles both halves of ldimm64, and
return its status directly from each validated instruction case. This
avoids routing the success path through a common label and leaves the
helper free to report errors.

The shared relocation code applies this restriction to both libbpf and
in-kernel CO-RE.

Fixes: eacaaed784e2 ("libbpf: Implement enum value-based CO-RE relocations")
Reported-by: Nicholas Carlini <npc@anthropic.com>
Suggested-by: Nicholas Carlini <npc@anthropic.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 tools/lib/bpf/relo_core.c | 58 +++++++++++++++++++++------------------
 1 file changed, 31 insertions(+), 27 deletions(-)

diff --git a/tools/lib/bpf/relo_core.c b/tools/lib/bpf/relo_core.c
index 8ad2715721cf..2672623a4198 100644
--- a/tools/lib/bpf/relo_core.c
+++ b/tools/lib/bpf/relo_core.c
@@ -980,23 +980,30 @@ static int bpf_core_calc_relo(const char *prog_name,
 }
 
 /*
- * Turn instruction for which CO_RE relocation failed into invalid one with
+ * Turn instruction for which CO-RE relocation failed into invalid one with
  * distinct signature.
  */
-static void bpf_core_poison_insn(const char *prog_name, int relo_idx,
-				 int insn_idx, struct bpf_insn *insn)
+static int bpf_core_poison_insn(const char *prog_name, int relo_idx,
+				struct bpf_insn *insn, int insn_idx)
 {
-	pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n",
-		 prog_name, relo_idx, insn_idx);
-	insn->code = BPF_JMP | BPF_CALL;
-	insn->dst_reg = 0;
-	insn->src_reg = 0;
-	insn->off = 0;
-	/* if this instruction is reachable (not a dead code),
-	 * verifier will complain with the following message:
-	 * invalid func unknown#195896080
-	 */
-	insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */
+	int insn_cnt = is_ldimm64_insn(insn) ? 2 : 1;
+	int i;
+
+	for (i = 0; i < insn_cnt; i++) {
+		pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n",
+			 prog_name, relo_idx, insn_idx + i);
+		insn[i].code = BPF_JMP | BPF_CALL;
+		insn[i].dst_reg = 0;
+		insn[i].src_reg = 0;
+		insn[i].off = 0;
+		/*
+		 * If this instruction is reachable (not dead code), the verifier
+		 * will complain with "invalid func unknown#195896080".
+		 */
+		insn[i].imm = 195896080; /* => 0xbad2310 => "bad relo" */
+	}
+
+	return 0;
 }
 
 static int insn_bpf_size_to_bytes(struct bpf_insn *insn)
@@ -1047,17 +1054,6 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
 
 	class = BPF_CLASS(insn->code);
 
-	if (res->poison) {
-poison:
-		/* poison second part of ldimm64 to avoid confusing error from
-		 * verifier about "unknown opcode 00"
-		 */
-		if (is_ldimm64_insn(insn))
-			bpf_core_poison_insn(prog_name, relo_idx, insn_idx + 1, insn + 1);
-		bpf_core_poison_insn(prog_name, relo_idx, insn_idx, insn);
-		return 0;
-	}
-
 	orig_val = res->orig_val;
 	new_val = res->new_val;
 
@@ -1065,7 +1061,9 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
 	case BPF_ALU:
 	case BPF_ALU64:
 		if (BPF_SRC(insn->code) != BPF_K)
-			return -EINVAL;
+			goto bad_insn;
+		if (res->poison)
+			return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx);
 		if (res->validate && insn->imm != orig_val) {
 			pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %d, exp %llu -> %llu\n",
 				prog_name, relo_idx,
@@ -1082,6 +1080,8 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
 	case BPF_LDX:
 	case BPF_ST:
 	case BPF_STX:
+		if (res->poison)
+			return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx);
 		if (res->validate && insn->off != orig_val) {
 			pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDX/ST/STX) value: got %d, exp %llu -> %llu\n",
 				prog_name, relo_idx, insn_idx, insn->off, (unsigned long long)orig_val,
@@ -1097,7 +1097,7 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
 			pr_warn("prog '%s': relo #%d: insn #%d (LDX/ST/STX) accesses field incorrectly. "
 				"Make sure you are accessing pointers, unsigned integers, or fields of matching type and size.\n",
 				prog_name, relo_idx, insn_idx);
-			goto poison;
+			return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx);
 		}
 
 		orig_val = insn->off;
@@ -1140,6 +1140,9 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
 			return -EINVAL;
 		}
 
+		if (res->poison)
+			return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx);
+
 		imm = (__u32)insn[0].imm | ((__u64)insn[1].imm << 32);
 		if (res->validate && imm != orig_val) {
 			pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDIMM64) value: got %llu, exp %llu -> %llu\n",
@@ -1157,6 +1160,7 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
 		break;
 	}
 	default:
+bad_insn:
 		pr_warn("prog '%s': relo #%d: trying to relocate unrecognized insn #%d, code:0x%x, src:0x%x, dst:0x%x, off:0x%x, imm:0x%x\n",
 			prog_name, relo_idx, insn_idx, insn->code,
 			(unsigned)insn->src_reg, (unsigned)insn->dst_reg, (unsigned)insn->off, (unsigned)insn->imm);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 07/11] selftests/bpf: Test CO-RE instruction poisoning restrictions
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
                   ` (5 preceding siblings ...)
  2026-09-14 22:25 ` [PATCH bpf v4 06/11] bpf: Restrict CO-RE poisoning to relocatable instructions Kumar Kartikeya Dwivedi
@ 2026-09-14 22:25 ` Kumar Kartikeya Dwivedi
  2026-09-16 18:27   ` Eduard Zingerman
  2026-09-14 22:25 ` [PATCH bpf v4 08/11] bpf: Assign lock identity to callback map values Kumar Kartikeya Dwivedi
                   ` (3 subsequent siblings)
  10 siblings, 1 reply; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:25 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, Nicholas Carlini, kkd,
	kernel-team

Add raw CO-RE relocations that fail to resolve their target enum value.
Place each supported and unsupported instruction form in dead code.
Unsupported targets must fail relocation with a diagnostic even when they
are unreachable. Supported ALU immediates, memory accesses, and ldimm64
instructions must still be poisoned and removed as dead code, allowing the
program to load. Check that both halves of ldimm64 are poisoned.

Update the subprogram terminator regression to require rejection at the
relocation step now that an exit is not accepted as a relocation target.
Load every program without relocations first to ensure that rejection is
caused by the relocation rather than the original instruction stream.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 .../selftests/bpf/prog_tests/core_reloc_raw.c | 68 ++++++++++++++++++-
 1 file changed, 66 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
index 6fdeba6d7eaa..90f1ea85354d 100644
--- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
+++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
@@ -50,6 +50,28 @@ static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt
 
 static void test_early_core_relo(void)
 {
+	static const char unrecognized[] = "trying to relocate unrecognized insn #2";
+	static const struct {
+		const char *name;
+		struct bpf_insn insns[2];
+		const char *err_msg;
+	} tests[] = {
+		{ "poison_exit", { BPF_EXIT_INSN() }, unrecognized },
+		{ "poison_ja", { BPF_JMP_A(1) }, unrecognized },
+		{ "poison_jmp", { BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized },
+		{ "poison_jmp32", { BPF_JMP32_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized },
+		{ "poison_call", { BPF_EMIT_CALL(BPF_FUNC_get_prandom_u32) }, unrecognized },
+		{ "poison_alu_reg", { BPF_MOV32_REG(BPF_REG_0, BPF_REG_1) }, unrecognized },
+		{ "poison_alu64_reg", { BPF_MOV64_REG(BPF_REG_0, BPF_REG_1) }, unrecognized },
+		{ "poison_ld_abs", { BPF_LD_ABS(BPF_W, 0) },
+		  "insn #2 (LDIMM64) has unexpected form" },
+		{ "poison_alu_imm", { BPF_MOV32_IMM(BPF_REG_0, 0) } },
+		{ "poison_alu64_imm", { BPF_MOV64_IMM(BPF_REG_0, 0) } },
+		{ "poison_ldx", { BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0) } },
+		{ "poison_st", { BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0) } },
+		{ "poison_stx", { BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4) } },
+		{ "poison_ldimm64", { BPF_LD_IMM64(BPF_REG_0, 0) } },
+	};
 	struct bpf_insn core_only[] = {
 		BPF_MOV64_IMM(BPF_REG_0, 0),
 		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1),
@@ -68,7 +90,7 @@ static void test_early_core_relo(void)
 	struct btf *btf;
 	__u32 raw_btf_size;
 	int access_str_off, btf_fd = -1, enum_id;
-	int int_id, main_id, prog_fd = -1, proto_id, sub_id;
+	int int_id, main_id, prog_fd = -1, proto_id, sub_id, i;
 
 	btf = btf__new_empty();
 	if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
@@ -127,7 +149,49 @@ static void test_early_core_relo(void)
 					       enum_id, access_str_off, 2, true);
 		if (!ASSERT_LT(prog_fd, 0, "poisoned_load"))
 			goto cleanup;
-		ASSERT_HAS_SUBSTR(log, "last insn is not an exit or jmp", "poisoned_load_log");
+		ASSERT_HAS_SUBSTR(log, unrecognized, "poisoned_load_log");
+	}
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		struct bpf_insn insns[] = {
+			BPF_MOV64_IMM(BPF_REG_0, 0),
+			BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
+			tests[i].insns[0],
+			BPF_MOV64_IMM(BPF_REG_0, 0),
+			BPF_MOV64_IMM(BPF_REG_0, 0),
+			BPF_EXIT_INSN(),
+		};
+		bool is_ldimm64 = insns[2].code == (BPF_LD | BPF_DW | BPF_IMM);
+		int insn_cnt = ARRAY_SIZE(insns);
+
+		if (!test__start_subtest(tests[i].name))
+			continue;
+		if (is_ldimm64) {
+			insns[3] = tests[i].insns[1];
+		} else {
+			insns[1].off = 1;
+			insns[4] = BPF_EXIT_INSN();
+			insn_cnt--;
+		}
+		prog_fd = load_core_relo_insns(btf_fd, insns, insn_cnt, funcs, 1,
+					       enum_id, access_str_off, 2, false);
+		if (!ASSERT_GE(prog_fd, 0, "control_load"))
+			goto cleanup;
+		close(prog_fd);
+		prog_fd = load_core_relo_insns(btf_fd, insns, insn_cnt, funcs, 1,
+					       enum_id, access_str_off, 2, true);
+		if (!tests[i].err_msg) {
+			ASSERT_GE(prog_fd, 0, "dead_poison_load");
+			ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log");
+			if (is_ldimm64)
+				ASSERT_HAS_SUBSTR(log, "substituting insn #3", "poison_ldimm64_log");
+		} else {
+			ASSERT_LT(prog_fd, 0, "invalid_poison_load");
+			ASSERT_HAS_SUBSTR(log, tests[i].err_msg, "invalid_poison_log");
+			ASSERT_NULL(strstr(log, "substituting insn"), "invalid_poison_substitution");
+		}
+		close(prog_fd);
+		prog_fd = -1;
 	}
 
 cleanup:
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 08/11] bpf: Assign lock identity to callback map values
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
                   ` (6 preceding siblings ...)
  2026-09-14 22:25 ` [PATCH bpf v4 07/11] selftests/bpf: Test CO-RE instruction poisoning restrictions Kumar Kartikeya Dwivedi
@ 2026-09-14 22:25 ` Kumar Kartikeya Dwivedi
  2026-09-14 22:25 ` [PATCH bpf v4 09/11] selftests/bpf: Check callback map value lock identity Kumar Kartikeya Dwivedi
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:25 UTC (permalink / raw)
  To: bpf
  Cc: Nicholas Carlini, Eduard Zingerman, Alexei Starovoitov,
	Andrii Nakryiko, Daniel Borkmann, Emil Tsalapatis, kkd,
	kernel-team

A nested bpf_for_each_map_elem() callback can unlock a different element
of the same map:

  static long inner(void *map, int *key, struct value *v,
                    struct value **outer_value)
  {
          bpf_spin_lock(&v->lock);
          bpf_spin_unlock(&(*outer_value)->lock);
          return 0;
  }

  static long outer(void *map, int *key, struct value *v, void *ctx)
  {
          bpf_for_each_map_elem(map, inner, &v, 0);
          return 0;
  }

Both callback values currently have ID zero and the same map_ptr.
process_spin_lock() compares those two fields, so it accepts the unlock
even though the two callbacks can receive different map elements.

Assign a fresh ID to every callback map value in the for-each,
timer/workqueue, and task-work constructors. Copies of one callback
argument retain its ID, so locking and unlocking through that argument
continues to work. Distinct callbacks also get distinct IDs for
single-element arrays, including inner arrays sharing inner_map_meta.

Preserve map_uid for every inner-map lookup and compare it through
check_ids() during state pruning. This preserves relationships between
maps, keys, and values while allowing equivalent states with different
lookup IDs to match. It avoids field-specific rules for when an inner map
needs an identity.

Move map_uid out of the metadata union and next to the other IDs, so
register comparisons can use the existing memcmp() ranges and remap the
IDs separately. Clear it when resetting a register or converting a map
lookup result to a socket pointer. Shrink frameno to u8, which is enough
for MAX_CALL_FRAMES, to make room without growing bpf_reg_state.

Fixes: d0d78c1df9b1 ("bpf: Allow locking bpf_spin_lock global variables")
Reported-by: Nicholas Carlini <npc@anthropic.com>
Suggested-by: Nicholas Carlini <npc@anthropic.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 include/linux/bpf_verifier.h | 24 ++++++++++++------------
 kernel/bpf/states.c          |  6 ++++--
 kernel/bpf/verifier.c        | 13 +++++++++----
 3 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index bba5a727c651..1aa245faa30e 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -46,17 +46,10 @@ struct bpf_reg_state {
 		/* valid when type == PTR_TO_PACKET */
 		int range;
 
-		/* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE |
-		 *   PTR_TO_MAP_VALUE_OR_NULL
+		/*
+		 * For CONST_PTR_TO_MAP, PTR_TO_MAP_KEY and PTR_TO_MAP_VALUE.
 		 */
-		struct {
-			struct bpf_map *map_ptr;
-			/* To distinguish map lookups from outer map
-			 * the map_uid is non-zero for registers
-			 * pointing to inner maps.
-			 */
-			u32 map_uid;
-		};
+		struct bpf_map *map_ptr;
 
 		/* for PTR_TO_BTF_ID */
 		struct {
@@ -155,13 +148,20 @@ struct bpf_reg_state {
 	 * gets parent_id set to the dynptr's id.
 	 */
 	u32 parent_id;
-	/* Inside the callee two registers can be both PTR_TO_STACK like
+	/*
+	 * Distinguishes inner-map lookups and their keys and values. Zero for
+	 * other registers. Kept outside the metadata union for ID remapping
+	 * during state comparisons.
+	 */
+	u32 map_uid;
+	/*
+	 * Inside the callee two registers can be both PTR_TO_STACK like
 	 * R1=fp-8 and R2=fp-8, but one of them points to this function stack
 	 * while another to the caller's stack. To differentiate them 'frameno'
 	 * is used which is an index in bpf_verifier_state->frame[] array
 	 * pointing to bpf_func_state.
 	 */
-	u32 frameno;
+	u8 frameno;
 	/* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */
 	bool precise;
 };
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 6c88ad95b63b..e4ec007f7fa6 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -491,7 +491,8 @@ static bool regs_exact(const struct bpf_reg_state *rold,
 {
 	return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 &&
 	       check_ids(rold->id, rcur->id, idmap) &&
-	       check_ids(rold->parent_id, rcur->parent_id, idmap);
+	       check_ids(rold->parent_id, rcur->parent_id, idmap) &&
+	       check_ids(rold->map_uid, rcur->map_uid, idmap);
 }
 
 enum exact_level {
@@ -616,7 +617,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
 		       range_within(rold, rcur) &&
 		       tnum_in(rold->var_off, rcur->var_off) &&
 		       check_ids(rold->id, rcur->id, idmap) &&
-		       check_ids(rold->parent_id, rcur->parent_id, idmap);
+		       check_ids(rold->parent_id, rcur->parent_id, idmap) &&
+		       check_ids(rold->map_uid, rcur->map_uid, idmap);
 	case PTR_TO_PACKET_META:
 	case PTR_TO_PACKET:
 		/* We must have at least as much range as the old ptr
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 68d1b67b8c1a..a39d13a6614d 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -1864,6 +1864,7 @@ static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm)
 	       offsetof(struct bpf_reg_state, var_off) - sizeof(reg->type));
 	reg->id = 0;
 	reg->parent_id = 0;
+	reg->map_uid = 0;
 	___mark_reg_known(reg, imm);
 }
 
@@ -1925,17 +1926,18 @@ static void refine_map_lookup_value(struct bpf_reg_state *reg)
 	if (map->inner_map_meta) {
 		reg->type = CONST_PTR_TO_MAP | maybe_null;
 		reg->map_ptr = map->inner_map_meta;
-		/* transfer reg's id which is unique for every map_lookup_elem
+		/*
+		 * transfer reg's id which is unique for every map_lookup_elem
 		 * as UID of the inner map.
 		 */
-		if (btf_record_has_field(map->inner_map_meta->record,
-					 BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK))
-			reg->map_uid = reg->id;
+		reg->map_uid = reg->id;
 	} else if (map->map_type == BPF_MAP_TYPE_XSKMAP) {
 		reg->type = PTR_TO_XDP_SOCK | maybe_null;
+		reg->map_uid = 0;
 	} else if (map->map_type == BPF_MAP_TYPE_SOCKMAP ||
 		   map->map_type == BPF_MAP_TYPE_SOCKHASH) {
 		reg->type = PTR_TO_SOCKET | maybe_null;
+		reg->map_uid = 0;
 	}
 }
 
@@ -10048,6 +10050,7 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env,
 	__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
 	callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr;
 	callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid;
+	callee->regs[BPF_REG_3].id = ++env->id_gen;
 
 	/* pointer to stack or null */
 	callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3];
@@ -10144,6 +10147,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,
 	__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
 	callee->regs[BPF_REG_3].map_ptr = map_ptr;
 	callee->regs[BPF_REG_3].map_uid = map_uid;
+	callee->regs[BPF_REG_3].id = ++env->id_gen;
 
 	/* unused */
 	bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
@@ -10262,6 +10266,7 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,
 	__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
 	callee->regs[BPF_REG_3].map_ptr = map_ptr;
 	callee->regs[BPF_REG_3].map_uid = map_uid;
+	callee->regs[BPF_REG_3].id = ++env->id_gen;
 
 	/* unused */
 	bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 09/11] selftests/bpf: Check callback map value lock identity
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
                   ` (7 preceding siblings ...)
  2026-09-14 22:25 ` [PATCH bpf v4 08/11] bpf: Assign lock identity to callback map values Kumar Kartikeya Dwivedi
@ 2026-09-14 22:25 ` Kumar Kartikeya Dwivedi
  2026-09-16 19:04   ` Eduard Zingerman
  2026-09-14 22:25 ` [PATCH bpf v4 10/11] libbpf: Reject truncated ldimm64 CO-RE relocations Kumar Kartikeya Dwivedi
  2026-09-14 22:25 ` [PATCH bpf v4 11/11] selftests/bpf: Add truncated CO-RE ldimm64 test Kumar Kartikeya Dwivedi
  10 siblings, 1 reply; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:25 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, Nicholas Carlini, kkd,
	kernel-team

Add a verifier test which retains a map value from an outer callback and
then acquires a lock through an inner callback value before attempting to
release the outer callback value. Both values can denote different elements,
so the verifier must reject the mismatched unlock.

Also exercise callbacks reached through two inner-map lookups. The lookup
results share inner_map_meta but may refer to different one-element
arrays, so their callback values must retain distinct lock identities.

Keep nested and one-element array callbacks as positive controls for
balanced locking through one callback argument.

For timer, workqueue, and task-work callbacks, lock and unlock the value,
then pass it to bpf_this_cpu_ptr() to expose its ID in the verifier error
log. Require a nonzero ID, following the existing lock identity tests.
Balanced locking alone would still succeed if a constructor stopped
assigning an ID.

Extend the existing spin_lock test table and reuse its array and inner-map
fixtures to keep the callback cases alongside the other lock identity tests.

Update the nested callback reference-leak expectation for the extra
callback value ID.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 .../selftests/bpf/prog_tests/cb_refs.c        |   2 +-
 .../selftests/bpf/prog_tests/spin_lock.c      |  33 +++-
 .../selftests/bpf/progs/test_spin_lock_fail.c | 167 +++++++++++++++++-
 3 files changed, 192 insertions(+), 10 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
index 78566b817fd7..490e15e7126d 100644
--- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c
+++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
@@ -13,7 +13,7 @@ struct {
 } cb_refs_tests[] = {
 	{ "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" },
 	{ "leak_prog", "Possibly NULL pointer passed to helper R2" },
-	{ "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */
+	{ "nested_cb", "Unreleased reference id=5 alloc_insn=2" }, /* alloc_insn=2{4,5} */
 	{ "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */
 };
 
diff --git a/tools/testing/selftests/bpf/prog_tests/spin_lock.c b/tools/testing/selftests/bpf/prog_tests/spin_lock.c
index 5c3579438427..d49f52a8f822 100644
--- a/tools/testing/selftests/bpf/prog_tests/spin_lock.c
+++ b/tools/testing/selftests/bpf/prog_tests/spin_lock.c
@@ -10,8 +10,8 @@ static char log_buf[1024 * 1024];
 
 static struct {
 	const char *prog_name;
-	const char *err_msg;
-} spin_lock_fail_tests[] = {
+	const char *err_msg; /* NULL if loading should succeed. */
+} spin_lock_tests[] = {
 	{ "lock_id_kptr_preserve",
 	  "[0-9]\\+: (bf) r1 = r0                       ; R0=ptr_foo(id=2)"
 	  " R1=ptr_foo(id=2) refs=2\n"
@@ -54,6 +54,22 @@ static struct {
 	{ "lock_global_sleepable_helper_subprog", "global function calls are not allowed while holding a lock" },
 	{ "lock_global_sleepable_kfunc_subprog", "global function calls are not allowed while holding a lock" },
 	{ "lock_global_sleepable_subprog_indirect", "global function calls are not allowed while holding a lock" },
+	{ "callback_value_lock_identity", "bpf_spin_unlock of different lock" },
+	{ "callback_inner_map_value_lock_identity", "bpf_spin_unlock of different lock" },
+	{ "callback_value_lock_identity_same", NULL },
+	{ "callback_single_value_lock_identity_same", NULL },
+	{ "lock_id_timer_preserve",
+	  "R1=map_value(id=[1-9][0-9]*,map=async_lock_map,[^\n]*\n"
+	  "[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n"
+	  "R1 type=map_value expected=percpu_ptr_" },
+	{ "lock_id_wq_preserve",
+	  "R1=map_value(id=[1-9][0-9]*,map=async_lock_map,[^\n]*\n"
+	  "[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n"
+	  "R1 type=map_value expected=percpu_ptr_" },
+	{ "lock_id_task_work_preserve",
+	  "R1=map_value(id=[1-9][0-9]*,map=async_lock_map,[^\n]*\n"
+	  "[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n"
+	  "R1 type=map_value expected=percpu_ptr_" },
 };
 
 static int match_regex(const char *pattern, const char *string)
@@ -74,7 +90,7 @@ static int match_regex(const char *pattern, const char *string)
 	return rc == 0 ? 1 : 0;
 }
 
-static void test_spin_lock_fail_prog(const char *prog_name, const char *err_msg)
+static void test_spin_lock_prog(const char *prog_name, const char *err_msg)
 {
 	LIBBPF_OPTS(bpf_object_open_opts, opts, .kernel_log_buf = log_buf,
 						.kernel_log_size = sizeof(log_buf),
@@ -94,6 +110,10 @@ static void test_spin_lock_fail_prog(const char *prog_name, const char *err_msg)
 	bpf_program__set_autoload(prog, true);
 
 	ret = test_spin_lock_fail__load(skel);
+	if (!err_msg) {
+		ASSERT_OK(ret, "test_spin_lock_fail__load");
+		goto end;
+	}
 	if (!ASSERT_ERR(ret, "test_spin_lock_fail__load must fail"))
 		goto end;
 
@@ -166,10 +186,9 @@ void test_spin_lock(void)
 
 	test_spin_lock_success();
 
-	for (i = 0; i < ARRAY_SIZE(spin_lock_fail_tests); i++) {
-		if (!test__start_subtest(spin_lock_fail_tests[i].prog_name))
+	for (i = 0; i < ARRAY_SIZE(spin_lock_tests); i++) {
+		if (!test__start_subtest(spin_lock_tests[i].prog_name))
 			continue;
-		test_spin_lock_fail_prog(spin_lock_fail_tests[i].prog_name,
-					 spin_lock_fail_tests[i].err_msg);
+		test_spin_lock_prog(spin_lock_tests[i].prog_name, spin_lock_tests[i].err_msg);
 	}
 }
diff --git a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c
index f678ee6bd7ea..a27bbbe3523f 100644
--- a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c
+++ b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c
@@ -14,17 +14,18 @@ struct array_map {
 	__type(key, int);
 	__type(value, struct foo);
 	__uint(max_entries, 1);
-} array_map SEC(".maps");
+} array_map SEC(".maps"), array_map_b SEC(".maps");
 
 struct {
 	__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
-	__uint(max_entries, 1);
+	__uint(max_entries, 2);
 	__type(key, int);
 	__type(value, int);
 	__array(values, struct array_map);
 } map_of_maps SEC(".maps") = {
 	.values = {
 		[0] = &array_map,
+		[1] = &array_map_b,
 	},
 };
 
@@ -314,4 +315,166 @@ int lock_global_sleepable_subprog_indirect(struct __sk_buff *ctx)
 	return ret;
 }
 
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 2);
+	__type(key, int);
+	__type(value, struct foo);
+} callback_array_map SEC(".maps");
+
+struct callback_ctx {
+	struct foo *value;
+};
+
+static long lock_different_value(struct bpf_map *map, int *key,
+				 struct foo *value, struct callback_ctx *ctx)
+{
+	bpf_spin_lock(&value->lock);
+	bpf_spin_unlock(&ctx->value->lock);
+	return 0;
+}
+
+static long nest_lock_different_value(struct bpf_map *map, int *key,
+				      struct foo *value, void *data)
+{
+	struct callback_ctx ctx = { .value = value };
+
+	bpf_for_each_map_elem(&callback_array_map, lock_different_value, &ctx, 0);
+	return 0;
+}
+
+SEC("?tc")
+int callback_value_lock_identity(void *ctx)
+{
+	bpf_for_each_map_elem(&callback_array_map, nest_lock_different_value, NULL, 0);
+	return 0;
+}
+
+static long nest_lock_different_inner_value(struct bpf_map *map, int *key,
+					    struct foo *value, void *data)
+{
+	struct callback_ctx ctx = { .value = value };
+	int inner_key = 1;
+	void *inner_map;
+
+	inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key);
+	if (!inner_map)
+		return 0;
+	bpf_for_each_map_elem(inner_map, lock_different_value, &ctx, 0);
+	return 0;
+}
+
+SEC("?tc")
+int callback_inner_map_value_lock_identity(void *ctx)
+{
+	int inner_key = 0;
+	void *inner_map;
+
+	inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key);
+	if (!inner_map)
+		return 0;
+	bpf_for_each_map_elem(inner_map, nest_lock_different_inner_value, NULL, 0);
+	return 0;
+}
+
+static long lock_same_value(struct bpf_map *map, int *key,
+			    struct foo *value, void *data)
+{
+	bpf_spin_lock(&value->lock);
+	bpf_spin_unlock(&value->lock);
+	return 0;
+}
+
+static long nest_lock_same_value(struct bpf_map *map, int *key,
+				 struct foo *value, void *data)
+{
+	bpf_for_each_map_elem(&callback_array_map, lock_same_value, NULL, 0);
+	return 0;
+}
+
+SEC("?tc")
+int callback_value_lock_identity_same(void *ctx)
+{
+	bpf_for_each_map_elem(&callback_array_map, nest_lock_same_value, NULL, 0);
+	return 0;
+}
+
+SEC("?tc")
+int callback_single_value_lock_identity_same(void *ctx)
+{
+	bpf_for_each_map_elem(&array_map, lock_same_value, NULL, 0);
+	return 0;
+}
+
+struct async_lock_value {
+	struct bpf_spin_lock lock;
+	struct bpf_timer timer;
+	struct bpf_wq work;
+	struct bpf_task_work task_work;
+	int counter;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 2);
+	__type(key, int);
+	__type(value, struct async_lock_value);
+} async_lock_map SEC(".maps");
+
+/*
+ * Expose the callback value ID in the verifier log, as in lock_id_mapval_preserve().
+ */
+static int async_lock_id_preserve(void *map, int *key, void *data)
+{
+	struct async_lock_value *value = data;
+
+	bpf_spin_lock(&value->lock);
+	value->counter++;
+	bpf_spin_unlock(&value->lock);
+	bpf_this_cpu_ptr(value);
+	return 0;
+}
+
+SEC("?tc")
+int lock_id_timer_preserve(void *ctx)
+{
+	struct async_lock_value *value;
+	int key = 0;
+
+	value = bpf_map_lookup_elem(&async_lock_map, &key);
+	if (value && !bpf_timer_init(&value->timer, &async_lock_map, 1))
+		bpf_timer_set_callback(&value->timer, async_lock_id_preserve);
+	return 0;
+}
+
+SEC("?tc")
+int lock_id_wq_preserve(void *ctx)
+{
+	struct async_lock_value *value;
+	int key = 0;
+
+	value = bpf_map_lookup_elem(&async_lock_map, &key);
+	if (value && !bpf_wq_init(&value->work, &async_lock_map, 0))
+		bpf_wq_set_callback(&value->work, async_lock_id_preserve, 0);
+	return 0;
+}
+
+static int task_work_lock_id_preserve(struct bpf_map *map, void *key, void *data)
+{
+	return async_lock_id_preserve(map, key, data);
+}
+
+SEC("?tc")
+int lock_id_task_work_preserve(void *ctx)
+{
+	struct async_lock_value *value;
+	int key = 0;
+
+	value = bpf_map_lookup_elem(&async_lock_map, &key);
+	if (value)
+		bpf_task_work_schedule_resume(bpf_get_current_task_btf(), &value->task_work,
+					      &async_lock_map, task_work_lock_id_preserve);
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 10/11] libbpf: Reject truncated ldimm64 CO-RE relocations
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
                   ` (8 preceding siblings ...)
  2026-09-14 22:25 ` [PATCH bpf v4 09/11] selftests/bpf: Check callback map value lock identity Kumar Kartikeya Dwivedi
@ 2026-09-14 22:25 ` Kumar Kartikeya Dwivedi
  2026-09-16 18:37   ` Eduard Zingerman
  2026-09-14 22:25 ` [PATCH bpf v4 11/11] selftests/bpf: Add truncated CO-RE ldimm64 test Kumar Kartikeya Dwivedi
  10 siblings, 1 reply; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:25 UTC (permalink / raw)
  To: bpf
  Cc: Sashiko, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, Nicholas Carlini, kkd,
	kernel-team

CO-RE relocation of an ldimm64 instruction operates on two instruction
slots. The original ldimm64 support checked that the second slot existed
only on the resolved relocation path, leaving unresolved relocation
poisoning able to write past the instruction allocation. When CO-RE logic
was moved to the shared relocation implementation, even the resolved-path
check was lost because bpf_core_patch_insn() no longer received the
program length.

A malformed BPF ELF can therefore end a function after the first ldimm64
slot and attach a CO-RE relocation to it. libbpf allocates the instruction
array according to the function symbol size. bpf_core_patch_insn() then
reads the absent second slot while validating a resolved relocation or
writes a poison instruction past the allocation for an unresolved one.

Pass the total instruction count to the shared patch helper and reject a
terminal ldimm64 before either relocation result is handled. Thread the
count through the in-kernel caller as well, since it compiles the same
shared relocation implementation.

Fixes: eacaaed784e2 ("libbpf: Implement enum value-based CO-RE relocations")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://lore.kernel.org/20260914140852.03DA21F0089B@smtp.kernel.org
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 include/linux/bpf.h       |  2 +-
 kernel/bpf/btf.c          |  6 +++---
 kernel/bpf/check_btf.c    |  2 +-
 tools/lib/bpf/libbpf.c    |  3 ++-
 tools/lib/bpf/relo_core.c | 11 +++++++++--
 tools/lib/bpf/relo_core.h |  5 +++--
 6 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index e57af902560c..62155d87ce31 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3196,7 +3196,7 @@ bool btf_type_ids_nocast_alias(struct bpf_verifier_log *log,
 			       const struct btf *arg_btf, u32 arg_id);
 
 int bpf_core_apply(struct bpf_core_ctx *ctx, const struct bpf_core_relo *relo,
-		   int relo_idx, void *insn);
+		   int relo_idx, void *insn, size_t insn_cnt);
 
 static inline bool unprivileged_ebpf_enabled(void)
 {
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index d67a169ba2bb..78cd9661cdbd 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -9729,7 +9729,7 @@ bpf_core_find_cands(struct bpf_core_ctx *ctx, u32 local_type_id)
 }
 
 int bpf_core_apply(struct bpf_core_ctx *ctx, const struct bpf_core_relo *relo,
-		   int relo_idx, void *insn)
+		   int relo_idx, void *insn, size_t insn_cnt)
 {
 	bool need_cands = relo->kind != BPF_CORE_TYPE_ID_LOCAL;
 	struct bpf_core_cand_list cands = {};
@@ -9793,8 +9793,8 @@ int bpf_core_apply(struct bpf_core_ctx *ctx, const struct bpf_core_relo *relo,
 	if (err)
 		goto out;
 
-	err = bpf_core_patch_insn((void *)ctx->log, insn, relo->insn_off / 8, relo, relo_idx,
-				  &targ_res);
+	err = bpf_core_patch_insn((void *)ctx->log, insn, insn_cnt, relo->insn_off / 8,
+				  relo, relo_idx, &targ_res);
 
 out:
 	kfree(specs);
diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c
index 4c1ed842f661..63d0375f5bb0 100644
--- a/kernel/bpf/check_btf.c
+++ b/kernel/bpf/check_btf.c
@@ -399,7 +399,7 @@ int bpf_check_core_relo(struct bpf_verifier_env *env,
 		}
 
 		err = bpf_core_apply(&ctx, &core_relo, i,
-				     &prog->insnsi[core_relo.insn_off / 8]);
+				     &prog->insnsi[core_relo.insn_off / 8], prog->len);
 		if (err)
 			break;
 		bpfptr_add(&u_core_relo, rec_size);
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index b749c01742ee..619e664f0c00 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -6223,7 +6223,8 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path)
 				goto out;
 			}
 
-			err = bpf_core_patch_insn(prog->name, insn, insn_idx, rec, i, &targ_res);
+			err = bpf_core_patch_insn(prog->name, insn, prog->insns_cnt, insn_idx,
+						  rec, i, &targ_res);
 			if (err) {
 				pr_warn("prog '%s': relo #%d: failed to patch insn #%d: %s\n",
 					prog->name, i, insn_idx, errstr(err));
diff --git a/tools/lib/bpf/relo_core.c b/tools/lib/bpf/relo_core.c
index 2672623a4198..92ff923f0d13 100644
--- a/tools/lib/bpf/relo_core.c
+++ b/tools/lib/bpf/relo_core.c
@@ -1046,12 +1046,19 @@ static int insn_bytes_to_bpf_size(__u32 sz)
  * 6. *(T *)(rX + <off>) = <imm>, where T is one of {u8, u16, u32, u64}.
  */
 int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
-			int insn_idx, const struct bpf_core_relo *relo,
-			int relo_idx, const struct bpf_core_relo_res *res)
+			size_t insn_cnt, int insn_idx,
+			const struct bpf_core_relo *relo, int relo_idx,
+			const struct bpf_core_relo_res *res)
 {
 	__u64 orig_val, new_val;
 	__u8 class;
 
+	if (is_ldimm64_insn(insn) && (size_t)insn_idx + 1 >= insn_cnt) {
+		pr_warn("prog '%s': relo #%d: insn #%d (LDIMM64) is truncated\n",
+			prog_name, relo_idx, insn_idx);
+		return -EINVAL;
+	}
+
 	class = BPF_CLASS(insn->code);
 
 	orig_val = res->orig_val;
diff --git a/tools/lib/bpf/relo_core.h b/tools/lib/bpf/relo_core.h
index 1c0566daf8e8..db2e106dc73d 100644
--- a/tools/lib/bpf/relo_core.h
+++ b/tools/lib/bpf/relo_core.h
@@ -87,8 +87,9 @@ int bpf_core_calc_relo_insn(const char *prog_name,
 			    struct bpf_core_relo_res *targ_res);
 
 int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
-			int insn_idx, const struct bpf_core_relo *relo,
-			int relo_idx, const struct bpf_core_relo_res *res);
+			size_t insn_cnt, int insn_idx,
+			const struct bpf_core_relo *relo, int relo_idx,
+			const struct bpf_core_relo_res *res);
 
 int bpf_core_parse_spec(const char *prog_name, const struct btf *btf,
 		        const struct bpf_core_relo *relo,
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH bpf v4 11/11] selftests/bpf: Add truncated CO-RE ldimm64 test
  2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
                   ` (9 preceding siblings ...)
  2026-09-14 22:25 ` [PATCH bpf v4 10/11] libbpf: Reject truncated ldimm64 CO-RE relocations Kumar Kartikeya Dwivedi
@ 2026-09-14 22:25 ` Kumar Kartikeya Dwivedi
  2026-09-14 23:15   ` bot+bpf-ci
  2026-09-16 18:43   ` Eduard Zingerman
  10 siblings, 2 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-14 22:25 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, Nicholas Carlini, kkd,
	kernel-team

Exercise CO-RE relocation of an ldimm64 instruction whose second slot is
missing. Build a valid BPF object with a compiler-emitted relocation, then
truncate the program symbol to retain only the first instruction slot.

Without bounds validation, libbpf reads or writes beyond the exact-sized
instruction allocation, depending on whether the relocation resolves.
Exercise both paths and require bpf_object__prepare() to reject the
malformed object with a precise diagnostic.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 .../prog_tests/core_reloc_truncated_ldimm64.c | 82 +++++++++++++++++++
 .../bpf/progs/core_reloc_truncated_ldimm64.c  | 27 ++++++
 2 files changed, 109 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/core_reloc_truncated_ldimm64.c
 create mode 100644 tools/testing/selftests/bpf/progs/core_reloc_truncated_ldimm64.c

diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_truncated_ldimm64.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_truncated_ldimm64.c
new file mode 100644
index 000000000000..b70c8874241d
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_truncated_ldimm64.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <elf.h>
+#include <test_progs.h>
+
+#include "core_reloc_truncated_ldimm64.skel.h"
+
+static bool truncate_ldimm64_prog(void *elf_data, size_t elf_sz, const char *prog_name)
+{
+	Elf64_Ehdr *ehdr = elf_data;
+	Elf64_Shdr *shdrs;
+	Elf64_Sym *syms;
+	const char *strs;
+	size_t i, j;
+
+	if (elf_sz < sizeof(*ehdr) || ehdr->e_shoff > elf_sz ||
+	    ehdr->e_shnum > (elf_sz - ehdr->e_shoff) / sizeof(*shdrs))
+		return false;
+
+	shdrs = elf_data + ehdr->e_shoff;
+	for (i = 0; i < ehdr->e_shnum; i++) {
+		if (shdrs[i].sh_type != SHT_SYMTAB)
+			continue;
+		syms = elf_data + shdrs[i].sh_offset;
+		strs = elf_data + shdrs[shdrs[i].sh_link].sh_offset;
+		for (j = 0; j < shdrs[i].sh_size / sizeof(*syms); j++) {
+			if (ELF64_ST_TYPE(syms[j].st_info) == STT_FUNC &&
+			    !strcmp(strs + syms[j].st_name, prog_name)) {
+				syms[j].st_size = sizeof(struct bpf_insn);
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+static void run_test(const char *prog_name)
+{
+	const void *elf_bytes;
+	size_t elf_sz;
+	struct bpf_object *obj = NULL;
+	void *elf_data = NULL;
+	char *log = NULL;
+	int err;
+
+	elf_bytes = core_reloc_truncated_ldimm64__elf_bytes(&elf_sz);
+	if (!ASSERT_OK_PTR(elf_bytes, "elf_bytes"))
+		return;
+
+	elf_data = malloc(elf_sz);
+	if (!ASSERT_OK_PTR(elf_data, "elf_data"))
+		return;
+	memcpy(elf_data, elf_bytes, elf_sz);
+	if (!ASSERT_TRUE(truncate_ldimm64_prog(elf_data, elf_sz, prog_name), "truncate_prog"))
+		goto cleanup;
+
+	obj = bpf_object__open_mem(elf_data, elf_sz, NULL);
+	if (!ASSERT_OK_PTR(obj, "obj_open"))
+		goto cleanup;
+
+	if (start_libbpf_log_capture())
+		goto cleanup;
+	err = bpf_object__prepare(obj);
+	log = stop_libbpf_log_capture();
+	ASSERT_EQ(err, -EINVAL, "obj_prepare");
+	ASSERT_HAS_SUBSTR(log, "insn #0 (LDIMM64) is truncated", "libbpf_log");
+
+cleanup:
+	free(log);
+	bpf_object__close(obj);
+	free(elf_data);
+}
+
+void test_core_reloc_truncated_ldimm64(void)
+{
+	if (test__start_subtest("resolved"))
+		run_test("core_reloc_truncated_ldimm64_resolved");
+	if (test__start_subtest("poison"))
+		run_test("core_reloc_truncated_ldimm64_poison");
+}
diff --git a/tools/testing/selftests/bpf/progs/core_reloc_truncated_ldimm64.c b/tools/testing/selftests/bpf/progs/core_reloc_truncated_ldimm64.c
new file mode 100644
index 000000000000..d8d9a3650fe0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/core_reloc_truncated_ldimm64.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+
+enum core_reloc_truncated_ldimm64 {
+	CORE_RELOC_TRUNCATED_LDIMM64_VALUE,
+};
+
+SEC("raw_tracepoint/sys_enter")
+int core_reloc_truncated_ldimm64_resolved(void *ctx)
+{
+	return bpf_core_enum_value_exists(enum core_reloc_truncated_ldimm64,
+					  CORE_RELOC_TRUNCATED_LDIMM64_VALUE);
+}
+
+SEC("raw_tracepoint/sys_exit")
+int core_reloc_truncated_ldimm64_poison(void *ctx)
+{
+	return bpf_core_enum_value(enum core_reloc_truncated_ldimm64,
+				   CORE_RELOC_TRUNCATED_LDIMM64_VALUE);
+}
+
+char LICENSE[] SEC("license") = "GPL";
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 04/11] bpf: Apply CO-RE relocations before subprogram validation
  2026-09-14 22:25 ` [PATCH bpf v4 04/11] bpf: Apply CO-RE relocations before subprogram validation Kumar Kartikeya Dwivedi
@ 2026-09-14 23:15   ` bot+bpf-ci
  2026-09-16 18:22   ` Eduard Zingerman
  1 sibling, 0 replies; 24+ messages in thread
From: bot+bpf-ci @ 2026-09-14 23:15 UTC (permalink / raw)
  To: memxor, bpf
  Cc: andrii, ast, daniel, eddyz87, emil, npc, kkd, kernel-team, ast,
	andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
	ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 1431 bytes --]

> check_subprogs() verifies that each subprogram ends in an exit or an
> unconditional jump before in-kernel CO-RE relocations are applied. An
> unresolved relocation can then replace that terminal instruction with an
> invalid helper call. The resulting fall-through into another subprogram
> breaks the CFG invariant used by postorder and stack liveness analysis,
> which can write past their per-subprogram arrays.
>
> Apply CO-RE relocations immediately after preparing the program BTF, before
> subprogram discovery and validation. Keep func_info and line_info validation
> after subprogram discovery because those records depend on the complete
> subprogram layout.
>
> Include core_relo_cnt when deciding whether to prepare program BTF. A load
> that supplied only CO-RE relocation metadata previously skipped both BTF
> setup and relocation processing.
>
> Suggested-by: Andrii Nakryiko <andrii@kernel.org>
> Suggested-by: Alexei Starovoitov <ast@kernel.org>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>

This looks like a bug fix for CO-RE relocation ordering. Should this
include:

  Fixes: fbd94c7afcf9 ("bpf: Pass a set of bpf_core_relo-s to prog_load command.")


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34904671165

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 11/11] selftests/bpf: Add truncated CO-RE ldimm64 test
  2026-09-14 22:25 ` [PATCH bpf v4 11/11] selftests/bpf: Add truncated CO-RE ldimm64 test Kumar Kartikeya Dwivedi
@ 2026-09-14 23:15   ` bot+bpf-ci
  2026-09-16 18:43   ` Eduard Zingerman
  1 sibling, 0 replies; 24+ messages in thread
From: bot+bpf-ci @ 2026-09-14 23:15 UTC (permalink / raw)
  To: memxor, bpf
  Cc: ast, andrii, daniel, eddyz87, emil, npc, kkd, kernel-team, ast,
	andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
	ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 3223 bytes --]

> diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_truncated_ldimm64.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_truncated_ldimm64.c
> new file mode 100644
> index 0000000000000..b70c8874241de
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_truncated_ldimm64.c

[ ... ]

> @@ -0,0 +1,82 @@
> +static bool truncate_ldimm64_prog(void *elf_data, size_t elf_sz, const char *prog_name)
> +{
> +	Elf64_Ehdr *ehdr = elf_data;
> +	Elf64_Shdr *shdrs;
> +	Elf64_Sym *syms;
> +	const char *strs;
> +	size_t i, j;
> +
> +	if (elf_sz < sizeof(*ehdr) || ehdr->e_shoff > elf_sz ||
> +	    ehdr->e_shnum > (elf_sz - ehdr->e_shoff) / sizeof(*shdrs))
> +		return false;
> +
> +	shdrs = elf_data + ehdr->e_shoff;
> +	for (i = 0; i < ehdr->e_shnum; i++) {
> +		if (shdrs[i].sh_type != SHT_SYMTAB)
> +			continue;
> +		syms = elf_data + shdrs[i].sh_offset;
> +		strs = elf_data + shdrs[shdrs[i].sh_link].sh_offset;

This isn't a bug, but since elf_bytes here is always the skeleton's own
build artifact, would it read more simply to drop the header size
guard - or, if it is meant to make the walk safe, to also bound
sh_link, sh_offset and sh_size?

> +		for (j = 0; j < shdrs[i].sh_size / sizeof(*syms); j++) {
> +			if (ELF64_ST_TYPE(syms[j].st_info) == STT_FUNC &&
> +			    !strcmp(strs + syms[j].st_name, prog_name)) {
> +				syms[j].st_size = sizeof(struct bpf_insn);
> +				return true;
> +			}
> +		}
> +	}
> +
> +	return false;
> +}

[ ... ]

> diff --git a/tools/testing/selftests/bpf/progs/core_reloc_truncated_ldimm64.c b/tools/testing/selftests/bpf/progs/core_reloc_truncated_ldimm64.c
> new file mode 100644
> index 0000000000000..d8d9a3650fe0d
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/core_reloc_truncated_ldimm64.c
> @@ -0,0 +1,27 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
> +
> +#include "vmlinux.h"
> +
> +#include <bpf/bpf_helpers.h>
> +#include <bpf/bpf_core_read.h>
> +
> +enum core_reloc_truncated_ldimm64 {
> +	CORE_RELOC_TRUNCATED_LDIMM64_VALUE,
> +};
> +
> +SEC("raw_tracepoint/sys_enter")
> +int core_reloc_truncated_ldimm64_resolved(void *ctx)
> +{
> +	return bpf_core_enum_value_exists(enum core_reloc_truncated_ldimm64,
> +					  CORE_RELOC_TRUNCATED_LDIMM64_VALUE);
> +}
> +
> +SEC("raw_tracepoint/sys_exit")
> +int core_reloc_truncated_ldimm64_poison(void *ctx)
> +{
> +	return bpf_core_enum_value(enum core_reloc_truncated_ldimm64,
> +				   CORE_RELOC_TRUNCATED_LDIMM64_VALUE);
> +}
> +
> +char LICENSE[] SEC("license") = "GPL";


The commit message says:

>     Without bounds validation, libbpf reads or writes beyond the exact-sized
>     instruction allocation, depending on whether the relocation resolves.

This isn't a bug, but would it be worth saying that the second-slot read
happens on both paths and only the poison write is unresolved-specific,
and noting whether the kernel-side bpf_core_apply() path is covered
elsewhere?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34904671165

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 06/11] bpf: Restrict CO-RE poisoning to relocatable instructions
  2026-09-14 22:25 ` [PATCH bpf v4 06/11] bpf: Restrict CO-RE poisoning to relocatable instructions Kumar Kartikeya Dwivedi
@ 2026-09-14 23:28   ` bot+bpf-ci
  0 siblings, 0 replies; 24+ messages in thread
From: bot+bpf-ci @ 2026-09-14 23:28 UTC (permalink / raw)
  To: memxor, bpf
  Cc: npc, eddyz87, ast, andrii, daniel, emil, kkd, kernel-team, ast,
	andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
	ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 3423 bytes --]

> bpf: Restrict CO-RE poisoning to relocatable instructions
>
> CO-RE relocation records can name any instruction offset. When a
> relocation cannot be resolved, bpf_core_patch_insn() currently poisons its
> target before checking whether that instruction is a valid relocation
> target. Malformed metadata can therefore replace jumps, calls, exits,
> register-source arithmetic, or non-immediate loads instead of failing at
> the relocation step.
>
> Handle poisoning only after the instruction has passed the same class and
> operand-form checks used for a resolved relocation. Route invalid forms
> through the existing diagnostic and return a hard error. Keep poisoning
> supported instructions, including both halves of a plain ldimm64, so an
> unresolved relocation in dead code remains valid.
>
> Move poisoning into a helper that handles both halves of ldimm64, and
> return its status directly from each validated instruction case. This
> avoids routing the success path through a common label and leaves the
> helper free to report errors.
>
> The shared relocation code applies this restriction to both libbpf and
> in-kernel CO-RE.
>
> Fixes: eacaaed784e2 ("libbpf: Implement enum value-based CO-RE relocations")

Does the Fixes: tag point to the commit that introduced the defect the
changelog describes?

The changelog says the bug is that "bpf_core_patch_insn() currently poisons
its target before checking whether that instruction is a valid relocation
target". That structure predates eacaaed784e2. In eacaaed784e2's own parent
tree the pre-switch poison block is already there:

  $ git show eacaaed784e2^:tools/lib/bpf/libbpf.c | sed -n '4986,4989p'
        if (res->poison) {
                bpf_core_poison_insn(prog, relo_idx, insn_idx, insn);
                return 0;
        }
  ...
        switch (class) {

Tracing back, the unconditional "replace insn with invalid call before the
class switch" behaviour was introduced by d7a252708dbc ("libbpf: Improve
handling of failed CO-RE relocations"), which added the `else if
(!targ_spec)` branch that poisons and returns 0 ahead of `switch (class)`.

Then 353c788c2054 ("libbpf: Improve relocation ambiguity detection")
refactored it into `res->poison` + `bpf_core_poison_insn()`, still ahead of
the switch.

eacaaed784e2 only added the *second half* of ldimm64 to the existing poison
block. Poisoning was already reachable for field-based relos before
eacaaed784e2 (bpf_core_calc_field_relo's `if (!spec) return -EUCLEAN`), so
a malformed .BTF.ext record naming a jump/call/exit could already be
poisoned then.

d7a252708dbc appears to be the correct blame target for this patch.
eacaaed784e2 is the right target for the *following* patch in the series
(the one that rejects truncated ldimm64 CO-RE relocations), which does fix
something eacaaed784e2 added.

Because the tag drives stable backport range selection, the wrong tag means
the fix is offered to a narrower set of kernels than are affected.


Also, the third paragraph's "Move poisoning into a helper" is imprecise:
bpf_core_poison_insn() already existed as a helper before this patch. What
actually moves into it is the ldimm64 two-slot handling.


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34904671165

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 05/11] selftests/bpf: Test early in-kernel CO-RE relocation
  2026-09-14 22:25 ` [PATCH bpf v4 05/11] selftests/bpf: Test early in-kernel CO-RE relocation Kumar Kartikeya Dwivedi
@ 2026-09-14 23:28   ` bot+bpf-ci
  2026-09-16 18:23   ` Eduard Zingerman
  1 sibling, 0 replies; 24+ messages in thread
From: bot+bpf-ci @ 2026-09-14 23:28 UTC (permalink / raw)
  To: memxor, bpf
  Cc: ast, andrii, daniel, eddyz87, emil, npc, kkd, kernel-team, ast,
	andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
	ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 3605 bytes --]

> diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
> index a18d3680fb169..6fdeba6d7eaa9 100644
> --- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
> +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
> @@ -14,6 +14,133 @@
>
>  static char log[16 * 1024];
>
> +static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt,
> +				struct bpf_func_info *funcs, int func_cnt,
> +				int enum_id, int access_str_off, int insn_idx,
> +				bool relocate)
> +{
> +	struct bpf_core_relo relo = {
> +		.insn_off = insn_idx * sizeof(struct bpf_insn),
> +		.type_id = enum_id,
> +		.access_str_off = access_str_off,
> +		.kind = BPF_CORE_ENUMVAL_VALUE,
> +	};

[ ... ]

> +	if (test__start_subtest("poisoned_subprog_terminator")) {
> +		prog_fd = load_core_relo_insns(btf_fd, subprog, ARRAY_SIZE(subprog), funcs, 2,
> +					       enum_id, access_str_off, 2, false);
> +		if (!ASSERT_GE(prog_fd, 0, "control_load"))
> +			goto cleanup;
> +		close(prog_fd);
> +		prog_fd = load_core_relo_insns(btf_fd, subprog, ARRAY_SIZE(subprog), funcs, 2,
> +					       enum_id, access_str_off, 2, true);
> +		if (!ASSERT_LT(prog_fd, 0, "poisoned_load"))
> +			goto cleanup;
> +		ASSERT_HAS_SUBSTR(log, "last insn is not an exit or jmp", "poisoned_load_log");

Does this assertion break bisectability through the series?

At this commit the relocation targets insn #2 (the BPF_EXIT_INSN()
terminating the main subprogram) and bpf_core_patch_insn() poisons it
unconditionally before any instruction-class check:

    if (res->poison) {
    poison:
            ...
            bpf_core_poison_insn(prog_name, relo_idx, insn_idx, insn);
            return 0;
    }

so insn #2 becomes BPF_JMP|BPF_CALL and check_subprogs() emits exactly
"last insn is not an exit or jmp\n".

The immediate child commit 5dff146808a8b ("bpf: Restrict CO-RE
poisoning to relocatable instructions") deletes that early poison block
and routes BPF_JMP-class targets through the new 'bad_insn' label:

    default:
    bad_insn:
            pr_warn("prog '%s': relo #%d: trying to relocate unrecognized insn #%d, ...");
            return -EINVAL;

After that commit the load fails inside bpf_check_core_relo() (before
add_subprogs()/check_subprogs() ever run), so the log contains "trying
to relocate unrecognized insn #2" and never "last insn is not an exit
or jmp". ASSERT_LT(prog_fd, 0) still passes, but ASSERT_HAS_SUBSTR()
fails, breaking test_progs -t core_reloc_raw/poisoned_subprog_terminator
at 5dff146808a8b. Only the commit after it, 332e04e138005
("selftests/bpf: Test CO-RE instruction poisoning restrictions"),
repoints the assertion at the new message.

Either the assertion update belongs in 5dff146808a8b (the commit that
changes the behaviour), or this subtest should be ordered after the
poisoning restriction so that no commit in the series leaves it failing.

Also, keying the test on which validation stage rejects the program
(check_subprogs()' message) rather than on the load being refused is
what makes it fragile. The ordering property this commit's parent fixes
is observable as 'poisoning a terminal exit is refused before subprogram
layout is trusted', and the exact stage that refuses it is an
implementation detail that changed two commits later.

> +	}

[ ... ]


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34904671165

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 04/11] bpf: Apply CO-RE relocations before subprogram validation
  2026-09-14 22:25 ` [PATCH bpf v4 04/11] bpf: Apply CO-RE relocations before subprogram validation Kumar Kartikeya Dwivedi
  2026-09-14 23:15   ` bot+bpf-ci
@ 2026-09-16 18:22   ` Eduard Zingerman
  1 sibling, 0 replies; 24+ messages in thread
From: Eduard Zingerman @ 2026-09-16 18:22 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Andrii Nakryiko, Alexei Starovoitov, Daniel Borkmann,
	Emil Tsalapatis, Nicholas Carlini, kkd, kernel-team

On Tue, 2026-09-15 at 00:25 +0200, Kumar Kartikeya Dwivedi wrote:
> check_subprogs() verifies that each subprogram ends in an exit or an
> unconditional jump before in-kernel CO-RE relocations are applied. An
> unresolved relocation can then replace that terminal instruction with an
> invalid helper call. The resulting fall-through into another subprogram
> breaks the CFG invariant used by postorder and stack liveness analysis,
> which can write past their per-subprogram arrays.
> 
> Apply CO-RE relocations immediately after preparing the program BTF, before
> subprogram discovery and validation. Keep func_info and line_info validation
> after subprogram discovery because those records depend on the complete
> subprogram layout.
> 
> Include core_relo_cnt when deciding whether to prepare program BTF. A load
> that supplied only CO-RE relocation metadata previously skipped both BTF
> setup and relocation processing.
> 
> Suggested-by: Andrii Nakryiko <andrii@kernel.org>
> Suggested-by: Alexei Starovoitov <ast@kernel.org>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

...

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 05/11] selftests/bpf: Test early in-kernel CO-RE relocation
  2026-09-14 22:25 ` [PATCH bpf v4 05/11] selftests/bpf: Test early in-kernel CO-RE relocation Kumar Kartikeya Dwivedi
  2026-09-14 23:28   ` bot+bpf-ci
@ 2026-09-16 18:23   ` Eduard Zingerman
  2026-09-16 18:26     ` Kumar Kartikeya Dwivedi
  1 sibling, 1 reply; 24+ messages in thread
From: Eduard Zingerman @ 2026-09-16 18:23 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Emil Tsalapatis, Nicholas Carlini, kkd, kernel-team

On Tue, 2026-09-15 at 00:25 +0200, Kumar Kartikeya Dwivedi wrote:
> Add a raw program load with CO-RE relocation metadata but no func_info or
> line_info. Place the relocation in dead code and require the poisoning log,
> proving that the kernel processes standalone CO-RE metadata instead of
> silently skipping it.
> 
> Also poison a subprogram's terminal exit and require check_subprogs() to
> reject the resulting fall-through. This confirms that in-kernel CO-RE runs
> before the verifier validates the subprogram layout and before later CFG
> consumers rely on it.
> 
> Load both instruction streams without relocation metadata first to ensure
> that CO-RE processing causes the rejection and diagnostic.
> 
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

> +static void test_early_core_relo(void)
> +{
> +	struct bpf_insn core_only[] = {
> +		BPF_MOV64_IMM(BPF_REG_0, 0),
> +		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1),
> +		BPF_MOV64_IMM(BPF_REG_0, 0),
> +		BPF_EXIT_INSN(),
> +	};
> +	struct bpf_insn subprog[] = {
> +		BPF_CALL_REL(2),
> +		BPF_MOV64_IMM(BPF_REG_0, 0),
> +		BPF_EXIT_INSN(),
> +		BPF_MOV64_IMM(BPF_REG_0, 0),
> +		BPF_EXIT_INSN(),
> +	};
> +	struct bpf_func_info funcs[2];
> +	const void *raw_btf;
> +	struct btf *btf;
> +	__u32 raw_btf_size;
> +	int access_str_off, btf_fd = -1, enum_id;
> +	int int_id, main_id, prog_fd = -1, proto_id, sub_id;
> +
> +	btf = btf__new_empty();
> +	if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
> +		return;
> +	int_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
> +	if (!ASSERT_GT(int_id, 0, "add_int"))
> +		goto cleanup;
> +	proto_id = btf__add_func_proto(btf, int_id);
> +	if (!ASSERT_GT(proto_id, 0, "add_func_proto"))
> +		goto cleanup;
> +	main_id = btf__add_func(btf, "main_fn", BTF_FUNC_STATIC, proto_id);
> +	if (!ASSERT_GT(main_id, 0, "add_main_func"))
> +		goto cleanup;
> +	sub_id = btf__add_func(btf, "sub_fn", BTF_FUNC_STATIC, proto_id);
> +	if (!ASSERT_GT(sub_id, 0, "add_sub_func"))
> +		goto cleanup;
> +	enum_id = btf__add_enum(btf, "core_relo_poison_missing", 4);
> +	if (!ASSERT_GT(enum_id, 0, "add_enum") ||
> +	    !ASSERT_OK(btf__add_enum_value(btf, "value", 0), "add_enum_value"))
> +		goto cleanup;
> +	access_str_off = btf__add_str(btf, "0");
> +	if (!ASSERT_GT(access_str_off, 0, "add_access_str"))
> +		goto cleanup;

I asked to update this to use utility macro, but if there are no other
nits let's land as-is.

...

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 05/11] selftests/bpf: Test early in-kernel CO-RE relocation
  2026-09-16 18:23   ` Eduard Zingerman
@ 2026-09-16 18:26     ` Kumar Kartikeya Dwivedi
  0 siblings, 0 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 18:26 UTC (permalink / raw)
  To: Eduard Zingerman, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Emil Tsalapatis, Nicholas Carlini, kkd, kernel-team

On Wed Sep 16, 2026 at 8:23 PM CEST, Eduard Zingerman wrote:
> On Tue, 2026-09-15 at 00:25 +0200, Kumar Kartikeya Dwivedi wrote:
>> Add a raw program load with CO-RE relocation metadata but no func_info or
>> line_info. Place the relocation in dead code and require the poisoning log,
>> proving that the kernel processes standalone CO-RE metadata instead of
>> silently skipping it.
>>
>> Also poison a subprogram's terminal exit and require check_subprogs() to
>> reject the resulting fall-through. This confirms that in-kernel CO-RE runs
>> before the verifier validates the subprogram layout and before later CFG
>> consumers rely on it.
>>
>> Load both instruction streams without relocation metadata first to ensure
>> that CO-RE processing causes the rejection and diagnostic.
>>
>> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
>> ---
>
> Acked-by: Eduard Zingerman <eddyz87@gmail.com>
>
>> +static void test_early_core_relo(void)
>> +{
>> +	struct bpf_insn core_only[] = {
>> +		BPF_MOV64_IMM(BPF_REG_0, 0),
>> +		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1),
>> +		BPF_MOV64_IMM(BPF_REG_0, 0),
>> +		BPF_EXIT_INSN(),
>> +	};
>> +	struct bpf_insn subprog[] = {
>> +		BPF_CALL_REL(2),
>> +		BPF_MOV64_IMM(BPF_REG_0, 0),
>> +		BPF_EXIT_INSN(),
>> +		BPF_MOV64_IMM(BPF_REG_0, 0),
>> +		BPF_EXIT_INSN(),
>> +	};
>> +	struct bpf_func_info funcs[2];
>> +	const void *raw_btf;
>> +	struct btf *btf;
>> +	__u32 raw_btf_size;
>> +	int access_str_off, btf_fd = -1, enum_id;
>> +	int int_id, main_id, prog_fd = -1, proto_id, sub_id;
>> +
>> +	btf = btf__new_empty();
>> +	if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
>> +		return;
>> +	int_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
>> +	if (!ASSERT_GT(int_id, 0, "add_int"))
>> +		goto cleanup;
>> +	proto_id = btf__add_func_proto(btf, int_id);
>> +	if (!ASSERT_GT(proto_id, 0, "add_func_proto"))
>> +		goto cleanup;
>> +	main_id = btf__add_func(btf, "main_fn", BTF_FUNC_STATIC, proto_id);
>> +	if (!ASSERT_GT(main_id, 0, "add_main_func"))
>> +		goto cleanup;
>> +	sub_id = btf__add_func(btf, "sub_fn", BTF_FUNC_STATIC, proto_id);
>> +	if (!ASSERT_GT(sub_id, 0, "add_sub_func"))
>> +		goto cleanup;
>> +	enum_id = btf__add_enum(btf, "core_relo_poison_missing", 4);
>> +	if (!ASSERT_GT(enum_id, 0, "add_enum") ||
>> +	    !ASSERT_OK(btf__add_enum_value(btf, "value", 0), "add_enum_value"))
>> +		goto cleanup;
>> +	access_str_off = btf__add_str(btf, "0");
>> +	if (!ASSERT_GT(access_str_off, 0, "add_access_str"))
>> +		goto cleanup;
>
> I asked to update this to use utility macro, but if there are no other
> nits let's land as-is.
>
> ...

I have a local follow up, will send once this lands.
Thanks

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 07/11] selftests/bpf: Test CO-RE instruction poisoning restrictions
  2026-09-14 22:25 ` [PATCH bpf v4 07/11] selftests/bpf: Test CO-RE instruction poisoning restrictions Kumar Kartikeya Dwivedi
@ 2026-09-16 18:27   ` Eduard Zingerman
  0 siblings, 0 replies; 24+ messages in thread
From: Eduard Zingerman @ 2026-09-16 18:27 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Emil Tsalapatis, Nicholas Carlini, kkd, kernel-team

On Tue, 2026-09-15 at 00:25 +0200, Kumar Kartikeya Dwivedi wrote:
> Add raw CO-RE relocations that fail to resolve their target enum value.
> Place each supported and unsupported instruction form in dead code.
> Unsupported targets must fail relocation with a diagnostic even when they
> are unreachable. Supported ALU immediates, memory accesses, and ldimm64
> instructions must still be poisoned and removed as dead code, allowing the
> program to load. Check that both halves of ldimm64 are poisoned.
> 
> Update the subprogram terminator regression to require rejection at the
> relocation step now that an exit is not accepted as a relocation target.
> Load every program without relocations first to ensure that rejection is
> caused by the relocation rather than the original instruction stream.
> 
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

...

> @@ -127,7 +149,49 @@ static void test_early_core_relo(void)
>  					       enum_id, access_str_off, 2, true);
>  		if (!ASSERT_LT(prog_fd, 0, "poisoned_load"))
>  			goto cleanup;
> -		ASSERT_HAS_SUBSTR(log, "last insn is not an exit or jmp", "poisoned_load_log");
> +		ASSERT_HAS_SUBSTR(log, unrecognized, "poisoned_load_log");
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(tests); i++) {
> +		struct bpf_insn insns[] = {
> +			BPF_MOV64_IMM(BPF_REG_0, 0),
> +			BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
> +			tests[i].insns[0],
> +			BPF_MOV64_IMM(BPF_REG_0, 0),
> +			BPF_MOV64_IMM(BPF_REG_0, 0),
> +			BPF_EXIT_INSN(),
> +		};
> +		bool is_ldimm64 = insns[2].code == (BPF_LD | BPF_DW | BPF_IMM);
> +		int insn_cnt = ARRAY_SIZE(insns);
> +
> +		if (!test__start_subtest(tests[i].name))
> +			continue;
> +		if (is_ldimm64) {
> +			insns[3] = tests[i].insns[1];
> +		} else {
> +			insns[1].off = 1;
> +			insns[4] = BPF_EXIT_INSN();
> +			insn_cnt--;
> +		}

And I had a nit regarding the `if` and `insn_cnt` not being necessary.
But again, if there are no other changes to this series let's land as-is.

...

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 10/11] libbpf: Reject truncated ldimm64 CO-RE relocations
  2026-09-14 22:25 ` [PATCH bpf v4 10/11] libbpf: Reject truncated ldimm64 CO-RE relocations Kumar Kartikeya Dwivedi
@ 2026-09-16 18:37   ` Eduard Zingerman
  0 siblings, 0 replies; 24+ messages in thread
From: Eduard Zingerman @ 2026-09-16 18:37 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Sashiko, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Emil Tsalapatis, Nicholas Carlini, kkd, kernel-team

On Tue, 2026-09-15 at 00:25 +0200, Kumar Kartikeya Dwivedi wrote:
> CO-RE relocation of an ldimm64 instruction operates on two instruction
> slots. The original ldimm64 support checked that the second slot existed
> only on the resolved relocation path, leaving unresolved relocation
> poisoning able to write past the instruction allocation. When CO-RE logic
> was moved to the shared relocation implementation, even the resolved-path
> check was lost because bpf_core_patch_insn() no longer received the
> program length.
> 
> A malformed BPF ELF can therefore end a function after the first ldimm64
> slot and attach a CO-RE relocation to it. libbpf allocates the instruction
> array according to the function symbol size. bpf_core_patch_insn() then
> reads the absent second slot while validating a resolved relocation or
> writes a poison instruction past the allocation for an unresolved one.
> 
> Pass the total instruction count to the shared patch helper and reject a
> terminal ldimm64 before either relocation result is handled. Thread the
> count through the in-kernel caller as well, since it compiles the same
> shared relocation implementation.
> 
> Fixes: eacaaed784e2 ("libbpf: Implement enum value-based CO-RE relocations")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Link: https://lore.kernel.org/20260914140852.03DA21F0089B@smtp.kernel.org
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---

It's a bit ugly to thread insn_cnt so deep only for this check.
When talking about verifier fix, the change could be as simple as:

  if (prog->insnsi[prog->len - 1].code == (BPF_LD | BPF_IMM | BPF_DW)) {
	verbose(env, "invalid bpf_ld_imm64 insn\n");
	return -EINVAL;
  }

Somewhere at the beginning of bpf_check().
Wdyt?

...

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 11/11] selftests/bpf: Add truncated CO-RE ldimm64 test
  2026-09-14 22:25 ` [PATCH bpf v4 11/11] selftests/bpf: Add truncated CO-RE ldimm64 test Kumar Kartikeya Dwivedi
  2026-09-14 23:15   ` bot+bpf-ci
@ 2026-09-16 18:43   ` Eduard Zingerman
  2026-09-16 19:02     ` Kumar Kartikeya Dwivedi
  1 sibling, 1 reply; 24+ messages in thread
From: Eduard Zingerman @ 2026-09-16 18:43 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Emil Tsalapatis, Nicholas Carlini, kkd, kernel-team

On Tue, 2026-09-15 at 00:25 +0200, Kumar Kartikeya Dwivedi wrote:
> Exercise CO-RE relocation of an ldimm64 instruction whose second slot is
> missing. Build a valid BPF object with a compiler-emitted relocation, then
> truncate the program symbol to retain only the first instruction slot.
> 
> Without bounds validation, libbpf reads or writes beyond the exact-sized
> instruction allocation, depending on whether the relocation resolves.
> Exercise both paths and require bpf_object__prepare() to reject the
> malformed object with a precise diagnostic.
> 
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---

Tbh, assuming we are going for simple ldimm64 check at the start of
the program verification sequence, I'd just add one bogus test case for
test_verifier runner under tools/testing/selftests/bpf/verifier/*.c.
That would be something like 5 lines of code.

...

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 11/11] selftests/bpf: Add truncated CO-RE ldimm64 test
  2026-09-16 18:43   ` Eduard Zingerman
@ 2026-09-16 19:02     ` Kumar Kartikeya Dwivedi
  0 siblings, 0 replies; 24+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 19:02 UTC (permalink / raw)
  To: Eduard Zingerman, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Emil Tsalapatis, Nicholas Carlini, kkd, kernel-team

On Wed Sep 16, 2026 at 8:43 PM CEST, Eduard Zingerman wrote:
> On Tue, 2026-09-15 at 00:25 +0200, Kumar Kartikeya Dwivedi wrote:
>> Exercise CO-RE relocation of an ldimm64 instruction whose second slot is
>> missing. Build a valid BPF object with a compiler-emitted relocation, then
>> truncate the program symbol to retain only the first instruction slot.
>>
>> Without bounds validation, libbpf reads or writes beyond the exact-sized
>> instruction allocation, depending on whether the relocation resolves.
>> Exercise both paths and require bpf_object__prepare() to reject the
>> malformed object with a precise diagnostic.
>>
>> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
>> ---
>
> Tbh, assuming we are going for simple ldimm64 check at the start of
> the program verification sequence, I'd just add one bogus test case for
> test_verifier runner under tools/testing/selftests/bpf/verifier/*.c.
> That would be something like 5 lines of code.
>

Ok, I will rework the last fix. But for now, if there are no other concerns, you
could skip the final two patches when applying. Otherwise will respin the stack
with corrections.

> ...


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH bpf v4 09/11] selftests/bpf: Check callback map value lock identity
  2026-09-14 22:25 ` [PATCH bpf v4 09/11] selftests/bpf: Check callback map value lock identity Kumar Kartikeya Dwivedi
@ 2026-09-16 19:04   ` Eduard Zingerman
  0 siblings, 0 replies; 24+ messages in thread
From: Eduard Zingerman @ 2026-09-16 19:04 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Emil Tsalapatis, Nicholas Carlini, kkd, kernel-team

On Tue, 2026-09-15 at 00:25 +0200, Kumar Kartikeya Dwivedi wrote:
> Add a verifier test which retains a map value from an outer callback and
> then acquires a lock through an inner callback value before attempting to
> release the outer callback value. Both values can denote different elements,
> so the verifier must reject the mismatched unlock.
> 
> Also exercise callbacks reached through two inner-map lookups. The lookup
> results share inner_map_meta but may refer to different one-element
> arrays, so their callback values must retain distinct lock identities.
> 
> Keep nested and one-element array callbacks as positive controls for
> balanced locking through one callback argument.
> 
> For timer, workqueue, and task-work callbacks, lock and unlock the value,
> then pass it to bpf_this_cpu_ptr() to expose its ID in the verifier error
> log. Require a nonzero ID, following the existing lock identity tests.
> Balanced locking alone would still succeed if a constructor stopped
> assigning an ID.
> 
> Extend the existing spin_lock test table and reuse its array and inner-map
> fixtures to keep the callback cases alongside the other lock identity tests.
> 
> Update the nested callback reference-leak expectation for the extra
> callback value ID.
> 
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---

If we are going to respin because of the ldimm64 patch,
let's also trim these tests. I think that only lock_different_value()
test cases are necessary.

...

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-09-16 19:04 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-14 22:24 [PATCH bpf v4 00/11] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
2026-09-14 22:24 ` [PATCH bpf v4 01/11] bpf: Make post-verification instruction rewrites killable Kumar Kartikeya Dwivedi
2026-09-14 22:24 ` [PATCH bpf v4 02/11] bpf: Preserve packet pointer class displacement in regsafe() Kumar Kartikeya Dwivedi
2026-09-14 22:25 ` [PATCH bpf v4 03/11] selftests/bpf: Test packet pointer class displacement pruning Kumar Kartikeya Dwivedi
2026-09-14 22:25 ` [PATCH bpf v4 04/11] bpf: Apply CO-RE relocations before subprogram validation Kumar Kartikeya Dwivedi
2026-09-14 23:15   ` bot+bpf-ci
2026-09-16 18:22   ` Eduard Zingerman
2026-09-14 22:25 ` [PATCH bpf v4 05/11] selftests/bpf: Test early in-kernel CO-RE relocation Kumar Kartikeya Dwivedi
2026-09-14 23:28   ` bot+bpf-ci
2026-09-16 18:23   ` Eduard Zingerman
2026-09-16 18:26     ` Kumar Kartikeya Dwivedi
2026-09-14 22:25 ` [PATCH bpf v4 06/11] bpf: Restrict CO-RE poisoning to relocatable instructions Kumar Kartikeya Dwivedi
2026-09-14 23:28   ` bot+bpf-ci
2026-09-14 22:25 ` [PATCH bpf v4 07/11] selftests/bpf: Test CO-RE instruction poisoning restrictions Kumar Kartikeya Dwivedi
2026-09-16 18:27   ` Eduard Zingerman
2026-09-14 22:25 ` [PATCH bpf v4 08/11] bpf: Assign lock identity to callback map values Kumar Kartikeya Dwivedi
2026-09-14 22:25 ` [PATCH bpf v4 09/11] selftests/bpf: Check callback map value lock identity Kumar Kartikeya Dwivedi
2026-09-16 19:04   ` Eduard Zingerman
2026-09-14 22:25 ` [PATCH bpf v4 10/11] libbpf: Reject truncated ldimm64 CO-RE relocations Kumar Kartikeya Dwivedi
2026-09-16 18:37   ` Eduard Zingerman
2026-09-14 22:25 ` [PATCH bpf v4 11/11] selftests/bpf: Add truncated CO-RE ldimm64 test Kumar Kartikeya Dwivedi
2026-09-14 23:15   ` bot+bpf-ci
2026-09-16 18:43   ` Eduard Zingerman
2026-09-16 19:02     ` Kumar Kartikeya Dwivedi

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.