From: Yonghong Song yonghong.song@linux.dev
[ Upstream commit 5a427fddec5e76360725a0f03df3a2a003efbe2e ]
With latest llvm22, I got the following verification failure:
... ; int big_alloc2(void *ctx) @ verifier_arena_large.c:207 0: (b4) w6 = 1 ; R6_w=1 ... ; if (err) @ verifier_arena_large.c:233 53: (56) if w6 != 0x0 goto pc+62 ; R6=0 54: (b7) r7 = -4 ; R7_w=-4 55: (18) r8 = 0x7f4000000000 ; R8_w=scalar() 57: (bf) r9 = addr_space_cast(r8, 0, 1) ; R8_w=scalar() R9_w=arena 58: (b4) w6 = 5 ; R6_w=5 ; pg = page[i]; @ verifier_arena_large.c:238 59: (bf) r1 = r7 ; R1_w=-4 R7_w=-4 60: (07) r1 += 4 ; R1_w=0 61: (79) r2 = *(u64 *)(r9 +0) ; R2_w=scalar() R9_w=arena ; if (*pg != i) @ verifier_arena_large.c:239 62: (bf) r3 = addr_space_cast(r2, 0, 1) ; R2_w=scalar() R3_w=arena 63: (71) r3 = *(u8 *)(r3 +0) ; R3_w=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) 64: (5d) if r1 != r3 goto pc+51 ; R1_w=0 R3_w=0 ; bpf_arena_free_pages(&arena, (void __arena *)pg, 2); @ verifier_arena_large.c:241 65: (18) r1 = 0xff11000114548000 ; R1_w=map_ptr(map=arena,ks=0,vs=0) 67: (b4) w3 = 2 ; R3_w=2 68: (85) call bpf_arena_free_pages#72675 ; 69: (b7) r1 = 0 ; R1_w=0 ; page[i + 1] = NULL; @ verifier_arena_large.c:243 70: (7b) *(u64 *)(r8 +8) = r1 R8 invalid mem access 'scalar' processed 61 insns (limit 1000000) max_states_per_insn 0 total_states 6 peak_states 6 mark_read 2 ============= #489/5 verifier_arena_large/big_alloc2:FAIL
The main reason is that 'r8' in insn '70' is not an arena pointer. Further debugging at llvm side shows that llvm commit ([1]) caused the failure. For the original code: page[i] = NULL; page[i + 1] = NULL; the llvm transformed it to something like below at source level: __builtin_memset(&page[i], 0, 16) Such transformation prevents llvm BPFCheckAndAdjustIR pass from generating proper addr_space_cast insns ([2]).
Adding support in llvm BPFCheckAndAdjustIR pass should work, but not sure that such a pattern exists or not in real applications. At the same time, simply adding a memory barrier between two 'page' assignment can fix the issue.
[1] https://github.com/llvm/llvm-project/pull/155415 [2] https://github.com/llvm/llvm-project/pull/84410
Cc: Eduard Zingerman eddyz87@gmail.com Signed-off-by: Yonghong Song yonghong.song@linux.dev Link: https://lore.kernel.org/r/20250920045805.3288551-1-yonghong.song@linux.dev Signed-off-by: Alexei Starovoitov ast@kernel.org Signed-off-by: Sasha Levin sashal@kernel.org ---
LLM Generated explanations, may be completely bogus:
YES
**Why this matters** - Fixes a real, reproducible selftest failure with LLVM 22 where two adjacent pointer stores get optimized into a memset, breaking BPF verifier expectations and causing “R8 invalid mem access 'scalar'” during big_alloc2. - Keeps BPF selftests reliable across toolchains without changing kernel/runtime behavior.
**What changed** - Adds a single compiler barrier between two consecutive NULL assignments to break LLVM’s store-combining optimization: - Before: `page[i] = NULL;` immediately followed by `page[i + 1] = NULL;` - After: inserts `barrier();` between them to prevent transformation into `__builtin_memset(...)`. - Location in tree: `tools/testing/selftests/bpf/progs/verifier_arena_large.c` - In the “Free pairs of pages” loop: after freeing 2 pages (`bpf_arena_free_pages`), the code does: - `page[i] = NULL;` at tools/testing/selftests/bpf/progs/verifier_arena_large.c:242 - [PATCH] `barrier();` added between the two stores - `page[i + 1] = NULL;` at tools/testing/selftests/bpf/progs/verifier_arena_large.c:243 - The barrier macro is available via the already-included `bpf/bpf_helpers.h` (`barrier()` is defined as an empty inline asm memory clobber), used elsewhere in BPF selftests, and is safe for BPF.
**Root cause and effect** - LLVM 22 transforms two adjacent stores into a 16-byte memset when it sees: - `page[i] = NULL;` - `page[i + 1] = NULL;` - This prevents LLVM’s BPFCheckAndAdjustIR pass from inserting necessary `addr_space_cast` for arena pointers, leading to the verifier seeing a scalar pointer (R8) on the subsequent store and rejecting the program. - The inserted `barrier()` prevents that transformation, ensuring LLVM keeps separate stores and the IR pass emits `addr_space_cast`, fixing the verifier error.
**Risk and scope** - Minimal risk: - Single-line change in a selftest program. - No ABI changes, no functional changes to kernel subsystems. - Barrier only affects compiler optimization; runtime semantics remain identical. - Selftest-only change: - Does not affect production kernel behavior. - Improves test robustness across compilers.
**Stable criteria fit** - Important bugfix: prevents a deterministic selftest failure with a widely used toolchain (LLVM 22). - Small and contained: one-line addition in a single selftest file. - No architectural changes and no cross-subsystem impact. - No side effects beyond keeping IR and verifier expectations aligned for this test. - Even without an explicit “Cc: stable”, selftest fixes like this are commonly accepted to keep CI and developer workflows healthy across toolchains.
**Dependencies and compatibility** - The code path is guarded by `__BPF_FEATURE_ADDR_SPACE_CAST` (see block starting at tools/testing/selftests/bpf/progs/verifier_arena_large.c:168), so it only builds where the feature is available, reducing risk on older compilers. - `barrier()` is already defined for BPF programs via `bpf_helpers.h`, which is included at tools/testing/selftests/bpf/progs/verifier_arena_large.c:6, ensuring portability across supported clang versions.
Given the above, this is a low-risk, self-contained fix for a real breakage in selftests caused by a compiler change. It should be backported to stable trees that contain this selftest.
tools/testing/selftests/bpf/progs/verifier_arena_large.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/tools/testing/selftests/bpf/progs/verifier_arena_large.c b/tools/testing/selftests/bpf/progs/verifier_arena_large.c index 9dbdf123542d3..f19e15400b3e1 100644 --- a/tools/testing/selftests/bpf/progs/verifier_arena_large.c +++ b/tools/testing/selftests/bpf/progs/verifier_arena_large.c @@ -240,6 +240,7 @@ int big_alloc2(void *ctx) return 5; bpf_arena_free_pages(&arena, (void __arena *)pg, 2); page[i] = NULL; + barrier(); page[i + 1] = NULL; cond_break; }