All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place
@ 2026-08-11 13:15 Daniel Borkmann
  2026-08-11 13:15 ` [PATCH bpf-next v3 2/6] bpf, riscv: Clear fetch destination on faulting arena atomic Daniel Borkmann
                   ` (6 more replies)
  0 siblings, 7 replies; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-11 13:15 UTC (permalink / raw)
  To: memxor; +Cc: eddyz87, puranjay, bpf

check_atomic_rmw() open codes the mapping from a BPF_ATOMIC to the register
it reads the old value into, the BPF_STX case of insn_def_regno() open codes
the very same mapping a second time, the const folding and the liveness
transfer functions a third and a fourth time, and BPF JITs need it as well
to know which register a faulting BPF_PROBE_ATOMIC has to clear.

Add a small helper so that all of them can share it. No functional change.
The BPF_LOAD_ACQ case is there for the JITs, which do walk all instruction
classes. const_reg_xfer() loses its explicit BPF_ATOMIC mode test since the
helper checks class and mode itself; the BPF_PROBE_ATOMIC it additionally
accepts cannot be seen there as it is only set from bpf_do_misc_fixups(),
that is, after const folding has run. arg_track_xfer() keeps its mode test
since that also guards the stack clearing next to it.

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
  v2 -> v3:
   - Also use the helper for const_fold and liveness (bpf ci)
   - Drop the return value comment in verifier since the helper
     comment already covers it (Jakub)
  v1 -> v2:
   - also convert insn_def_regno (Eduard, sashiko)

 include/linux/filter.h  | 24 ++++++++++++++++++++++++
 kernel/bpf/const_fold.c | 11 +++--------
 kernel/bpf/fixups.c     | 11 +----------
 kernel/bpf/liveness.c   |  9 +++------
 kernel/bpf/verifier.c   | 13 ++-----------
 5 files changed, 33 insertions(+), 35 deletions(-)

diff --git a/include/linux/filter.h b/include/linux/filter.h
index 4edba8182db1..15d83684c6e9 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -414,6 +414,30 @@ static inline bool bpf_atomic_is_load_acq(const struct bpf_insn *insn)
 	       insn->imm == BPF_LOAD_ACQ;
 }
 
+/*
+ * Given an instruction @insn, return the number of the BPF register that a
+ * BPF_ATOMIC reads the value at its memory operand into, or -1 if there is
+ * no such register. That is the register a BPF_PROBE_ATOMIC has to clear when
+ * the access faults. Like bpf_atomic_is_load_acq(), @insn is not assumed to
+ * be a BPF_ATOMIC here.
+ */
+static inline int bpf_atomic_load_reg(const struct bpf_insn *insn)
+{
+	if (BPF_CLASS(insn->code) != BPF_STX ||
+	    (BPF_MODE(insn->code) != BPF_ATOMIC &&
+	     BPF_MODE(insn->code) != BPF_PROBE_ATOMIC))
+		return -1;
+
+	switch (insn->imm) {
+	case BPF_LOAD_ACQ:
+		return insn->dst_reg;
+	case BPF_CMPXCHG:
+		return BPF_REG_0;
+	default:
+		return (insn->imm & BPF_FETCH) ? insn->src_reg : -1;
+	}
+}
+
 /* Memory store, *(uint *) (dst_reg + off16) = imm32 */
 
 #define BPF_ST_MEM(SIZE, DST, OFF, IMM)				\
diff --git a/kernel/bpf/const_fold.c b/kernel/bpf/const_fold.c
index b2a19acadb91..4cf120c7b2cb 100644
--- a/kernel/bpf/const_fold.c
+++ b/kernel/bpf/const_fold.c
@@ -199,14 +199,9 @@ static void const_reg_xfer(struct bpf_verifier_env *env, struct const_arg_info *
 			ci_out[r] = unknown;
 		break;
 	case BPF_STX:
-		if (mode != BPF_ATOMIC)
-			break;
-		if (insn->imm == BPF_CMPXCHG)
-			ci_out[BPF_REG_0] = unknown;
-		else if (insn->imm == BPF_LOAD_ACQ)
-			*dst = unknown;
-		else if (insn->imm & BPF_FETCH)
-			*src = unknown;
+		r = bpf_atomic_load_reg(insn);
+		if (r >= 0)
+			ci_out[r] = unknown;
 		break;
 	}
 }
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 661e2d13a604..c4bd70befbb5 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -49,16 +49,7 @@ static int insn_def_regno(const struct bpf_insn *insn)
 	case BPF_ST:
 		return -1;
 	case BPF_STX:
-		if (BPF_MODE(insn->code) == BPF_ATOMIC ||
-		    BPF_MODE(insn->code) == BPF_PROBE_ATOMIC) {
-			if (insn->imm == BPF_CMPXCHG)
-				return BPF_REG_0;
-			else if (insn->imm == BPF_LOAD_ACQ)
-				return insn->dst_reg;
-			else if (insn->imm & BPF_FETCH)
-				return insn->src_reg;
-		}
-		return -1;
+		return bpf_atomic_load_reg(insn);
 	default:
 		return insn->dst_reg;
 	}
diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c
index ef9a5a922887..1c997aeba6fa 100644
--- a/kernel/bpf/liveness.c
+++ b/kernel/bpf/liveness.c
@@ -1209,12 +1209,9 @@ static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn,
 				clear_stack_for_all_offs(insn, at_out, insn->dst_reg,
 							 at_stack_out, sz);
 
-			if (insn->imm == BPF_CMPXCHG)
-				at_out[BPF_REG_0] = none;
-			else if (insn->imm == BPF_LOAD_ACQ)
-				*dst = none;
-			else if (insn->imm & BPF_FETCH)
-				*src = none;
+			r = bpf_atomic_load_reg(insn);
+			if (r >= 0)
+				at_out[r] = none;
 		}
 	} else if (class == BPF_ST && BPF_MODE(insn->code) == BPF_MEM) {
 		u32 sz = bpf_size_to_bytes(BPF_SIZE(insn->code));
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index add3affc5703..61ef43325c6f 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6485,21 +6485,12 @@ static int check_atomic_rmw(struct bpf_verifier_env *env,
 		return -EACCES;
 	}
 
-	if (insn->imm & BPF_FETCH) {
-		if (insn->imm == BPF_CMPXCHG)
-			load_reg = BPF_REG_0;
-		else
-			load_reg = insn->src_reg;
-
+	load_reg = bpf_atomic_load_reg(insn);
+	if (load_reg >= 0) {
 		/* check and record load of old value */
 		err = check_reg_arg(env, load_reg, DST_OP);
 		if (err)
 			return err;
-	} else {
-		/* This instruction accesses a memory location but doesn't
-		 * actually load it into a register.
-		 */
-		load_reg = -1;
 	}
 
 	dst_reg = cur_regs(env) + insn->dst_reg;
-- 
2.43.0


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

* [PATCH bpf-next v3 2/6] bpf, riscv: Clear fetch destination on faulting arena atomic
  2026-08-11 13:15 [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Daniel Borkmann
@ 2026-08-11 13:15 ` Daniel Borkmann
  2026-08-11 13:15 ` [PATCH bpf-next v3 3/6] bpf, x86: " Daniel Borkmann
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-11 13:15 UTC (permalink / raw)
  To: memxor; +Cc: eddyz87, puranjay, bpf, Pu Lehui

A RMW atomic on an arena pointer is converted to BPF_PROBE_ATOMIC and
gets an exception table entry, but that entry records no destination
register to clear unless the instruction is a load-acquire today. That
is right for a plain BPF_{ADD,AND,OR,XOR}, which only writes memory,
but an RMW carrying BPF_FETCH also reads the old value into a register:
src_reg for BPF_{ADD,AND,OR,XOR} | BPF_FETCH and BPF_XCHG, and r0 for
BPF_CMPXCHG. emit_atomic_rmw() emits it that way, e.g.:

  [...]
  case BPF_XCHG:
          ctx->ex_insn_off = ctx->ninsns;
          emit(is64 ? rv_amoswap_d(rs, rs, rd, 1, 1) :
               rv_amoswap_w(rs, rs, rd, 1, 1), ctx);
  [...]

Thus, a fault over an unmapped arena page ex_handler_bpf() jumps over
the access but leaves rs untouched, and the program resumes with
whatever it held before the atomic instead of the 0 that every other
BPF_PROBE_* access delivers. Fill the exception table entry in from
bpf_atomic_load_reg(), which returns the BPF register an atomic reads
the memory operand into or -1 when it has none. A load-acquire ends up
with the same register it gets today, it just goes through the helper.
Unlike x86-64 and arm64, riscv64 does not report arena violations from
its exception handler, so there is no access direction to correct here,
only the missing register clear.

Fixes: fb7cefabae81 ("riscv, bpf: Add support arena atomics for RV64")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Reviewed-by: Pu Lehui <pulehui@huawei.com>
---
 arch/riscv/net/bpf_jit_comp64.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
index 6b9972b07c1b..2504df1fa111 100644
--- a/arch/riscv/net/bpf_jit_comp64.c
+++ b/arch/riscv/net/bpf_jit_comp64.c
@@ -1992,10 +1992,19 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 			ret = emit_atomic_rmw(rd, rs, insn, ctx);
 
 		/* ret can be 1 (skip-zext); extable entry still needs to be added */
-		if (ret >= 0)
-			ret = add_exception_handler(insn,
-				bpf_atomic_is_load_acq(insn) ? rd : REG_DONT_CLEAR_MARKER,
-				ctx) ?: ret;
+		if (ret >= 0) {
+			/*
+			 * A load-acquire reads into dst_reg, and a read-modify-write
+			 * carrying BPF_FETCH reads the old value into src_reg, or into
+			 * r0 for a BPF_CMPXCHG. Clear that register on fault, the
+			 * remaining atomics have no destination register.
+			 */
+			int load_reg = bpf_atomic_load_reg(insn);
+
+			ret = add_exception_handler(insn, load_reg < 0 ?
+					REG_DONT_CLEAR_MARKER : regmap[load_reg],
+					ctx) ?: ret;
+		}
 
 		if (ret)
 			return ret;
-- 
2.43.0


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

* [PATCH bpf-next v3 3/6] bpf, x86: Clear fetch destination on faulting arena atomic
  2026-08-11 13:15 [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Daniel Borkmann
  2026-08-11 13:15 ` [PATCH bpf-next v3 2/6] bpf, riscv: Clear fetch destination on faulting arena atomic Daniel Borkmann
@ 2026-08-11 13:15 ` Daniel Borkmann
  2026-08-11 13:15 ` [PATCH bpf-next v3 4/6] bpf, arm64: " Daniel Borkmann
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-11 13:15 UTC (permalink / raw)
  To: memxor; +Cc: eddyz87, puranjay, bpf

populate_extable() encodes "there is no destination register to clear" as
DONT_CLEAR in the DST_REG field of the exception table metadata, and later
ex_handler_bpf() then reuses that very value to derive the direction it
reports the fault with is_write = (reg == DONT_CLEAR). The two coincide
for a plain load or store, but not for a RMW carrying BPF_FETCH. Such an
atomic writes memory, so it has to be reported as a WRITE, and it also reads
the old value into a register, src_reg for BPF_ADD | BPF_FETCH and BPF_XCHG,
r0 for BPF_CMPXCHG, so that register has to be cleared on fault. A single
DONT_CLEAR cannot say both, and the store branch picks it unconditionally:

  [...]
  } else {
          arena_reg = reg2pt_regs[dst_reg];
          fixup_reg = DONT_CLEAR;
  }
  [...]

The reported direction is therefore right, but on a fault over an unmapped
arena page the fetch destination keeps whatever it held before the atomic,
where every other BPF_PROBE_* access delivers 0. Give the metadata its own
ARENA_WRITE bit so that the reported direction no longer depends on whether
there is a register to clear, and fill DST_REG in from bpf_atomic_load_reg().
BPF_{AND,OR,XOR} | BPF_FETCH need no handling here, bpf_jit_supports_insn()
already rejects those in the arena.

Fixes: d503a04f8bc0 ("bpf: Add support for certain atomics in bpf_arena to x86 JIT")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Reviewed-by: Puranjay Mohan <puranjay@kernel.org>
---
 arch/x86/net/bpf_jit_comp.c | 35 +++++++++++++++++++++++++++--------
 1 file changed, 27 insertions(+), 8 deletions(-)

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 8dddb5d7af21..d920772af7d5 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1473,17 +1473,20 @@ static int emit_atomic_ld_st_index(u8 **pprog, u32 atomic_op, u32 size,
  *
  * Bit layout of `fixup` (32-bit):
  *
- * +-----------+--------+-----------+---------+----------+
- * | 31        | 30-24  |   23-16   |   15-8  |    7-0   |
- * |           |        |           |         |          |
- * | ARENA_ACC | Unused | ARENA_REG | DST_REG | INSN_LEN |
- * +-----------+--------+-----------+---------+----------+
+ * +-----------+-------------+--------+-----------+---------+----------+
+ * | 31        | 30          | 29-24  |   23-16   |   15-8  |    7-0   |
+ * |           |             |        |           |         |          |
+ * | ARENA_ACC | ARENA_WRITE | Unused | ARENA_REG | DST_REG | INSN_LEN |
+ * +-----------+-------------+--------+-----------+---------+----------+
  *
  * - INSN_LEN (8 bits): Length of faulting insn (max x86 insn = 15 bytes (fits in 8 bits)).
  * - DST_REG  (8 bits): Offset of dst_reg from reg2pt_regs[] (max offset = 112 (fits in 8 bits)).
- *                      This is set to DONT_CLEAR if the insn is a store.
+ *                      This is set to DONT_CLEAR if the insn does not read into a register.
  * - ARENA_REG (8 bits): Offset of the register that is used to calculate the
  *                       address for load/store when accessing the arena region.
+ * - ARENA_WRITE (1 bit): This bit is set when the faulting instruction wrote to the arena region.
+ *                        It is independent of DST_REG, since a read-modify-write both writes to
+ *                        memory and reads the old value into a register.
  * - ARENA_ACCESS (1 bit): This bit is set when the faulting instruction accessed the arena region.
  *
  * Bit layout of `data` (32-bit):
@@ -1502,6 +1505,7 @@ static int emit_atomic_ld_st_index(u8 **pprog, u32 atomic_op, u32 size,
 #define FIXUP_INSN_LEN_MASK	GENMASK(7, 0)
 #define FIXUP_REG_MASK		GENMASK(15, 8)
 #define FIXUP_ARENA_REG_MASK	GENMASK(23, 16)
+#define FIXUP_ARENA_WRITE	BIT(30)
 #define FIXUP_ARENA_ACCESS	BIT(31)
 #define DATA_ARENA_OFFSET_MASK	GENMASK(31, 16)
 
@@ -1510,7 +1514,7 @@ bool ex_handler_bpf(const struct exception_table_entry *x, struct pt_regs *regs)
 	u32 reg = FIELD_GET(FIXUP_REG_MASK, x->fixup);
 	u32 insn_len = FIELD_GET(FIXUP_INSN_LEN_MASK, x->fixup);
 	bool is_arena = !!(x->fixup & FIXUP_ARENA_ACCESS);
-	bool is_write = (reg == DONT_CLEAR);
+	bool is_write = !!(x->fixup & FIXUP_ARENA_WRITE);
 	unsigned long addr;
 	s16 off;
 	u32 arena_reg;
@@ -2348,6 +2352,7 @@ st:			insn_off = insn->off;
 				struct exception_table_entry *ex;
 				u8 *_insn = image + proglen + (start_of_ldx - temp);
 				u32 arena_reg, fixup_reg;
+				bool is_write;
 				s64 delta;
 
 				if (!bpf_prog->aux->extable)
@@ -2384,15 +2389,29 @@ st:			insn_off = insn->off;
 				    bpf_atomic_is_load_acq(insn)) {
 					arena_reg = reg2pt_regs[src_reg];
 					fixup_reg = reg2pt_regs[dst_reg];
+					is_write = false;
 				} else {
+					/*
+					 * A store has no destination register to clear,
+					 * except for a read-modify-write with BPF_FETCH,
+					 * which also reads the old value into src_reg, or
+					 * into r0 for a BPF_CMPXCHG. Either way the access
+					 * is still reported as a write.
+					 */
+					int load_reg = bpf_atomic_load_reg(insn);
+
 					arena_reg = reg2pt_regs[dst_reg];
-					fixup_reg = DONT_CLEAR;
+					fixup_reg = load_reg < 0 ? DONT_CLEAR :
+						    reg2pt_regs[load_reg];
+					is_write = true;
 				}
 
 				ex->fixup = FIELD_PREP(FIXUP_INSN_LEN_MASK, prog - start_of_ldx) |
 					    FIELD_PREP(FIXUP_ARENA_REG_MASK, arena_reg) |
 					    FIELD_PREP(FIXUP_REG_MASK, fixup_reg);
 				ex->fixup |= FIXUP_ARENA_ACCESS;
+				if (is_write)
+					ex->fixup |= FIXUP_ARENA_WRITE;
 
 				ex->data |= FIELD_PREP(DATA_ARENA_OFFSET_MASK, insn->off);
 			}
-- 
2.43.0


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

* [PATCH bpf-next v3 4/6] bpf, arm64: Clear fetch destination on faulting arena atomic
  2026-08-11 13:15 [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Daniel Borkmann
  2026-08-11 13:15 ` [PATCH bpf-next v3 2/6] bpf, riscv: Clear fetch destination on faulting arena atomic Daniel Borkmann
  2026-08-11 13:15 ` [PATCH bpf-next v3 3/6] bpf, x86: " Daniel Borkmann
@ 2026-08-11 13:15 ` Daniel Borkmann
  2026-08-12 12:18   ` Puranjay Mohan
  2026-08-11 13:15 ` [PATCH bpf-next v3 5/6] bpf, s390: " Daniel Borkmann
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-11 13:15 UTC (permalink / raw)
  To: memxor; +Cc: eddyz87, puranjay, bpf

Same problem as on x86-64: add_exception_handler() folds "there is no
destination register to clear" and "this is a store" into one DONT_CLEAR
value ...

  if (BPF_CLASS(insn->code) != BPF_LDX && !bpf_atomic_is_load_acq(insn))
          dst_reg = DONT_CLEAR;

... which ex_handler_bpf() then reads back as the access direction:

  bool is_write = (dst_reg == DONT_CLEAR);

A RMW carrying BPF_FETCH is both. emit_lse_atomic() reads the old value
into src_reg for BPF_{ADD,AND,OR,XOR} | BPF_FETCH and BPF_XCHG, and into
r0 for BPF_CMPXCHG, so a fault over an unmapped arena page is correctly
reported as a WRITE but leaves that register holding a stale value instead
of the 0 that every other BPF_PROBE_* access delivers. Same as on x86-64,
add a separate ARENA_WRITE bit for the direction.

FIXUP_REG is now filled in by the callers of add_exception_handler(), the
BPF_PROBE_ATOMIC one deriving it from bpf_atomic_load_reg(), so that the
helper only has to determine the direction. This is how the riscv64 JIT
already does it, and it stops the two store callers from handing in a
dst_reg that was only going to be overwritten with DONT_CLEAR anyway.

Fixes: e612b5c1d3ee ("bpf, arm64: Add support for lse atomics in bpf_arena")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Cc: Puranjay Mohan <puranjay@kernel.org>
---
 v1 -> v2:
   - move bpf_atomic_load_reg into build_insn (Eduard)

 arch/arm64/net/bpf_jit_comp.c | 44 ++++++++++++++++++++++++-----------
 1 file changed, 30 insertions(+), 14 deletions(-)

diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index d14d297ebb96..74b4083791da 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -1082,23 +1082,27 @@ static void build_epilogue(struct jit_ctx *ctx, bool was_classic)
  *
  * Bit layout of `fixup` (32-bit):
  *
- * +-----------+--------+-----------+-----------+----------+
- * |   31-27   | 26-22  |     21    |   20-16   |   15-0   |
- * |           |        |           |           |          |
- * | FIXUP_REG | Unused | ARENA_ACC | ARENA_REG |  OFFSET  |
- * +-----------+--------+-----------+-----------+----------+
+ * +-----------+--------+-------------+-----------+-----------+----------+
+ * |   31-27   | 26-23  |      22     |     21    |   20-16   |   15-0   |
+ * |           |        |             |           |           |          |
+ * | FIXUP_REG | Unused | ARENA_WRITE | ARENA_ACC | ARENA_REG |  OFFSET  |
+ * +-----------+--------+-------------+-----------+-----------+----------+
  *
  * - OFFSET (16 bits): Offset used to compute address for Load/Store instruction.
  * - ARENA_REG (5 bits): Register that is used to calculate the address for load/store when
  *                       accessing the arena region.
  * - ARENA_ACCESS (1 bit): This bit is set when the faulting instruction accessed the arena region.
+ * - ARENA_WRITE (1 bit): This bit is set when the faulting instruction wrote to the arena region.
+ *                        It is independent of FIXUP_REG, since a read-modify-write both writes to
+ *                        memory and reads the old value into a register.
  * - FIXUP_REG (5 bits): Destination register for the load instruction (cleared on fault) or set to
- *                       DONT_CLEAR if it is a store instruction.
+ *                       DONT_CLEAR if the instruction does not read into a register.
  */
 
 #define BPF_FIXUP_OFFSET_MASK      GENMASK(15, 0)
 #define BPF_FIXUP_ARENA_REG_MASK   GENMASK(20, 16)
 #define BPF_ARENA_ACCESS           BIT(21)
+#define BPF_ARENA_WRITE            BIT(22)
 #define BPF_FIXUP_REG_MASK	GENMASK(31, 27)
 #define DONT_CLEAR 5 /* Unused ARM64 register from BPF's POV */
 
@@ -1109,7 +1113,7 @@ bool ex_handler_bpf(const struct exception_table_entry *ex,
 	s16 off = FIELD_GET(BPF_FIXUP_OFFSET_MASK, ex->fixup);
 	int arena_reg = FIELD_GET(BPF_FIXUP_ARENA_REG_MASK, ex->fixup);
 	bool is_arena = !!(ex->fixup & BPF_ARENA_ACCESS);
-	bool is_write = (dst_reg == DONT_CLEAR);
+	bool is_write = !!(ex->fixup & BPF_ARENA_WRITE);
 	unsigned long addr;
 
 	if (is_arena) {
@@ -1132,7 +1136,7 @@ static int add_exception_handler(const struct bpf_insn *insn,
 {
 	off_t ins_offset;
 	s16 off = insn->off;
-	bool is_arena;
+	bool is_arena, is_write;
 	int arena_reg;
 	unsigned long pc;
 	struct exception_table_entry *ex;
@@ -1181,15 +1185,18 @@ static int add_exception_handler(const struct bpf_insn *insn,
 	/*
 	 * A load-acquire is of BPF_STX class, but reads from src_reg into
 	 * dst_reg like a BPF_LDX does, hence it must not be treated as a store
-	 * here.
+	 * here. A read-modify-write carrying BPF_FETCH is reported as a write
+	 * even though it does have a register to clear, see the callers.
 	 */
-	if (BPF_CLASS(insn->code) != BPF_LDX && !bpf_atomic_is_load_acq(insn))
-		dst_reg = DONT_CLEAR;
+	is_write = BPF_CLASS(insn->code) != BPF_LDX &&
+		   !bpf_atomic_is_load_acq(insn);
 
 	ex->fixup = FIELD_PREP(BPF_FIXUP_REG_MASK, dst_reg);
 
 	if (is_arena) {
 		ex->fixup |= BPF_ARENA_ACCESS;
+		if (is_write)
+			ex->fixup |= BPF_ARENA_WRITE;
 		/*
 		 * insn->src_reg/dst_reg holds the address in the arena region with upper 32-bits
 		 * being zero because of a preceding addr_space_cast(r<n>, 0x0, 0x1) instruction.
@@ -1889,7 +1896,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 			break;
 		}
 
-		ret = add_exception_handler(insn, ctx, dst);
+		ret = add_exception_handler(insn, ctx, DONT_CLEAR);
 		if (ret)
 			return ret;
 		break;
@@ -1956,7 +1963,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 			break;
 		}
 
-		ret = add_exception_handler(insn, ctx, dst);
+		ret = add_exception_handler(insn, ctx, DONT_CLEAR);
 		if (ret)
 			return ret;
 		break;
@@ -1979,7 +1986,16 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 			return ret;
 
 		if (BPF_MODE(insn->code) == BPF_PROBE_ATOMIC) {
-			ret = add_exception_handler(insn, ctx, dst);
+			/*
+			 * A load-acquire reads into dst_reg, and a read-modify-write
+			 * carrying BPF_FETCH reads the old value into src_reg, or into
+			 * r0 for a BPF_CMPXCHG. Clear that register on fault, the
+			 * remaining atomics have no destination register.
+			 */
+			int load_reg = bpf_atomic_load_reg(insn);
+
+			ret = add_exception_handler(insn, ctx, load_reg < 0 ?
+						    DONT_CLEAR : bpf2a64[load_reg]);
 			if (ret)
 				return ret;
 		}
-- 
2.43.0


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

* [PATCH bpf-next v3 5/6] bpf, s390: Clear fetch destination on faulting arena atomic
  2026-08-11 13:15 [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Daniel Borkmann
                   ` (2 preceding siblings ...)
  2026-08-11 13:15 ` [PATCH bpf-next v3 4/6] bpf, arm64: " Daniel Borkmann
@ 2026-08-11 13:15 ` Daniel Borkmann
  2026-08-12  9:40   ` Ilya Leoshkevich
  2026-08-11 13:16 ` [PATCH bpf-next v3 6/6] selftests/bpf: Add arena fault tests for atomics with fetch Daniel Borkmann
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-11 13:15 UTC (permalink / raw)
  To: memxor; +Cc: eddyz87, puranjay, bpf, Ilya Leoshkevich

Same missing register clear as on riscv64. A RMW atomic on an arena pointer
is converted to BPF_PROBE_ATOMIC and gets an exception table entry, but
bpf_jit_probe_atomic_pre() only fills in the arena base and the probe
offset, leaving probe->reg at the -1 that bpf_jit_probe_init() set, which
bpf_jit_probe_post() writes into the entry and ex_handler_bpf() then reads
back as "there is nothing to clear".

That is right for a plain BPF_{ADD,AND,OR,XOR}, which only writes memory,
but an RMW carrying BPF_FETCH also reads the old value into a register:
src_reg for BPF_{ADD,AND,OR,XOR} | BPF_FETCH and BPF_XCHG, and r0 for
BPF_CMPXCHG. So on a fault over an unmapped arena page the program resumes
at the landing pad with whatever that register held before the atomic
instead of the 0 that every other BPF_PROBE_* access delivers.

Fill probe->reg in from bpf_atomic_load_reg(). Unlike x86-64 and arm64,
s390x does not report arena violations from its exception handler, so there
is no access direction to correct here, only the missing register clear.

Fixes: 2f9469484a3b ("s390/bpf: Support arena atomics")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Cc: Ilya Leoshkevich <iii@linux.ibm.com>
---
 arch/s390/net/bpf_jit_comp.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c
index b60877478b45..c46872b071ce 100644
--- a/arch/s390/net/bpf_jit_comp.c
+++ b/arch/s390/net/bpf_jit_comp.c
@@ -774,6 +774,8 @@ static void bpf_jit_probe_atomic_pre(struct bpf_jit *jit,
 				     struct bpf_insn *insn,
 				     struct bpf_jit_probe *probe)
 {
+	int load_reg;
+
 	if (BPF_MODE(insn->code) != BPF_PROBE_ATOMIC)
 		return;
 
@@ -783,6 +785,14 @@ static void bpf_jit_probe_atomic_pre(struct bpf_jit *jit,
 	EMIT4(0xb9080000, REG_W1, insn->dst_reg);
 	probe->arena_reg = REG_W1;
 	probe->prg = jit->prg;
+	/*
+	 * A read-modify-write carrying BPF_FETCH reads the old value into
+	 * src_reg, or into r0 for a BPF_CMPXCHG. Clear that register on
+	 * fault, the remaining atomics only write memory.
+	 */
+	load_reg = bpf_atomic_load_reg(insn);
+	if (load_reg >= 0)
+		probe->reg = reg2hex[load_reg];
 }
 
 static int bpf_jit_probe_post(struct bpf_jit *jit, struct bpf_prog *fp,
@@ -1684,6 +1694,7 @@ static noinline int bpf_jit_insn(struct bpf_jit *jit, struct bpf_prog *fp,
 			if (load_probe.prg != -1) {
 				probe.prg = jit->prg;
 				probe.arena_reg = load_probe.arena_reg;
+				probe.reg = load_probe.reg;
 			}
 			loop_start = jit->prg;
 			/* 0: {csy|csg} %w0,%src,off(%arena) */
-- 
2.43.0


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

* [PATCH bpf-next v3 6/6] selftests/bpf: Add arena fault tests for atomics with fetch
  2026-08-11 13:15 [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Daniel Borkmann
                   ` (3 preceding siblings ...)
  2026-08-11 13:15 ` [PATCH bpf-next v3 5/6] bpf, s390: " Daniel Borkmann
@ 2026-08-11 13:16 ` Daniel Borkmann
  2026-08-11 14:27   ` bot+bpf-ci
  2026-08-11 15:57   ` Puranjay Mohan
  2026-08-11 21:45 ` [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Eduard Zingerman
  2026-08-12 17:40 ` patchwork-bot+netdevbpf
  6 siblings, 2 replies; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-11 13:16 UTC (permalink / raw)
  To: memxor; +Cc: eddyz87, puranjay, bpf

Add stream_arena_xchg_fault and stream_arena_cmpxchg_fault next to the
existing read, write and load-acquire fault tests, covering the two
places a read-modify-write can deposit the old value: src_reg for a
BPF_XCHG and r0 for a BPF_CMPXCHG. Both cover both halves of the JIT
bug that left the fetch destination alone when a RMW on an arena pointer
faulted:

  - the fault has to be reported as a WRITE, and at the address held by
    the destination register, which __stderr() and test_address() check
  - the register receiving the fetched value has to be cleared by the
    fault handler, which the programs check by poisoning it before the
    atomic and returning it, so __retval(0) fails if it is left untouched

The __stderr() annotation can only wildcard the faulting address since
the arena base is not known until runtime, hence the two test_address()
subtests on top, which pin it to the address held by dst_reg rather than
src_reg.

Note, the atomics are open coded since linux/filter.h cannot be included
alongside vmlinux.h.

  # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t stream
  [...]
  #464/1   stream_arena_fault_address/read_fault:OK
  #464/2   stream_arena_fault_address/write_fault:OK
  #464/3   stream_arena_fault_address/load_acquire_fault:OK
  #464/4   stream_arena_fault_address/xchg_fault:OK
  #464/5   stream_arena_fault_address/cmpxchg_fault:OK
  #464     stream_arena_fault_address:OK
  [...]
  #466/5   stream_success/stream_arena_write_fault:OK
  #466/6   stream_success/stream_arena_read_fault:OK
  #466/7   stream_success/stream_arena_load_acquire_fault:OK
  #466/8   stream_success/stream_arena_xchg_fault:OK
  #466/9   stream_success/stream_arena_cmpxchg_fault:OK
  [...]
  Summary: 4/22 PASSED, 0 SKIPPED, 0 FAILED

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
---
 v1 -> v2:
   - Updated commit msg wrt test_address structuring

 .../testing/selftests/bpf/prog_tests/stream.c |   4 +
 tools/testing/selftests/bpf/progs/stream.c    | 101 ++++++++++++++++++
 2 files changed, 105 insertions(+)

diff --git a/tools/testing/selftests/bpf/prog_tests/stream.c b/tools/testing/selftests/bpf/prog_tests/stream.c
index 15dd3ae2a84b..e4e9374309e2 100644
--- a/tools/testing/selftests/bpf/prog_tests/stream.c
+++ b/tools/testing/selftests/bpf/prog_tests/stream.c
@@ -105,6 +105,10 @@ void test_stream_arena_fault_address(void)
 		test_address(skel->progs.stream_arena_write_fault, &skel->bss->fault_addr);
 	if (test__start_subtest("load_acquire_fault"))
 		test_address(skel->progs.stream_arena_load_acquire_fault, &skel->bss->fault_addr);
+	if (test__start_subtest("xchg_fault"))
+		test_address(skel->progs.stream_arena_xchg_fault, &skel->bss->fault_addr);
+	if (test__start_subtest("cmpxchg_fault"))
+		test_address(skel->progs.stream_arena_cmpxchg_fault, &skel->bss->fault_addr);
 
 	stream__destroy(skel);
 }
diff --git a/tools/testing/selftests/bpf/progs/stream.c b/tools/testing/selftests/bpf/progs/stream.c
index cf5533e11f39..00a37933e411 100644
--- a/tools/testing/selftests/bpf/progs/stream.c
+++ b/tools/testing/selftests/bpf/progs/stream.c
@@ -229,6 +229,107 @@ int stream_arena_load_acquire_fault(void *ctx)
 	return val;
 }
 
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+__stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}")
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
+int stream_arena_xchg_fault(void *ctx)
+{
+	static const struct bpf_insn xchg_insn = {
+		.code	 = 0xc3,	/* BPF_STX | BPF_ATOMIC | BPF_W */
+		.dst_reg = 1,		/* BPF_REG_1 */
+		.src_reg = 2,		/* BPF_REG_2 */
+		.off	 = 0x7fff,
+		.imm	 = 0xe1,	/* BPF_XCHG */
+	};
+	struct bpf_arena *ptr = (void *)&arena;
+	u64 user_vm_start, val;
+
+	/*
+	 * Prevent GCC bounds warning: casting &arena to struct bpf_arena *
+	 * triggers bounds checking since the map definition is smaller than
+	 * struct bpf_arena. barrier_var() makes the pointer opaque to GCC,
+	 * preventing the bounds analysis.
+	 */
+	barrier_var(ptr);
+	user_vm_start = ptr->user_vm_start;
+	fault_addr = user_vm_start + 0x7fff;
+	bpf_addr_space_cast(user_vm_start, 0, 1);
+	/*
+	 * A read-modify-write carrying BPF_FETCH writes to memory, so the fault
+	 * has to be reported as a WRITE from the dst_reg address, but it also
+	 * reads the old value into src_reg, so the exception handler has to
+	 * clear src_reg. Poison it up front, the returned value must be 0.
+	 */
+	asm volatile (
+		"r1 = %[user_vm_start];"
+		"r2 = 1;"
+		".8byte %[xchg_insn];" /* r2 = xchg((u32 *)(r1 + 0x7fff), r2) */
+		"%[val] = r2;"
+		: [val] "=r" (val)
+		: [user_vm_start] "r" (user_vm_start),
+		  __imm_insn(xchg_insn, xchg_insn)
+		: "r1", "r2"
+	);
+	return val;
+}
+
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+__stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}")
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
+int stream_arena_cmpxchg_fault(void *ctx)
+{
+	static const struct bpf_insn cmpxchg_insn = {
+		.code	 = 0xc3,	/* BPF_STX | BPF_ATOMIC | BPF_W */
+		.dst_reg = 1,		/* BPF_REG_1 */
+		.src_reg = 2,		/* BPF_REG_2 */
+		.off	 = 0x7fff,
+		.imm	 = 0xf1,	/* BPF_CMPXCHG */
+	};
+	struct bpf_arena *ptr = (void *)&arena;
+	u64 user_vm_start, val;
+
+	/*
+	 * Prevent GCC bounds warning: casting &arena to struct bpf_arena *
+	 * triggers bounds checking since the map definition is smaller than
+	 * struct bpf_arena. barrier_var() makes the pointer opaque to GCC,
+	 * preventing the bounds analysis.
+	 */
+	barrier_var(ptr);
+	user_vm_start = ptr->user_vm_start;
+	fault_addr = user_vm_start + 0x7fff;
+	bpf_addr_space_cast(user_vm_start, 0, 1);
+	/*
+	 * Same as the exchange above, except that a BPF_CMPXCHG reads the old
+	 * value into r0 rather than into src_reg, so r0 is the register the
+	 * exception handler has to clear. It doubles as the compare value, but
+	 * the comparison never happens since the access faults first.
+	 */
+	asm volatile (
+		"r1 = %[user_vm_start];"
+		"r0 = 1;"
+		"r2 = 2;"
+		".8byte %[cmpxchg_insn];" /* r0 = cmpxchg((u32 *)(r1 + 0x7fff), r0, r2) */
+		"%[val] = r0;"
+		: [val] "=r" (val)
+		: [user_vm_start] "r" (user_vm_start),
+		  __imm_insn(cmpxchg_insn, cmpxchg_insn)
+		: "r0", "r1", "r2"
+	);
+	return val;
+}
+
 static __noinline void subprog(void)
 {
 	int __arena *addr = (int __arena *)0xdeadbeef;
-- 
2.43.0


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

* Re: [PATCH bpf-next v3 6/6] selftests/bpf: Add arena fault tests for atomics with fetch
  2026-08-11 13:16 ` [PATCH bpf-next v3 6/6] selftests/bpf: Add arena fault tests for atomics with fetch Daniel Borkmann
@ 2026-08-11 14:27   ` bot+bpf-ci
  2026-08-11 15:14     ` Daniel Borkmann
  2026-08-11 15:57   ` Puranjay Mohan
  1 sibling, 1 reply; 13+ messages in thread
From: bot+bpf-ci @ 2026-08-11 14:27 UTC (permalink / raw)
  To: daniel, memxor
  Cc: eddyz87, puranjay, bpf, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai

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

> diff --git a/tools/testing/selftests/bpf/prog_tests/stream.c b/tools/testing/selftests/bpf/prog_tests/stream.c
> --- a/tools/testing/selftests/bpf/prog_tests/stream.c
> +++ b/tools/testing/selftests/bpf/prog_tests/stream.c
> @@ -105,6 +105,10 @@ void test_stream_arena_fault_address(void)
>  		test_address(skel->progs.stream_arena_write_fault, &skel->bss->fault_addr);
>  	if (test__start_subtest("load_acquire_fault"))
>  		test_address(skel->progs.stream_arena_load_acquire_fault, &skel->bss->fault_addr);
> +	if (test__start_subtest("xchg_fault"))
> +		test_address(skel->progs.stream_arena_xchg_fault, &skel->bss->fault_addr);
> +	if (test__start_subtest("cmpxchg_fault"))
> +		test_address(skel->progs.stream_arena_cmpxchg_fault, &skel->bss->fault_addr);
>
>  	stream__destroy(skel);
>  }
> diff --git a/tools/testing/selftests/bpf/progs/stream.c b/tools/testing/selftests/bpf/progs/stream.c
> --- a/tools/testing/selftests/bpf/progs/stream.c
> +++ b/tools/testing/selftests/bpf/progs/stream.c
> @@ -229,6 +229,107 @@ int stream_arena_load_acquire_fault(void *ctx)
>  	return val;
>  }
>
> +SEC("syscall")
> +__arch_x86_64
> +__arch_arm64
> +__success __retval(0)
> +__stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}")

The series includes fixes for four JITs (x86, arm64, riscv, s390), but
these new programs only cover two of them. Could __arch_riscv64 and
__arch_s390x variants be added for the other half?

The riscv64 and s390 JIT fixes exist in the same series:

  a49d067b71354 bpf, riscv: Clear fetch destination on faulting arena atomic
  e0de31f547978 bpf, s390:  Clear fetch destination on faulting arena atomic

but the __arch gate provably excludes them from these tests.

The __stderr() half unavoidably gates on x86_64 and arm64, since only
those two JITs call bpf_prog_report_arena_violation(). Both the riscv
and s390 changelogs state this explicitly.

But the __retval(0) half does not depend on arena violation reporting.
A variant tagged __arch_riscv64 __arch_s390x with __success __retval(0)
and no __stderr() would exercise exactly the register-clear fix those two
commits make, using the same poison-and-return mechanism. As written, two
of the four fixed JITs ship with no test coverage.

> +__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
> +__stderr("Call trace:\n"
> +"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
> +"|[ \t]+[^\n]+\n)*}}")
> +int stream_arena_xchg_fault(void *ctx)
> +{
> +	static const struct bpf_insn xchg_insn = {
> +		.code	 = 0xc3,	/* BPF_STX | BPF_ATOMIC | BPF_W */
> +		.dst_reg = 1,		/* BPF_REG_1 */
> +		.src_reg = 2,		/* BPF_REG_2 */
> +		.off	 = 0x7fff,
> +		.imm	 = 0xe1,	/* BPF_XCHG */
> +	};

Does the misaligned .off = 0x7fff cause the test to validate a different
fault path on arm64 than the commit message describes?

user_vm_start is page aligned, so the effective address is
user_vm_start + 0x7fff, which is 3 mod 4. The 4-byte BPF_W access also
straddles the page boundary at 0x8000.

For plain LDR/STR (stream_arena_read_fault, stream_arena_write_fault),
this offset works as intended: arm64 permits unaligned access with
SCTLR_EL1.A=0, so those really do take a translation fault on the unmapped
page.

But atomics require natural alignment. arm64 LSE CAS/SWP raise an EL1
Alignment fault (ESR FSC 0b100001) when the access is misaligned or crosses
a 16-byte granule, and that takes priority over the translation fault.

The test still passes - fixup_exception() in arch/arm64/mm/fault.c runs
before any die_kernel_fault path, so ex_handler_bpf() still clears the
fetch register and still prints "Arena WRITE access at unmapped address".
The fix under test is fault-type independent.

But the arm64 arm of both new subtests validates the alignment-fault path
rather than the unmapped-arena-page path, while x86-64 (where page fault
is delivered ahead of split-lock alignment check) validates the intended
one, so the two architectures end up testing different code paths.

A 4-byte-aligned offset inside the unmapped page (e.g. 0x7ffc) would keep
the fault a translation fault on both architectures. The pre-existing
stream_arena_load_acquire_fault has the same property with its 0x7fff.

[ ... ]

> +	return val;
> +}
> +
> +SEC("syscall")
> +__arch_x86_64
> +__arch_arm64

A subsystem pattern flags this as potentially concerning: On arm64 these
two programs only load on CPUs that implement LSE atomics, and nothing in
the test gates on that.

arch/arm64/net/bpf_jit_comp.c:bpf_jit_supports_insn() rejects arena RMW
atomics without LSE:

  bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena)
  {
  	if (!in_arena)
  		return true;
  	switch (insn->code) {
  	case BPF_STX | BPF_ATOMIC | BPF_W:
  	case BPF_STX | BPF_ATOMIC | BPF_DW:
  		if (!bpf_atomic_is_load_store(insn) &&
  		    !cpus_have_cap(ARM64_HAS_LSE_ATOMICS))
  			return false;
  	}
  	return true;
  }

The verifier consults it for arena destinations in atomic_ptr_type_ok()
and on failure rejects the program with "BPF_ATOMIC stores into R1 arena
is not allowed". BPF_XCHG (0xe1) and BPF_CMPXCHG (0xf1) are not load-store
forms, so bpf_atomic_is_load_store() returns false for both and the LSE
check applies.

This is new for this object. Before the patch the arena programs in
progs/stream.c used plain BPF_LDX/BPF_STX and one BPF_LOAD_ACQ, and
BPF_LOAD_ACQ is exempted by bpf_atomic_is_load_store(). progs/stream.c
therefore had no CPU-feature dependency until now.

On an ARMv8.0 core (Cortex-A53/A57/A72) the consequences reach beyond the
two new subtests, because stream__open_and_load() is an all-or-nothing
whole-object load:

  - test_stream_arena_fault_address(): stream__open_and_load() returns
    NULL, ASSERT_OK_PTR fails and the function returns, so read_fault,
    write_fault and load_acquire_fault are lost too - three subtests
    that pass today.
  - test_stream_syscall(): same whole-object load, and this test is
    otherwise entirely architecture-neutral.
  - test_stream_success() (RUN_TESTS) loads programs individually
    (bpf_program__set_autoload), so there the damage is limited to the
    two new programs reporting load failure.

Would a runtime LSE probe, or moving the two RMW programs into a separate
object so a load failure cannot take down the unrelated subtests, contain
this?

progs/arena_atomics.c already does arena xchg/cmpxchg guarded only by
compile-time checks, prog_tests/arena_atomics.c loads that whole object
with arena_atomics__load(), and DENYLIST.aarch64 is empty - so the
arena_atomics test presumably already fails on non-LSE arm64. This class
of gap is pre-existing in the tree.

> +__success __retval(0)
> +__stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}")

[ ... ]


---
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/31496528509

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

* Re: [PATCH bpf-next v3 6/6] selftests/bpf: Add arena fault tests for atomics with fetch
  2026-08-11 14:27   ` bot+bpf-ci
@ 2026-08-11 15:14     ` Daniel Borkmann
  0 siblings, 0 replies; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-11 15:14 UTC (permalink / raw)
  To: bot+bpf-ci, memxor
  Cc: eddyz87, puranjay, bpf, ast, andrii, martin.lau, yonghong.song,
	clm, ihor.solodrai

On 8/11/26 4:27 PM, bot+bpf-ci@kernel.org wrote:
[...]
>> diff --git a/tools/testing/selftests/bpf/progs/stream.c b/tools/testing/selftests/bpf/progs/stream.c
>> --- a/tools/testing/selftests/bpf/progs/stream.c
>> +++ b/tools/testing/selftests/bpf/progs/stream.c
>> @@ -229,6 +229,107 @@ int stream_arena_load_acquire_fault(void *ctx)
>>   	return val;
>>   }
>>
>> +SEC("syscall")
>> +__arch_x86_64
>> +__arch_arm64
>> +__success __retval(0)
>> +__stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}")
> 
> The series includes fixes for four JITs (x86, arm64, riscv, s390), but
> these new programs only cover two of them. Could __arch_riscv64 and
> __arch_s390x variants be added for the other half?

Hm, so the __arch gate is forced by __stderr() and bpf_prog_report_arena_violation()
currently only has callers in the x86-64 and arm64 ex handlers. I'd leave
as-is for now.

[...]
> But the __retval(0) half does not depend on arena violation reporting.
> A variant tagged __arch_riscv64 __arch_s390x with __success __retval(0)
> and no __stderr() would exercise exactly the register-clear fix those two
> commits make, using the same poison-and-return mechanism. As written, two
> of the four fixed JITs ship with no test coverage.
> 
>> +__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
>> +__stderr("Call trace:\n"
>> +"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
>> +"|[ \t]+[^\n]+\n)*}}")
>> +int stream_arena_xchg_fault(void *ctx)
>> +{
>> +	static const struct bpf_insn xchg_insn = {
>> +		.code	 = 0xc3,	/* BPF_STX | BPF_ATOMIC | BPF_W */
>> +		.dst_reg = 1,		/* BPF_REG_1 */
>> +		.src_reg = 2,		/* BPF_REG_2 */
>> +		.off	 = 0x7fff,
>> +		.imm	 = 0xe1,	/* BPF_XCHG */
>> +	};
> 
> Does the misaligned .off = 0x7fff cause the test to validate a different
> fault path on arm64 than the commit message describes?
> 
> user_vm_start is page aligned, so the effective address is
> user_vm_start + 0x7fff, which is 3 mod 4. The 4-byte BPF_W access also
> straddles the page boundary at 0x8000.
> 
> For plain LDR/STR (stream_arena_read_fault, stream_arena_write_fault),
> this offset works as intended: arm64 permits unaligned access with
> SCTLR_EL1.A=0, so those really do take a translation fault on the unmapped
> page.
> 
> But atomics require natural alignment. arm64 LSE CAS/SWP raise an EL1
> Alignment fault (ESR FSC 0b100001) when the access is misaligned or crosses
> a 16-byte granule, and that takes priority over the translation fault.
> 
> The test still passes - fixup_exception() in arch/arm64/mm/fault.c runs
> before any die_kernel_fault path, so ex_handler_bpf() still clears the
> fetch register and still prints "Arena WRITE access at unmapped address".
> The fix under test is fault-type independent.
> 
> But the arm64 arm of both new subtests validates the alignment-fault path
> rather than the unmapped-arena-page path, while x86-64 (where page fault
> is delivered ahead of split-lock alignment check) validates the intended
> one, so the two architectures end up testing different code paths.
> 
> A 4-byte-aligned offset inside the unmapped page (e.g. 0x7ffc) would keep
> the fault a translation fault on both architectures. The pre-existing
> stream_arena_load_acquire_fault has the same property with its 0x7fff.

For the sake of what we test here, I'd leave as-is since the fault type
doesn't really matter here but rather the fact that we match on arena WRITE
plus the register clear.

> [ ... ]
> 
>> +	return val;
>> +}
>> +
>> +SEC("syscall")
>> +__arch_x86_64
>> +__arch_arm64
> 
> A subsystem pattern flags this as potentially concerning: On arm64 these
> two programs only load on CPUs that implement LSE atomics, and nothing in
> the test gates on that.
Hm, not relevant for upstream BPF CI given green as can be seen, but I can
look into that if desired.

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

* Re: [PATCH bpf-next v3 6/6] selftests/bpf: Add arena fault tests for atomics with fetch
  2026-08-11 13:16 ` [PATCH bpf-next v3 6/6] selftests/bpf: Add arena fault tests for atomics with fetch Daniel Borkmann
  2026-08-11 14:27   ` bot+bpf-ci
@ 2026-08-11 15:57   ` Puranjay Mohan
  1 sibling, 0 replies; 13+ messages in thread
From: Puranjay Mohan @ 2026-08-11 15:57 UTC (permalink / raw)
  To: Daniel Borkmann; +Cc: memxor, eddyz87, bpf

On Tue, Aug 11, 2026 at 2:16 PM Daniel Borkmann <daniel@iogearbox.net> wrote:
>
> Add stream_arena_xchg_fault and stream_arena_cmpxchg_fault next to the
> existing read, write and load-acquire fault tests, covering the two
> places a read-modify-write can deposit the old value: src_reg for a
> BPF_XCHG and r0 for a BPF_CMPXCHG. Both cover both halves of the JIT
> bug that left the fetch destination alone when a RMW on an arena pointer
> faulted:
>
>   - the fault has to be reported as a WRITE, and at the address held by
>     the destination register, which __stderr() and test_address() check
>   - the register receiving the fetched value has to be cleared by the
>     fault handler, which the programs check by poisoning it before the
>     atomic and returning it, so __retval(0) fails if it is left untouched
>
> The __stderr() annotation can only wildcard the faulting address since
> the arena base is not known until runtime, hence the two test_address()
> subtests on top, which pin it to the address held by dst_reg rather than
> src_reg.
>
> Note, the atomics are open coded since linux/filter.h cannot be included
> alongside vmlinux.h.
>
>   # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t stream
>   [...]
>   #464/1   stream_arena_fault_address/read_fault:OK
>   #464/2   stream_arena_fault_address/write_fault:OK
>   #464/3   stream_arena_fault_address/load_acquire_fault:OK
>   #464/4   stream_arena_fault_address/xchg_fault:OK
>   #464/5   stream_arena_fault_address/cmpxchg_fault:OK
>   #464     stream_arena_fault_address:OK
>   [...]
>   #466/5   stream_success/stream_arena_write_fault:OK
>   #466/6   stream_success/stream_arena_read_fault:OK
>   #466/7   stream_success/stream_arena_load_acquire_fault:OK
>   #466/8   stream_success/stream_arena_xchg_fault:OK
>   #466/9   stream_success/stream_arena_cmpxchg_fault:OK
>   [...]
>   Summary: 4/22 PASSED, 0 SKIPPED, 0 FAILED
>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> Acked-by: Eduard Zingerman <eddyz87@gmail.com>
> ---
>  v1 -> v2:
>    - Updated commit msg wrt test_address structuring
>
>  .../testing/selftests/bpf/prog_tests/stream.c |   4 +
>  tools/testing/selftests/bpf/progs/stream.c    | 101 ++++++++++++++++++
>  2 files changed, 105 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/stream.c b/tools/testing/selftests/bpf/prog_tests/stream.c
> index 15dd3ae2a84b..e4e9374309e2 100644
> --- a/tools/testing/selftests/bpf/prog_tests/stream.c
> +++ b/tools/testing/selftests/bpf/prog_tests/stream.c
> @@ -105,6 +105,10 @@ void test_stream_arena_fault_address(void)
>                 test_address(skel->progs.stream_arena_write_fault, &skel->bss->fault_addr);
>         if (test__start_subtest("load_acquire_fault"))
>                 test_address(skel->progs.stream_arena_load_acquire_fault, &skel->bss->fault_addr);
> +       if (test__start_subtest("xchg_fault"))
> +               test_address(skel->progs.stream_arena_xchg_fault, &skel->bss->fault_addr);
> +       if (test__start_subtest("cmpxchg_fault"))
> +               test_address(skel->progs.stream_arena_cmpxchg_fault, &skel->bss->fault_addr);
>
>         stream__destroy(skel);
>  }
> diff --git a/tools/testing/selftests/bpf/progs/stream.c b/tools/testing/selftests/bpf/progs/stream.c
> index cf5533e11f39..00a37933e411 100644
> --- a/tools/testing/selftests/bpf/progs/stream.c
> +++ b/tools/testing/selftests/bpf/progs/stream.c
> @@ -229,6 +229,107 @@ int stream_arena_load_acquire_fault(void *ctx)
>         return val;
>  }
>
> +SEC("syscall")
> +__arch_x86_64
> +__arch_arm64
> +__success __retval(0)
> +__stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}")
> +__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
> +__stderr("Call trace:\n"
> +"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
> +"|[ \t]+[^\n]+\n)*}}")
> +int stream_arena_xchg_fault(void *ctx)
> +{
> +       static const struct bpf_insn xchg_insn = {
> +               .code    = 0xc3,        /* BPF_STX | BPF_ATOMIC | BPF_W */
> +               .dst_reg = 1,           /* BPF_REG_1 */
> +               .src_reg = 2,           /* BPF_REG_2 */
> +               .off     = 0x7fff,
> +               .imm     = 0xe1,        /* BPF_XCHG */
> +       };
> +       struct bpf_arena *ptr = (void *)&arena;
> +       u64 user_vm_start, val;
> +
> +       /*
> +        * Prevent GCC bounds warning: casting &arena to struct bpf_arena *
> +        * triggers bounds checking since the map definition is smaller than
> +        * struct bpf_arena. barrier_var() makes the pointer opaque to GCC,
> +        * preventing the bounds analysis.
> +        */
> +       barrier_var(ptr);
> +       user_vm_start = ptr->user_vm_start;
> +       fault_addr = user_vm_start + 0x7fff;
> +       bpf_addr_space_cast(user_vm_start, 0, 1);
> +       /*
> +        * A read-modify-write carrying BPF_FETCH writes to memory, so the fault
> +        * has to be reported as a WRITE from the dst_reg address, but it also
> +        * reads the old value into src_reg, so the exception handler has to
> +        * clear src_reg. Poison it up front, the returned value must be 0.
> +        */
> +       asm volatile (
> +               "r1 = %[user_vm_start];"
> +               "r2 = 1;"
> +               ".8byte %[xchg_insn];" /* r2 = xchg((u32 *)(r1 + 0x7fff), r2) */
> +               "%[val] = r2;"
> +               : [val] "=r" (val)
> +               : [user_vm_start] "r" (user_vm_start),
> +                 __imm_insn(xchg_insn, xchg_insn)
> +               : "r1", "r2"
> +       );
> +       return val;
> +}
> +
> +SEC("syscall")
> +__arch_x86_64
> +__arch_arm64
> +__success __retval(0)
> +__stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}")
> +__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
> +__stderr("Call trace:\n"
> +"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
> +"|[ \t]+[^\n]+\n)*}}")
> +int stream_arena_cmpxchg_fault(void *ctx)
> +{
> +       static const struct bpf_insn cmpxchg_insn = {
> +               .code    = 0xc3,        /* BPF_STX | BPF_ATOMIC | BPF_W */
> +               .dst_reg = 1,           /* BPF_REG_1 */
> +               .src_reg = 2,           /* BPF_REG_2 */
> +               .off     = 0x7fff,
> +               .imm     = 0xf1,        /* BPF_CMPXCHG */
> +       };
> +       struct bpf_arena *ptr = (void *)&arena;
> +       u64 user_vm_start, val;
> +
> +       /*
> +        * Prevent GCC bounds warning: casting &arena to struct bpf_arena *
> +        * triggers bounds checking since the map definition is smaller than
> +        * struct bpf_arena. barrier_var() makes the pointer opaque to GCC,
> +        * preventing the bounds analysis.
> +        */
> +       barrier_var(ptr);
> +       user_vm_start = ptr->user_vm_start;
> +       fault_addr = user_vm_start + 0x7fff;
> +       bpf_addr_space_cast(user_vm_start, 0, 1);
> +       /*
> +        * Same as the exchange above, except that a BPF_CMPXCHG reads the old
> +        * value into r0 rather than into src_reg, so r0 is the register the
> +        * exception handler has to clear. It doubles as the compare value, but
> +        * the comparison never happens since the access faults first.
> +        */
> +       asm volatile (
> +               "r1 = %[user_vm_start];"
> +               "r0 = 1;"
> +               "r2 = 2;"
> +               ".8byte %[cmpxchg_insn];" /* r0 = cmpxchg((u32 *)(r1 + 0x7fff), r0, r2) */
> +               "%[val] = r0;"
> +               : [val] "=r" (val)
> +               : [user_vm_start] "r" (user_vm_start),
> +                 __imm_insn(cmpxchg_insn, cmpxchg_insn)
> +               : "r0", "r1", "r2"
> +       );
> +       return val;
> +}
> +
>  static __noinline void subprog(void)
>  {
>         int __arena *addr = (int __arena *)0xdeadbeef;
> --
> 2.43.0
>



Acked-by: Puranjay Mohan <puranjay@kernel.org>

Thanks,
Puranjay

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

* Re: [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place
  2026-08-11 13:15 [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Daniel Borkmann
                   ` (4 preceding siblings ...)
  2026-08-11 13:16 ` [PATCH bpf-next v3 6/6] selftests/bpf: Add arena fault tests for atomics with fetch Daniel Borkmann
@ 2026-08-11 21:45 ` Eduard Zingerman
  2026-08-12 17:40 ` patchwork-bot+netdevbpf
  6 siblings, 0 replies; 13+ messages in thread
From: Eduard Zingerman @ 2026-08-11 21:45 UTC (permalink / raw)
  To: Daniel Borkmann, memxor; +Cc: puranjay, bpf

On Tue, 2026-08-11 at 15:15 +0200, Daniel Borkmann wrote:
> check_atomic_rmw() open codes the mapping from a BPF_ATOMIC to the register
> it reads the old value into, the BPF_STX case of insn_def_regno() open codes
> the very same mapping a second time, the const folding and the liveness
> transfer functions a third and a fourth time, and BPF JITs need it as well
> to know which register a faulting BPF_PROBE_ATOMIC has to clear.
> 
> Add a small helper so that all of them can share it. No functional change.
> The BPF_LOAD_ACQ case is there for the JITs, which do walk all instruction
> classes. const_reg_xfer() loses its explicit BPF_ATOMIC mode test since the
> helper checks class and mode itself; the BPF_PROBE_ATOMIC it additionally
> accepts cannot be seen there as it is only set from bpf_do_misc_fixups(),
> that is, after const folding has run. arg_track_xfer() keeps its mode test
> since that also guards the stack clearing next to it.
> 
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> ---
>   v2 -> v3:
>    - Also use the helper for const_fold and liveness (bpf ci)
>    - Drop the return value comment in verifier since the helper
>      comment already covers it (Jakub)
>   v1 -> v2:
>    - also convert insn_def_regno (Eduard, sashiko)
> 
>  include/linux/filter.h  | 24 ++++++++++++++++++++++++
>  kernel/bpf/const_fold.c | 11 +++--------
>  kernel/bpf/fixups.c     | 11 +----------
>  kernel/bpf/liveness.c   |  9 +++------
>  kernel/bpf/verifier.c   | 13 ++-----------
>  5 files changed, 33 insertions(+), 35 deletions(-)

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

...

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

* Re: [PATCH bpf-next v3 5/6] bpf, s390: Clear fetch destination on faulting arena atomic
  2026-08-11 13:15 ` [PATCH bpf-next v3 5/6] bpf, s390: " Daniel Borkmann
@ 2026-08-12  9:40   ` Ilya Leoshkevich
  0 siblings, 0 replies; 13+ messages in thread
From: Ilya Leoshkevich @ 2026-08-12  9:40 UTC (permalink / raw)
  To: Daniel Borkmann, memxor; +Cc: eddyz87, puranjay, bpf



On 8/11/26 15:15, Daniel Borkmann wrote:
> Same missing register clear as on riscv64. A RMW atomic on an arena pointer
> is converted to BPF_PROBE_ATOMIC and gets an exception table entry, but
> bpf_jit_probe_atomic_pre() only fills in the arena base and the probe
> offset, leaving probe->reg at the -1 that bpf_jit_probe_init() set, which
> bpf_jit_probe_post() writes into the entry and ex_handler_bpf() then reads
> back as "there is nothing to clear".
> 
> That is right for a plain BPF_{ADD,AND,OR,XOR}, which only writes memory,
> but an RMW carrying BPF_FETCH also reads the old value into a register:
> src_reg for BPF_{ADD,AND,OR,XOR} | BPF_FETCH and BPF_XCHG, and r0 for
> BPF_CMPXCHG. So on a fault over an unmapped arena page the program resumes
> at the landing pad with whatever that register held before the atomic
> instead of the 0 that every other BPF_PROBE_* access delivers.
> 
> Fill probe->reg in from bpf_atomic_load_reg(). Unlike x86-64 and arm64,
> s390x does not report arena violations from its exception handler, so there
> is no access direction to correct here, only the missing register clear.
> 
> Fixes: 2f9469484a3b ("s390/bpf: Support arena atomics")
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> Cc: Ilya Leoshkevich <iii@linux.ibm.com>
> ---
>   arch/s390/net/bpf_jit_comp.c | 11 +++++++++++
>   1 file changed, 11 insertions(+)

Looks reasonable, thanks!

Reviewed-by: Ilya Leoshkevich <iii@linux.ibm.com>


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

* Re: [PATCH bpf-next v3 4/6] bpf, arm64: Clear fetch destination on faulting arena atomic
  2026-08-11 13:15 ` [PATCH bpf-next v3 4/6] bpf, arm64: " Daniel Borkmann
@ 2026-08-12 12:18   ` Puranjay Mohan
  0 siblings, 0 replies; 13+ messages in thread
From: Puranjay Mohan @ 2026-08-12 12:18 UTC (permalink / raw)
  To: Daniel Borkmann; +Cc: memxor, eddyz87, bpf

On Tue, Aug 11, 2026 at 2:16 PM Daniel Borkmann <daniel@iogearbox.net> wrote:
>
> Same problem as on x86-64: add_exception_handler() folds "there is no
> destination register to clear" and "this is a store" into one DONT_CLEAR
> value ...
>
>   if (BPF_CLASS(insn->code) != BPF_LDX && !bpf_atomic_is_load_acq(insn))
>           dst_reg = DONT_CLEAR;
>
> ... which ex_handler_bpf() then reads back as the access direction:
>
>   bool is_write = (dst_reg == DONT_CLEAR);
>
> A RMW carrying BPF_FETCH is both. emit_lse_atomic() reads the old value
> into src_reg for BPF_{ADD,AND,OR,XOR} | BPF_FETCH and BPF_XCHG, and into
> r0 for BPF_CMPXCHG, so a fault over an unmapped arena page is correctly
> reported as a WRITE but leaves that register holding a stale value instead
> of the 0 that every other BPF_PROBE_* access delivers. Same as on x86-64,
> add a separate ARENA_WRITE bit for the direction.
>
> FIXUP_REG is now filled in by the callers of add_exception_handler(), the
> BPF_PROBE_ATOMIC one deriving it from bpf_atomic_load_reg(), so that the
> helper only has to determine the direction. This is how the riscv64 JIT
> already does it, and it stops the two store callers from handing in a
> dst_reg that was only going to be overwritten with DONT_CLEAR anyway.
>
> Fixes: e612b5c1d3ee ("bpf, arm64: Add support for lse atomics in bpf_arena")
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> Cc: Puranjay Mohan <puranjay@kernel.org>
> ---
>  v1 -> v2:
>    - move bpf_atomic_load_reg into build_insn (Eduard)
>
>  arch/arm64/net/bpf_jit_comp.c | 44 ++++++++++++++++++++++++-----------
>  1 file changed, 30 insertions(+), 14 deletions(-)
>
> diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
> index d14d297ebb96..74b4083791da 100644
> --- a/arch/arm64/net/bpf_jit_comp.c
> +++ b/arch/arm64/net/bpf_jit_comp.c
> @@ -1082,23 +1082,27 @@ static void build_epilogue(struct jit_ctx *ctx, bool was_classic)
>   *
>   * Bit layout of `fixup` (32-bit):
>   *
> - * +-----------+--------+-----------+-----------+----------+
> - * |   31-27   | 26-22  |     21    |   20-16   |   15-0   |
> - * |           |        |           |           |          |
> - * | FIXUP_REG | Unused | ARENA_ACC | ARENA_REG |  OFFSET  |
> - * +-----------+--------+-----------+-----------+----------+
> + * +-----------+--------+-------------+-----------+-----------+----------+
> + * |   31-27   | 26-23  |      22     |     21    |   20-16   |   15-0   |
> + * |           |        |             |           |           |          |
> + * | FIXUP_REG | Unused | ARENA_WRITE | ARENA_ACC | ARENA_REG |  OFFSET  |
> + * +-----------+--------+-------------+-----------+-----------+----------+
>   *
>   * - OFFSET (16 bits): Offset used to compute address for Load/Store instruction.
>   * - ARENA_REG (5 bits): Register that is used to calculate the address for load/store when
>   *                       accessing the arena region.
>   * - ARENA_ACCESS (1 bit): This bit is set when the faulting instruction accessed the arena region.
> + * - ARENA_WRITE (1 bit): This bit is set when the faulting instruction wrote to the arena region.
> + *                        It is independent of FIXUP_REG, since a read-modify-write both writes to
> + *                        memory and reads the old value into a register.
>   * - FIXUP_REG (5 bits): Destination register for the load instruction (cleared on fault) or set to
> - *                       DONT_CLEAR if it is a store instruction.
> + *                       DONT_CLEAR if the instruction does not read into a register.
>   */
>
>  #define BPF_FIXUP_OFFSET_MASK      GENMASK(15, 0)
>  #define BPF_FIXUP_ARENA_REG_MASK   GENMASK(20, 16)
>  #define BPF_ARENA_ACCESS           BIT(21)
> +#define BPF_ARENA_WRITE            BIT(22)
>  #define BPF_FIXUP_REG_MASK     GENMASK(31, 27)
>  #define DONT_CLEAR 5 /* Unused ARM64 register from BPF's POV */
>
> @@ -1109,7 +1113,7 @@ bool ex_handler_bpf(const struct exception_table_entry *ex,
>         s16 off = FIELD_GET(BPF_FIXUP_OFFSET_MASK, ex->fixup);
>         int arena_reg = FIELD_GET(BPF_FIXUP_ARENA_REG_MASK, ex->fixup);
>         bool is_arena = !!(ex->fixup & BPF_ARENA_ACCESS);
> -       bool is_write = (dst_reg == DONT_CLEAR);
> +       bool is_write = !!(ex->fixup & BPF_ARENA_WRITE);
>         unsigned long addr;
>
>         if (is_arena) {
> @@ -1132,7 +1136,7 @@ static int add_exception_handler(const struct bpf_insn *insn,
>  {
>         off_t ins_offset;
>         s16 off = insn->off;
> -       bool is_arena;
> +       bool is_arena, is_write;
>         int arena_reg;
>         unsigned long pc;
>         struct exception_table_entry *ex;
> @@ -1181,15 +1185,18 @@ static int add_exception_handler(const struct bpf_insn *insn,
>         /*
>          * A load-acquire is of BPF_STX class, but reads from src_reg into
>          * dst_reg like a BPF_LDX does, hence it must not be treated as a store
> -        * here.
> +        * here. A read-modify-write carrying BPF_FETCH is reported as a write
> +        * even though it does have a register to clear, see the callers.
>          */
> -       if (BPF_CLASS(insn->code) != BPF_LDX && !bpf_atomic_is_load_acq(insn))
> -               dst_reg = DONT_CLEAR;
> +       is_write = BPF_CLASS(insn->code) != BPF_LDX &&
> +                  !bpf_atomic_is_load_acq(insn);
>
>         ex->fixup = FIELD_PREP(BPF_FIXUP_REG_MASK, dst_reg);
>
>         if (is_arena) {
>                 ex->fixup |= BPF_ARENA_ACCESS;
> +               if (is_write)
> +                       ex->fixup |= BPF_ARENA_WRITE;
>                 /*
>                  * insn->src_reg/dst_reg holds the address in the arena region with upper 32-bits
>                  * being zero because of a preceding addr_space_cast(r<n>, 0x0, 0x1) instruction.
> @@ -1889,7 +1896,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
>                         break;
>                 }
>
> -               ret = add_exception_handler(insn, ctx, dst);
> +               ret = add_exception_handler(insn, ctx, DONT_CLEAR);
>                 if (ret)
>                         return ret;
>                 break;
> @@ -1956,7 +1963,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
>                         break;
>                 }
>
> -               ret = add_exception_handler(insn, ctx, dst);
> +               ret = add_exception_handler(insn, ctx, DONT_CLEAR);
>                 if (ret)
>                         return ret;
>                 break;
> @@ -1979,7 +1986,16 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
>                         return ret;
>
>                 if (BPF_MODE(insn->code) == BPF_PROBE_ATOMIC) {
> -                       ret = add_exception_handler(insn, ctx, dst);
> +                       /*
> +                        * A load-acquire reads into dst_reg, and a read-modify-write
> +                        * carrying BPF_FETCH reads the old value into src_reg, or into
> +                        * r0 for a BPF_CMPXCHG. Clear that register on fault, the
> +                        * remaining atomics have no destination register.
> +                        */
> +                       int load_reg = bpf_atomic_load_reg(insn);
> +
> +                       ret = add_exception_handler(insn, ctx, load_reg < 0 ?
> +                                                   DONT_CLEAR : bpf2a64[load_reg]);
>                         if (ret)
>                                 return ret;
>                 }
> --
> 2.43.0
>

Reviewed-by: Puranjay Mohan <puranjay@kernel.org>

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

* Re: [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place
  2026-08-11 13:15 [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Daniel Borkmann
                   ` (5 preceding siblings ...)
  2026-08-11 21:45 ` [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Eduard Zingerman
@ 2026-08-12 17:40 ` patchwork-bot+netdevbpf
  6 siblings, 0 replies; 13+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-08-12 17:40 UTC (permalink / raw)
  To: Daniel Borkmann; +Cc: memxor, eddyz87, puranjay, bpf

Hello:

This series was applied to bpf/bpf-next.git (master)
by Eduard Zingerman <eddyz87@gmail.com>:

On Tue, 11 Aug 2026 15:15:55 +0200 you wrote:
> check_atomic_rmw() open codes the mapping from a BPF_ATOMIC to the register
> it reads the old value into, the BPF_STX case of insn_def_regno() open codes
> the very same mapping a second time, the const folding and the liveness
> transfer functions a third and a fourth time, and BPF JITs need it as well
> to know which register a faulting BPF_PROBE_ATOMIC has to clear.
> 
> Add a small helper so that all of them can share it. No functional change.
> The BPF_LOAD_ACQ case is there for the JITs, which do walk all instruction
> classes. const_reg_xfer() loses its explicit BPF_ATOMIC mode test since the
> helper checks class and mode itself; the BPF_PROBE_ATOMIC it additionally
> accepts cannot be seen there as it is only set from bpf_do_misc_fixups(),
> that is, after const folding has run. arg_track_xfer() keeps its mode test
> since that also guards the stack clearing next to it.
> 
> [...]

Here is the summary with links:
  - [bpf-next,v3,1/6] bpf: Derive the atomic load register in one place
    https://git.kernel.org/bpf/bpf-next/c/41c5dbb4be3c
  - [bpf-next,v3,2/6] bpf, riscv: Clear fetch destination on faulting arena atomic
    https://git.kernel.org/bpf/bpf-next/c/1519f488e8ce
  - [bpf-next,v3,3/6] bpf, x86: Clear fetch destination on faulting arena atomic
    https://git.kernel.org/bpf/bpf-next/c/cf92a108601a
  - [bpf-next,v3,4/6] bpf, arm64: Clear fetch destination on faulting arena atomic
    https://git.kernel.org/bpf/bpf-next/c/ea3f20cb5918
  - [bpf-next,v3,5/6] bpf, s390: Clear fetch destination on faulting arena atomic
    https://git.kernel.org/bpf/bpf-next/c/cc3e12330599
  - [bpf-next,v3,6/6] selftests/bpf: Add arena fault tests for atomics with fetch
    https://git.kernel.org/bpf/bpf-next/c/611a9f0d3dca

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



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

end of thread, other threads:[~2026-08-12 17:41 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-11 13:15 [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Daniel Borkmann
2026-08-11 13:15 ` [PATCH bpf-next v3 2/6] bpf, riscv: Clear fetch destination on faulting arena atomic Daniel Borkmann
2026-08-11 13:15 ` [PATCH bpf-next v3 3/6] bpf, x86: " Daniel Borkmann
2026-08-11 13:15 ` [PATCH bpf-next v3 4/6] bpf, arm64: " Daniel Borkmann
2026-08-12 12:18   ` Puranjay Mohan
2026-08-11 13:15 ` [PATCH bpf-next v3 5/6] bpf, s390: " Daniel Borkmann
2026-08-12  9:40   ` Ilya Leoshkevich
2026-08-11 13:16 ` [PATCH bpf-next v3 6/6] selftests/bpf: Add arena fault tests for atomics with fetch Daniel Borkmann
2026-08-11 14:27   ` bot+bpf-ci
2026-08-11 15:14     ` Daniel Borkmann
2026-08-11 15:57   ` Puranjay Mohan
2026-08-11 21:45 ` [PATCH bpf-next v3 1/6] bpf: Derive the atomic load register in one place Eduard Zingerman
2026-08-12 17:40 ` patchwork-bot+netdevbpf

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.