selftests/bpf: Add arena fault test for load-acquire

Add stream_arena_load_acquire_fault, which performs a load-acquire from an
unmapped arena address, next to the existing read and write fault tests.

The test covers both halves of the JIT bug that treated a load-acquire as
a store when populating its exception table entry:

  - the fault has to be reported as a READ, and at the address held by
    the source register, which __stderr() and test_address() check, and
  - the destination register has to be cleared by the fault handler,
    which the program checks by poisoning it before the load-acquire
    and returning it, so __retval(0) fails if it is left untouched

Note, load-acquire is 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_arena_fault_address
  [...]
  #462/1   stream_arena_fault_address/read_fault:OK
  #462/2   stream_arena_fault_address/write_fault:OK
  #462/3   stream_arena_fault_address/load_acquire_fault:OK
  #462     stream_arena_fault_address:OK
  Summary: 1/3 PASSED, 0 SKIPPED, 0 FAILED

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/20260806201047.333389-5-daniel@iogearbox.net
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
This commit is contained in:
Daniel Borkmann 2026-08-06 22:10:46 +02:00 committed by Kumar Kartikeya Dwivedi
parent af22d273aa
commit 007466d9e4
No known key found for this signature in database
GPG Key ID: 472D377B63542F83
2 changed files with 46 additions and 0 deletions

View File

@ -103,6 +103,8 @@ void test_stream_arena_fault_address(void)
test_address(skel->progs.stream_arena_read_fault, &skel->bss->fault_addr);
if (test__start_subtest("write_fault"))
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);
stream__destroy(skel);
}

View File

@ -185,6 +185,50 @@ int stream_arena_read_fault(void *ctx)
return 0;
}
SEC("syscall")
__arch_x86_64
__arch_arm64
__success __retval(0)
__stderr("ERROR: Arena READ 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_load_acquire_fault(void *ctx)
{
static const struct bpf_insn load_acquire_insn = {
.code = 0xc3, /* BPF_STX | BPF_ATOMIC | BPF_W */
.dst_reg = 0, /* BPF_REG_0 */
.src_reg = 1, /* BPF_REG_1 */
.off = 0x7fff,
.imm = 0x100, /* BPF_LOAD_ACQ */
};
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);
asm volatile (
"r1 = %[user_vm_start];"
"r0 = 1;"
".8byte %[load_acquire_insn];" /* r0 = load_acquire((u32 *)(r1 + 0x7fff)) */
"%[val] = r0;"
: [val] "=r" (val)
: [user_vm_start] "r" (user_vm_start),
__imm_insn(load_acquire_insn, load_acquire_insn)
: "r0", "r1"
);
return val;
}
static __noinline void subprog(void)
{
int __arena *addr = (int __arena *)0xdeadbeef;