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

eBPF验证器不允许返回被调用函数u16类型值的问题解析

问题描述

我编写了以下eBPF函数:

SEC(...)
int BPF_PROG(...)
{
   ...
   u16 mount_last_index = get_file_mount_root(file, event_info->mountroot);
   ...
}

static inline u16 get_file_mount_root(struct file *file, u8 buf[MAX_PATH_LENGTH])
{
    struct path f_path = BPF_CORE_READ(file, f_path);   
    return get_path_mount_root(&f_path, buf);
}

static inline u16 get_path_mount_root(const struct path *path, u8 buf[MAX_PATH_LENGTH])
{
    struct mount *mnt = get_mount(path);
    struct dentry *mnt_mountpoint = BPF_CORE_READ(mnt, mnt_mountpoint);
    return read_dentry_strings(mnt_mountpoint, buf, 0);
}

static inline u16 read_dentry_strings(struct dentry *dentry, u8 buf[MAX_PATH_LENGTH], u16 cur_index)
{

    if (!dentry)
        return 0;

    if (cur_index >= MAX_PATH_LENGTH)
        return 0;

    struct dentry *curr_dtry = dentry;
    struct dentry *lastdtryp = dentry;

    for (int capacity = MAX_PATH_LENGTH; capacity > 0; capacity--)
    {       
        if (cur_index + MAX_DIR_NAME_LENGTH >= MAX_PATH_LENGTH)
            return cur_index <= MAX_PATH_LENGTH ? cur_index : MAX_PATH_LENGTH;
        const unsigned char *name = BPF_CORE_READ(curr_dtry, d_name.name);      
        long read_len = bpf_core_read_str(&buf[cur_index], MAX_DIR_NAME_LENGTH, name);      
        if (read_len > 1)
        {
            capacity -= (read_len - 1);         
            if (cur_index + read_len >= MAX_PATH_LENGTH)
                break;
            cur_index += read_len;      
            struct dentry *parent = BPF_CORE_READ(curr_dtry, d_parent);     
            if (parent != lastdtryp)
            {
                lastdtryp = parent;
                curr_dtry = parent;
                buf[cur_index - 1] = '/';
                buf[cur_index] = '\0';
            }
            else
            {               
                if (buf[cur_index - 2] == '/')
                {
                    buf[cur_index - 2] = '\0';
                    cur_index = cur_index - 2;
                }
                break;
            }
        }
        else
        {
            break;
        }
    }
    return cur_index;
}

运行时出现如下错误:

=5
        341: (c7) r8 s>>= 32                  ; frame1: R8_w=scalar(umin=2566,umax=4084,var_off=(0x800; 0x7ff),s32_min=2048,s32_max=4095,u32_min=2048,u32_max=4095) refs=5
        ; for (int capacity = MAX_PATH_LENGTH; capacity > 0; capacity--)
        342: (b7) r2 = 1                      ; frame1: R2_w=1 refs=5
        343: (6d) if r2 s> r8 goto pc+6       ; frame1: R2_w=1 R8_w=scalar(umin=2566,umax=4084,var_off=(0x800; 0x7ff)) refs=5
        344: (bf) r2 = r7                     ; frame1: R2_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) R7_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) refs=5
        345: (57) r2 &= 65535                 ; frame1: R2_w=scalar(umax=1530,var_off=(0x0; 0x7ff)) refs=5
        346: (bf) r9 = r7                     ; frame1: R7_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) R9_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) refs=5
        347: (bf) r6 = r1                     ; frame1: R1_w=scalar(id=32) R6_w=scalar(id=32) refs=5
        348: (b7) r1 = 3841                   ; frame1: R1_w=3841 refs=5
        349: (2d) if r1 > r2 goto pc+1        ; frame1: R0_w=scalar() R1_w=3841 R2_w=scalar(umax=1530,var_off=(0x0; 0x7ff)) R3_w=47 R6_w=scalar(id=32) R7_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) R8_w=scalar(umin=2566,umax=4084,var_off=(0x800; 0x7ff)) R9_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) R10=fp0 fp-8=mmmmmmmm fp-16=mmmmmmmm fp-24=mmmmmmmm fp-32=alloc_mem refs=5
        ; return get_path_mount_root(&f_path, buf);
        351: (bf) r3 = r6                     ; frame1: R3_w=scalar(id=32) R6_w=scalar(id=32) refs=5
        352: (b7) r1 = 40
        (truncated)
[06:22:38.693] ERROR  [EBPF] failed to load objects: field DoSysOpenat2: program do_sys_openat2: load program: permission denied: 351: (bf) r3 = r6                     ; frame1: R3_w=scalar(id=32) R6_w=scalar(id=32) refs=5: 352: (b7) r1 = 40 (truncated, 770 line(s) omitted)
exit status 1
make: *** [Makefile:5: runEbpf] Error 1

当我将任意一个函数的返回类型改为非u16类型(例如u8)时,即便后续函数期望u16类型,程序也能正常运行:

static inline u8 get_path_mount_root(const struct path *path, u8 buf[MAX_PATH_LENGTH])
{
    struct mount *mnt = get_mount(path);
    struct dentry *mnt_mountpoint = BPF_CORE_READ(mnt, mnt_mountpoint);
    return read_dentry_strings(mnt_mountpoint, buf, 0);
}
原因分析

这是eBPF验证器对16位整数类型的边界检查逻辑缺陷导致的:

  • 寄存器宽度不匹配:eBPF通用寄存器是64位,返回u16时编译器会生成& 65535的截断操作,但验证器无法正确跟踪截断后值的实际范围,误判其可能达到65535的上限。
  • 范围跟踪失效:你的函数返回的cur_index实际最大值是MAX_PATH_LENGTH(远小于65535),但验证器没识别到这个约束。当用该值作为数组索引时,会被判定存在越界风险。
  • 非16位类型的简化检查:改为u8后,验证器对8位值的范围跟踪逻辑更简单,能正确识别其最大值不会超过数组边界,因此通过检查。截断后的值零扩展为64位也不会影响业务逻辑。
解决方法

方案1:显式约束返回值范围

在每个返回u16的函数中,给返回值加上明确的上限限制,帮助验证器识别实际边界:

static inline u16 read_dentry_strings(struct dentry *dentry, u8 buf[MAX_PATH_LENGTH], u16 cur_index)
{
    // ... 原有逻辑 ...
    // 显式限制返回值不超过数组长度
    return cur_index > MAX_PATH_LENGTH ? MAX_PATH_LENGTH : cur_index;
}

调用链上的其他u16返回函数也需要添加同样的约束。

方案2:改用32位整数类型

eBPF验证器对32位整数的范围跟踪更成熟,将所有相关函数的返回类型改为u32:

static inline u32 get_file_mount_root(struct file *file, u8 buf[MAX_PATH_LENGTH])
{
    struct path f_path = BPF_CORE_READ(file, f_path);   
    return get_path_mount_root(&f_path, buf);
}

static inline u32 get_path_mount_root(const struct path *path, u8 buf[MAX_PATH_LENGTH])
{
    struct mount *mnt = get_mount(path);
    struct dentry *mnt_mountpoint = BPF_CORE_READ(mnt, mnt_mountpoint);
    return read_dentry_strings(mnt_mountpoint, buf, 0);
}

static inline u32 read_dentry_strings(struct dentry *dentry, u8 buf[MAX_PATH_LENGTH], u32 cur_index)
{
    // ... 原有逻辑,将cur_index相关检查调整为u32 ...
    return cur_index;
}

方案3:用局部变量传递范围信息

在函数内部用局部变量保存返回值,并显式做范围限制,引导验证器跟踪正确的边界:

static inline u16 read_dentry_strings(struct dentry *dentry, u8 buf[MAX_PATH_LENGTH], u16 cur_index)
{
    // ... 原有逻辑 ...
    u16 ret = cur_index;
    // 显式告知验证器返回值上限
    if (ret > MAX_PATH_LENGTH)
        ret = MAX_PATH_LENGTH;
    return ret;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 05:15:55