You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

eBPF程序加载失败:数据包非法访问与bpf_trace_printk问题排查

问题分析:eBPF程序加载时"invalid access to packet"错误

问题背景

我用BCC框架开发eBPF程序,用于分析HTTP请求:拦截8000端口的TCP数据包,搜索payload中的"cmd="模式。简化后的eBPF代码如下:

#include <uapi/linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

#define TCP_PORT_HTTP 8000
#define MAX_ITERATIONS 100 
#define MAX_CMD_LENGTH 100


int xdp_search_http_request(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;

    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    if (eth->h_proto != htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *ip = (void *)eth + sizeof(*eth);
    if ((void *)(ip + 1) > data_end || ip->protocol != IPPROTO_TCP)
        return XDP_PASS;

    struct tcphdr *tcp = (void *)ip + ip->ihl * 4;
    if ((void *)(tcp + 1) > data_end || tcp->dest != htons(TCP_PORT_HTTP))
        return XDP_PASS;

    unsigned char *payload = (unsigned char *)tcp + (tcp->doff * 4);
    if ((void *)(payload + 1) > data_end)
        return XDP_PASS;

    unsigned char *end = data_end;
    int cmd_value_start = 0;

    // Simple search for "cmd=" in the payload
    for (int i = 0; i < MAX_ITERATIONS && (payload + i + 4) <= end; i++) {
        if (payload[i] == 'c' && payload[i+1] == 'm' && payload[i+2] == 'd' && payload[i+3] == '=') {
            // Found the "cmd=" string
            bpf_trace_printk("Found cmd parameter in HTTP request\n");
            // Move the pointer to the value part of "cmd="
            cmd_value_start = i + 4;              

            int j = 0;
            char cmd [100] = {0};
            for (j = cmd_value_start; (j < (cmd_value_start + MAX_CMD_LENGTH)) && ((payload + j) < end) && payload[j] != ' ' && payload[j] != '&'; j++) {
                cmd[j - cmd_value_start] = payload[j];
            }

            bpf_trace_printk("cmd value: %s", cmd);

            break;  // Exit after finding the first occurrence
        }
    }

    return XDP_PASS;
}

错误现象

加载程序时,BPF验证器提示"invalid access to packet",最终报错"Permission denied"。注释掉bpf_trace_printk("cmd value: %s", cmd);语句后,程序可正常加载运行。已确认具备必要内核能力且以特权身份运行,环境为Ubuntu 22.04(内核5.15.0-100-generic)。

问题原因

  1. 字符串终止符缺失风险:bpf_trace_printk使用%s格式符时,要求字符串必须以\0结尾。当前代码中,如果从payload拷贝的内容刚好填满100字节的cmd数组,数组末尾没有预留终止符位置,验证器判定存在越界访问风险。
  2. 循环边界的验证器判定逻辑:拷贝循环的边界检查是逐次执行的,内核5.15的BPF验证器无法静态确认所有情况下拷贝操作都不会超出数据包边界,尤其是当cmd_value_start接近数据包末尾时,可能触发非法访问判定。
  3. bpf_trace_printk的安全检查限制:该函数对栈上字符串的安全性校验极为严格,哪怕是潜在的未终止字符串,都会被验证器标记为非法操作。

解决及调试方案

修复方案1:强制保证字符串终止符

修改拷贝循环,预留数组最后一个字节用于存放\0,避免字符串越界:

char cmd[MAX_CMD_LENGTH] = {0};
// 预留最后一个字节放终止符,避免填满数组时无终止位
for (j = cmd_value_start; (j < (cmd_value_start + MAX_CMD_LENGTH - 1)) && ((payload + j) < end) && payload[j] != ' ' && payload[j] != '&'; j++) {
    cmd[j - cmd_value_start] = payload[j];
}
// 强制添加终止符,确保字符串合法
cmd[MAX_CMD_LENGTH - 1] = '\0';
bpf_trace_printk("cmd value: %s", cmd);

修复方案2:使用安全字符串拷贝辅助函数

改用内核提供的bpf_probe_read_str函数(5.15内核支持),该函数会自动处理字符串终止符和边界检查:

char cmd[MAX_CMD_LENGTH] = {0};
// 从payload安全拷贝字符串,最多读取MAX_CMD_LENGTH-1字节并自动添加终止符
bpf_probe_read_str(cmd, sizeof(cmd), payload + cmd_value_start);
// 手动截断到第一个空格或&的位置(因为bpf_probe_read_str仅以\0为终止条件)
for (int k = 0; k < MAX_CMD_LENGTH; k++) {
    if (cmd[k] == ' ' || cmd[k] == '&') {
        cmd[k] = '\0';
        break;
    }
}
bpf_trace_printk("cmd value: %s", cmd);

调试方案

  • 开启BCC调试模式,加载程序时添加-v参数,查看验证器的详细报错信息,定位具体非法访问位置:
    sudo ./your_bcc_script.py -v
    
  • 使用bpftool手动加载程序,获取更详细的验证日志:
    sudo bpftool prog load ./compiled_prog.o /sys/fs/bpf/test_prog
    
  • 逐步简化代码逻辑,比如固定cmd_value_start为安全值,测试bpf_trace_printk是否正常,再逐步恢复原有逻辑,定位问题点。

内容的提问来源于stack exchange,提问作者you_know_who

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 12:05:03