Skip to content

Linux 内核 BPF (Berkeley Packet Filter) 安全机制详细分析

目录

  1. BPF 系统调用
  2. BPF 验证器
  3. BPF 沙箱机制
  4. BPF 映射 (Map)
  5. BPF 程序类型
  6. JIT 编译
  7. 安全机制总结

1. BPF 系统调用

1.1 系统调用入口

BPF 系统调用的入口定义在 /Users/sphinx/github/linux/kernel/bpf/syscall.c:6360:

c
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
{
    return __sys_bpf(cmd, USER_BPFPTR(uattr), size);
}

1.2 BPF 命令 (enum bpf_cmd)

定义在 /Users/sphinx/github/linux/include/uapi/linux/bpf.h:955-997:

c
enum bpf_cmd {
    BPF_MAP_CREATE,           // 创建 BPF 映射
    BPF_MAP_LOOKUP_ELEM,     // 查找映射元素
    BPF_MAP_UPDATE_ELEM,     // 更新映射元素
    BPF_MAP_DELETE_ELEM,     // 删除映射元素
    BPF_MAP_GET_NEXT_KEY,    // 获取下一个键
    BPF_PROG_LOAD,           // 加载 BPF 程序
    BPF_OBJ_PIN,             // 将对象固定到文件系统
    BPF_OBJ_GET,             // 获取固定的对象
    BPF_PROG_ATTACH,         // 附加 BPF 程序
    BPF_PROG_DETACH,         // 分离 BPF 程序
    BPF_PROG_TEST_RUN,       // 测试运行 BPF 程序
    BPF_PROG_GET_NEXT_ID,    // 获取下一个程序 ID
    BPF_MAP_GET_NEXT_ID,     // 获取下一个映射 ID
    // ... 更多命令
    BPF_TOKEN_CREATE,        // 创建 BPF Token (用于细粒度权限控制)
    // ...
};

1.3 BPF_PROG_LOAD 流程

bpf_prog_load() 函数 (/Users/sphinx/github/linux/kernel/bpf/syscall.c:2871) 是加载 BPF 程序的核心:

c
static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
{
    // 1. 权限检查
    bpf_cap = bpf_token_capable(token, CAP_BPF);

    // 2. 指令数量限制
    if (attr->insn_cnt == 0 ||
        attr->insn_cnt > (bpf_cap ? BPF_COMPLEXITY_LIMIT_INSNS : BPF_MAXINSNS)) {
        err = -E2BIG;
        goto put_token;
    }

    // 3. 非特权用户限制
    if (type != BPF_PROG_TYPE_SOCKET_FILTER &&
        type != BPF_PROG_TYPE_CGROUP_SKB &&
        !bpf_cap)
        goto put_token;

    // 4. 网络管理权限检查
    if (is_net_admin_prog_type(type) && !bpf_token_capable(token, CAP_NET_ADMIN))
        goto put_token;

    // 5. 性能监控权限检查
    if (is_perfmon_prog_type(type) && !bpf_token_capable(token, CAP_PERFMON))
        goto put_token;

    // 6. 安全模块检查 (SELinux/AppArmor)
    err = security_bpf_prog_load(prog, attr, token, uattr.is_kernel);

    // 7. 运行 BPF 验证器
    err = bpf_check(&prog, attr, uattr, uattr_size);

    // 8. 选择运行时 (JIT 或解释器)
    prog = bpf_prog_select_runtime(prog, &err);
}

1.4 权限分类

定义在 /Users/sphinx/github/linux/kernel/bpf/syscall.c:2759-2798:

c
// 需要 CAP_NET_ADMIN 权限的程序类型
static bool is_net_admin_prog_type(enum bpf_prog_type prog_type)
{
    switch (prog_type) {
    case BPF_PROG_TYPE_SCHED_CLS:  // 调度分类器
    case BPF_PROG_TYPE_SCHED_ACT:  // 调度动作
    case BPF_PROG_TYPE_XDP:        // 快速数据包处理
    case BPF_PROG_TYPE_LWT_IN:
    case BPF_PROG_TYPE_LWT_OUT:
    case BPF_PROG_TYPE_LWT_XMIT:
    // ... 更多网络相关类型
    return true;
    }
}

// 需要 CAP_PERFMON 权限的程序类型
static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
{
    switch (prog_type) {
    case BPF_PROG_TYPE_KPROBE:      // 内核探针
    case BPF_PROG_TYPE_TRACEPOINT:  // 跟踪点
    case BPF_PROG_TYPE_PERF_EVENT: // 性能事件
    case BPF_PROG_TYPE_RAW_TRACEPOINT:
    // ...
    return true;
    }
}

// 无特权用户可加载的程序类型 (仅需 CAP_BPF)
static bool is_unprivileged_allow(enum bpf_prog_type prog_type)
{
    switch (prog_type) {
    case BPF_PROG_TYPE_CGROUP_SKB:  // 始终允许
    case BPF_PROG_TYPE_SK_REUSEPORT: // 相当于 SOCKET_FILTER
    default:
        return false;
    }
}

2. BPF 验证器

2.1 验证器概述

BPF 验证器 (/Users/sphinx/github/linux/kernel/bpf/verifier.c) 是 BPF 安全的核心组件。它通过静态分析确保 BPF 程序不会危害系统。

验证器文档注释 (/Users/sphinx/github/linux/kernel/bpf/verifier.c:56-70):

c
/* bpf_check() is a static code analyzer that walks eBPF program
 * instruction by instruction and updates register/stack state.
 * All paths of conditional branches are analyzed until 'bpf_exit' insn.
 *
 * The first pass is depth-first-search to check that the program is a DAG.
 * It rejects the following programs:
 * - larger than BPF_MAXINSNS insns
 * - if loop is present (detected via back-edge)
 * - unreachable insns exist (shouldn't be a forest. program = one function)
 * - out of bounds or malformed jumps
 * The second pass is all possible path descent from the 1st insn.
 * Since it's analyzing all paths through the program, the length of the
 * analysis is limited to 64k insn, which may be hit even if total number of
 * insn is less then 4K, but there are too many branches that change stack/regs.
 * Number of 'branches to be analyzed' is limited to 1k
 */

2.2 BPF 指令结构

定义在 /Users/sphinx/github/linux/include/uapi/linux/bpf.h:80-86:

c
struct bpf_insn {
    __u8    code;       /* 操作码 */
    __u8    dst_reg:4;  /* 目标寄存器 (0-9) */
    __u8    src_reg:4;  /* 源寄存器 (0-9) */
    __s16   off;        /* 有符号偏移量 */
    __s32   imm;        /* 有符号立即数 */
};

2.3 寄存器类型 (enum bpf_reg_type)

BPF 验证器使用多种寄存器类型来跟踪值:

c
enum bpf_reg_type {
    NOT_INIT = 0,           // 未初始化
    SCALAR_VALUE,           // 标量值 (不是指针)
    PTR_TO_CTX,             // 指向上下文的指针 (如 sk_buff)
    PTR_TO_MAP_KEY,         // 指向映射键的指针
    PTR_TO_MAP_VALUE,       // 指向映射值的指针
    PTR_TO_STACK,           // 指向栈的指针
    PTR_TO_SOCKET,          // 指向套接字的指针
    PTR_TO_SOCK_COMMON,     // 指向 sock_common 的指针
    // ... 更多指针类型
};

2.4 验证器环境结构

定义在 /Users/sphinx/github/linux/include/linux/bpf_verifier.h:748-789:

c
struct bpf_verifier_env {
    u32 insn_idx;              // 当前指令索引
    u32 prev_insn_idx;
    struct bpf_prog *prog;    // 被验证的 BPF 程序
    const struct bpf_verifier_ops *ops;
    struct bpf_verifier_stack_elem *head; // 验证状态栈
    int stack_size;           // 待处理状态数
    bool strict_alignment;    // 执行严格指针对齐检查
    bool test_state_freq;     // 使用不同修剪频率测试验证器
    struct bpf_verifier_state *cur_state; // 当前验证状态
    struct list_head *explored_states; // 搜索修剪优化
    struct bpf_map *used_maps[MAX_USED_MAPS]; // 程序使用的映射
    u32 used_map_cnt;
    u32 id_gen;               // 生成唯一寄存器 ID
    bool allow_ptr_leaks;     // 允许指针泄漏 (仅特权)
    bool allow_uninit_stack;  // 允许访问未初始化栈内存
    bool bpf_capable;         // 是否具有 CAP_BPF 权限
    bool bypass_spec_v1;       // 绕过安全检查 v1
    bool bypass_spec_v4;      // 绕过安全检查 v4
    struct bpf_insn_aux_data *insn_aux_data; // 每条指令的辅助状态
    // ...
};

2.5 check_cfg() - 控制流图检查

定义在 /Users/sphinx/github/linux/kernel/bpf/verifier.c:18953-19039:

c
static int check_cfg(struct bpf_verifier_env *env)
{
    int insn_cnt = env->prog->len;
    int *insn_stack, *insn_state;

    // 分配状态和栈数组
    insn_state = env->cfg.insn_state = kvzalloc_objs(int, insn_cnt, GFP_KERNEL_ACCOUNT);
    insn_stack = env->cfg.insn_stack = kvzalloc_objs(int, insn_cnt, GFP_KERNEL_ACCOUNT);

    insn_state[0] = DISCOVERED;
    insn_stack[0] = 0;
    env->cfg.cur_stack = 1;

walk_cfg:
    while (env->cfg.cur_stack > 0) {
        int t = insn_stack[env->cfg.cur_stack - 1];
        ret = visit_insn(t, env);
        // 处理 DONE_EXPLORING, KEEP_EXPLORING 或错误
    }

    // 检查是否有未到达的指令
    for (i = 0; i < insn_cnt; i++) {
        if (insn_state[i] != EXPLORED) {
            verbose(env, "unreachable insn %d\n", i);
            ret = -EINVAL;
            goto err_free;
        }
        // 检查 ldimm64 指令完整性
        if (bpf_is_ldimm64(insn)) {
            if (insn_state[i + 1] != 0) {
                verbose(env, "jump into the middle of ldimm64 insn %d\n", i);
                ret = -EINVAL;
                goto err_free;
            }
            i++;
        }
    }
}

check_cfg() 安全检查:

  • 检测循环 (通过回边检测)
  • 检测不可达指令
  • 检测跳转到 ldimm64 指令中间
  • 确保程序是一个单一函数 (非森林)

2.6 check_subprogs() - 子程序检查

定义在 /Users/sphinx/github/linux/kernel/bpf/verifier.c:3686-3684:

c
static int check_subprogs(struct bpf_verifier_env *env)
{
    // 检查所有跳转都在同一子程序内
    for (i = 0; i < insn_cnt; i++) {
        // 验证跳转目标在当前子程序范围内
        if (跳转目标不在当前子程序) {
            verbose(env, "jump out of subprog %d\n", cur_subprog);
            return -EINVAL;
        }
    }
}

2.7 do_check() - 主验证循环

定义在 /Users/sphinx/github/linux/kernel/bpf/verifier.c:21244-21365:

c
static int do_check(struct bpf_verifier_env *env)
{
    for (;;) {
        struct bpf_insn *insn = &insns[env->insn_idx];

        // 检查指令处理数量限制
        if (++env->insn_processed > BPF_COMPLEXITY_LIMIT_INSNS) {
            verbose(env, "BPF program is too large. Processed %d insn\n",
                    env->insn_processed);
            return -E2BIG;
        }

        // 检查是否需要剪枝
        if (is_prune_point(env, env->insn_idx)) {
            err = is_state_visited(env, env->insn_idx);
            if (err == 1) {
                // 发现等价状态,可以剪枝搜索
                goto process_bpf_exit;
            }
        }

        // 处理每条指令
        err = do_check_insn(env, &do_print_state);
        if (err >= 0) {
            marks_err = bpf_commit_stack_write_marks(env);
        }

        env->insn_idx++;
    }
}

2.8 check_helper_call() - 辅助函数调用检查

定义在 /Users/sphinx/github/linux/kernel/bpf/verifier.c:11640-11750:

c
static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn, int *insn_idx_p)
{
    enum bpf_prog_type prog_type = resolve_prog_type(env->prog);
    const struct bpf_func_proto *fn = NULL;

    // 1. 获取函数原型
    func_id = insn->imm;
    err = get_helper_proto(env, insn->imm, &fn);

    // 2. 检查上下文
    err = check_context_access(env, insn, off, size, type, info);

    // 3. 检查参数类型
    for (i = 0; i < 5; i++) {
        err = check_func_arg(env, reg, arg_type, &meta);
    }

    // 4. 记录参考对象
    if (fn->ret_type == RET_PTR_TO_MEM ||
        fn->ret_type == RET_PTR_TO_MAP_VALUE_OR_NULL) {
        err = acquire_reference(env, insn_idx);
    }
}

3. BPF 沙箱机制

3.1 指令限制

最大指令数限制 (/Users/sphinx/github/linux/kernel/bpf/verifier.c:21271):

c
// 复杂程序限制
if (++env->insn_processed > BPF_COMPLEXITY_LIMIT_INSNS) {
    return -E2BIG;
}

// 非特权用户限制更严格
if (attr->insn_cnt > (bpf_cap ? BPF_COMPLEXITY_LIMIT_INSNS : BPF_MAXINSNS)) {
    return -E2BIG;
}

复杂度限制常量 (/Users/sphinx/github/linux/kernel/bpf/verifier.c:195-196):

c
#define BPF_COMPLEXITY_LIMIT_JMP_SEQ  8192   // 最大跳转序列复杂度
#define BPF_COMPLEXITY_LIMIT_STATES   64     // 每个指令的最大状态数

3.2 内存访问限制

栈大小限制 (/Users/sphinx/github/linux/include/linux/filter.h:99):

c
/* BPF program can access up to 512 bytes of stack space. */
#define MAX_BPF_STACK  512

栈边界检查 (/Users/sphinx/github/linux/kernel/bpf/verifier.c:7604-7617):

c
static int check_stack_slot_within_bounds(struct bpf_verifier_env *env,
                                          s64 off, struct bpf_func_state *state,
                                          enum bpf_access_type t)
{
    int min_valid_off;

    if (t == BPF_WRITE || env->allow_uninit_stack)
        min_valid_off = -MAX_BPF_STACK;  // -512
    else
        min_valid_off = -state->allocated_stack;

    if (off < min_valid_off || off > -1)
        return -EACCES;
}

内存访问检查 (/Users/sphinx/github/linux/kernel/bpf/verifier.c:5854-5880):

c
static int __check_mem_access(struct bpf_verifier_env *env, int regno,
                              int off, int size, u32 mem_size,
                              bool zero_size_allowed)
{
    bool size_ok = size > 0 || (size == 0 && zero_size_allowed);

    // 基本边界检查
    if (off >= 0 && size_ok && (u64)off + size <= mem_size)
        return 0;

    // 根据寄存器类型给出具体错误信息
    switch (reg->type) {
    case PTR_TO_MAP_KEY:
        verbose(env, "invalid access to map key, key_size=%d off=%d size=%d\n", ...);
        break;
    case PTR_TO_MAP_VALUE:
        verbose(env, "invalid access to map value, value_size=%d off=%d size=%d\n", ...);
        break;
    // ...
    }
    return -EACCES;
}

变量偏移量限制 (/Users/sphinx/github/linux/include/linux/bpf_verifier.h:16):

c
#define BPF_MAX_VAR_OFF (1 << 29)  // 约 512MB

3.3 辅助函数白名单

BPF 辅助函数 ID 枚举 (/Users/sphinx/github/linux/include/uapi/linux/bpf.h:6130-6134):

c
enum bpf_func_id {
    BPF_FUNC_map_lookup_elem = 1,
    BPF_FUNC_map_update_elem = 2,
    BPF_FUNC_map_delete_elem = 3,
    BPF_FUNC_probe_read = 4,
    BPF_FUNC_ktime_get_ns = 5,
    // ... 更多函数 (共 211 个)
    __BPF_FUNC_MAX_ID,
};

辅助函数调用检查 (/Users/sphinx/github/linux/kernel/bpf/verifier.c:11620-11628):

c
static int get_helper_proto(struct bpf_verifier_env *env, int func_id,
                           const struct bpf_func_proto **ptr)
{
    if (func_id < 0 || func_id >= __BPF_FUNC_MAX_ID)
        return -ERANGE;

    if (!env->ops->get_func_proto)
        return -EINVAL;

    *ptr = env->ops->get_func_proto(func_id, env->prog);
    return *ptr && (*ptr)->func ? 0 : -EINVAL;
}

每个程序类型有其自己的辅助函数集合:

c
// sched_cls、xdp 等程序类型允许的辅助函数
case BPF_PROG_TYPE_SCHED_CLS:
case BPF_PROG_TYPE_SCHED_ACT:
case BPF_PROG_TYPE_XDP:
case BPF_PROG_TYPE_SK_SKB:
case BPF_PROG_TYPE_SK_MSG:
    // 允许访问网络数据包和映射

3.4 指针操作限制

指针算术限制 (/Users/sphinx/github/linux/kernel/bpf/verifier.c:14510-14535):

c
if (known && (val >= BPF_MAX_VAR_OFF || val <= -BPF_MAX_VAR_OFF)) {
    verbose(env, "math between %s pointer and %lld is not allowed\n", ...);
    return false;
}

if (reg->off >= BPF_MAX_VAR_OFF || reg->off <= -BPF_MAX_VAR_OFF) {
    verbose(env, "%s pointer offset %d is not allowed\n", ...);
    return false;
}

if (smin >= BPF_MAX_VAR_OFF || smin <= -BPF_MAX_VAR_OFF) {
    verbose(env, "value %lld makes %s pointer be out of bounds\n", ...);
    return false;
}

禁止指针到标量值转换 (/Users/sphinx/github/linux/kernel/bpf/verifier.c:8243-8246):

c
if (!env->allow_ptr_leaks && reg->type == SCALAR_VALUE &&
    is_spillable_regtype(ptr_reg->type)) {
    verbose(env, "R%d variable offset stack access prohibited for !root\n", ...);
    return -EACCES;
}

3.5 特殊安全标志

c
struct bpf_verifier_env {
    bool bypass_spec_v1;    // 绕过推测执行检查 v1 (Spectre v1)
    bool bypass_spec_v4;   // 绕过推测执行检查 v4 (Spectre v4)
    bool allow_ptr_leaks;  // 允许指针泄漏 (仅调试用)
    bool allow_uninit_stack; // 允许访问未初始化栈
};

4. BPF 映射 (Map)

4.1 bpf_map 结构

定义在 /Users/sphinx/github/linux/include/linux/bpf.h:296-339:

c
struct bpf_map {
    u8 sha[SHA256_DIGEST_SIZE];           // 映射内容的哈希
    const struct bpf_map_ops *ops;       // 映射操作函数集
    struct bpf_map *inner_map_meta;       // 内部映射元数据
#ifdef CONFIG_SECURITY
    void *security;                       // 安全模块数据
#endif
    enum bpf_map_type map_type;           // 映射类型
    u32 key_size;                         // 键大小
    u32 value_size;                       // 值大小
    u32 max_entries;                      // 最大条目数
    u64 map_extra;                         // 特定类型额外数据
    u32 map_flags;                         // 映射标志
    u32 id;                                // 映射 ID
    struct btf_record *record;             // BTF 记录
    int numa_node;                         // NUMA 节点
    struct btf *btf;                        // BTF 数据
    char name[BPF_OBJ_NAME_LEN];           // 映射名称
    atomic64_t refcnt;                     // 引用计数
    atomic64_t usercnt;                    // 用户计数
    bool frozen;                           // 是否冻结 (只读)
    // ...
};

4.2 映射类型 (enum bpf_map_type)

定义在 /Users/sphinx/github/linux/include/uapi/linux/bpf.h:999-1049:

类型说明权限要求
BPF_MAP_TYPE_HASH哈希映射CAP_BPF
BPF_MAP_TYPE_ARRAY数组映射CAP_BPF
BPF_MAP_TYPE_PROG_ARRAY程序数组CAP_BPF
BPF_MAP_TYPE_PERF_EVENT_ARRAY性能事件数组CAP_BPF
BPF_MAP_TYPE_PERCPU_HASH每 CPU 哈希CAP_BPF
BPF_MAP_TYPE_LRU_HASHLRU 哈希CAP_BPF
BPF_MAP_TYPE_SOCKMAP套接字映射CAP_NET_ADMIN
BPF_MAP_TYPE_DEVMAP设备映射CAP_NET_ADMIN
BPF_MAP_TYPE_XSKMAPXDP 套接字映射CAP_NET_ADMIN
BPF_MAP_TYPE_CGROUP_ARRAYCgroup 数组CAP_BPF
BPF_MAP_TYPE_RINGBUF环形缓冲区CAP_BPF
BPF_MAP_TYPE_ARENABPF 内存区域CAP_BPF

4.3 映射操作函数集

定义在 /Users/sphinx/github/linux/include/linux/bpf.h:83-187:

c
struct bpf_map_ops {
    /* 用户空间可调用的函数 (通过系统调用) */
    int (*map_alloc_check)(union bpf_attr *attr);
    struct bpf_map *(*map_alloc)(union bpf_attr *attr);
    void (*map_free)(struct bpf_map *map);
    int (*map_get_next_key)(struct bpf_map *map, void *key, void *next_key);

    /* 用户空间和 eBPF 程序都可调用的函数 */
    void *(*map_lookup_elem)(struct bpf_map *map, void *key);
    long (*map_update_elem)(struct bpf_map *map, void *key, void *value, u64 flags);
    long (*map_delete_elem)(struct bpf_map *map, void *key);
    long (*map_push_elem)(struct bpf_map *map, void *value, u64 flags);
    long (*map_pop_elem)(struct bpf_map *map, void *value);

    /* 直接值访问 */
    int (*map_direct_value_addr)(const struct bpf_map *map, u64 *imm, u32 off);
    int (*map_direct_value_meta)(const struct bpf_map *map, u64 imm, u32 *off);
    // ...
};

4.4 映射创建权限检查

定义在 /Users/sphinx/github/linux/kernel/bpf/syscall.c:1447-1505:

c
// 检查 unprivileged_bpf_disabled 设置
if (sysctl_unprivileged_bpf_disabled && !bpf_token_capable(token, CAP_BPF))
    goto put_token;

// 根据映射类型检查权限
switch (map_type) {
case BPF_MAP_TYPE_ARRAY:
case BPF_MAP_TYPE_PERCPU_ARRAY:
case BPF_MAP_TYPE_PROG_ARRAY:
    // ... 基本类型
    if (!bpf_token_capable(token, CAP_BPF))
        goto put_token;
    break;

case BPF_MAP_TYPE_SOCKMAP:
case BPF_MAP_TYPE_SOCKHASH:
case BPF_MAP_TYPE_DEVMAP:
case BPF_MAP_TYPE_XSKMAP:
    // 网络相关类型需要 CAP_NET_ADMIN
    if (!bpf_token_capable(token, CAP_NET_ADMIN))
        goto put_token;
    break;
}

5. BPF 程序类型

5.1 程序类型枚举 (enum bpf_prog_type)

定义在 /Users/sphinx/github/linux/include/uapi/linux/bpf.h:1060-1094:

c
enum bpf_prog_type {
    BPF_PROG_TYPE_UNSPEC,
    BPF_PROG_TYPE_SOCKET_FILTER,     // 套接字过滤器 (非特权)
    BPF_PROG_TYPE_KPROBE,            // KPROBE 探针
    BPF_PROG_TYPE_SCHED_CLS,         // 调度分类器 (网络)
    BPF_PROG_TYPE_SCHED_ACT,         // 调度动作
    BPF_PROG_TYPE_TRACEPOINT,        // 跟踪点
    BPF_PROG_TYPE_XDP,              // 快速数据包处理
    BPF_PROG_TYPE_PERF_EVENT,       // 性能事件
    BPF_PROG_TYPE_CGROUP_SKB,       // Cgroup 套接字过滤 (非特权)
    BPF_PROG_TYPE_CGROUP_SOCK,      // Cgroup 套接字
    BPF_PROG_TYPE_LWT_IN,           // LWT 入口
    BPF_PROG_TYPE_LWT_OUT,          // LWT 出口
    BPF_PROG_TYPE_LWT_XMIT,         // LWT 传输
    BPF_PROG_TYPE_SOCK_OPS,         // 套接字选项
    BPF_PROG_TYPE_SK_SKB,           // 套接字 SKB
    BPF_PROG_TYPE_CGROUP_DEVICE,   // Cgroup 设备
    BPF_PROG_TYPE_SK_MSG,           // 套接字消息
    BPF_PROG_TYPE_RAW_TRACEPOINT,   // 原始跟踪点
    BPF_PROG_TYPE_CGROUP_SOCK_ADDR, // Cgroup 套接字地址
    BPF_PROG_TYPE_LWT_SEG6LOCAL,    // SEG6 本地
    BPF_PROG_TYPE_LIRC_MODE2,       // LIRC 模式 2
    BPF_PROG_TYPE_SK_REUSEPORT,     // 套接字重用端口
    BPF_PROG_TYPE_FLOW_DISSECTOR,   // 流解析器
    BPF_PROG_TYPE_CGROUP_SYSCTL,    // Cgroup 系统控制
    BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, // 可写原始跟踪点
    BPF_PROG_TYPE_CGROUP_SOCKOPT,   // Cgroup 套接字选项
    BPF_PROG_TYPE_TRACING,          // 跟踪 (fentry/fexit/modify_return)
    BPF_PROG_TYPE_STRUCT_OPS,       // 结构操作
    BPF_PROG_TYPE_EXT,              // 扩展程序
    BPF_PROG_TYPE_LSM,             // Linux 安全模块
    BPF_PROG_TYPE_SK_LOOKUP,       // 套接字查找
    BPF_PROG_TYPE_SYSCALL,         // 系统调用程序
    BPF_PROG_TYPE_NETFILTER,       // 网络过滤器
};

5.2 各程序类型的辅助函数权限

SCHED_CLS (调度分类器) (/Users/sphinx/github/linux/kernel/bpf/verifier.c:6322-6327):

c
case BPF_PROG_TYPE_SCHED_CLS:
case BPF_PROG_TYPE_SCHED_ACT:
case BPF_PROG_TYPE_XDP:
case BPF_PROG_TYPE_LWT_XMIT:
case BPF_PROG_TYPE_SK_SKB:
case BPF_PROG_TYPE_SK_MSG:
    // 允许直接读取和写入数据包
    return true;

KPROBE 类型 (/Users/sphinx/github/linux/kernel/bpf/verifier.c:6705-6709):

c
case BPF_PROG_TYPE_KPROBE:
case BPF_PROG_TYPE_TRACEPOINT:
case BPF_PROG_TYPE_PERF_EVENT:
case BPF_PROG_TYPE_RAW_TRACEPOINT:
    return PRIV_STACK_ADAPTIVE;

5.3 程序查找类型

定义在 /Users/sphinx/github/linux/kernel/bpf/syscall.c:2268-2296:

c
static const struct bpf_prog_ops * const bpf_prog_types[] = {
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) \
    [_id] = & _name ## _prog_ops,
#define BPF_MAP_TYPE(_id, _ops)
#define BPF_LINK_TYPE(_id, _name)
#include <linux/bpf_types.h>
};

static int find_prog_type(enum bpf_prog_type type, struct bpf_prog *prog)
{
    if (type >= ARRAY_SIZE(bpf_prog_types))
        return -EINVAL;

    type = array_index_nospec(type, ARRAY_SIZE(bpf_prog_types));
    ops = bpf_prog_types[type];
    if (!ops)
        return -EINVAL;

    prog->aux->ops = ops;
    prog->type = type;
    return 0;
}

6. JIT 编译

6.1 JIT 编译概述

BPF 程序可以通过 JIT 编译器编译为本机机器码以提高性能。

JIT 入口点 (/Users/sphinx/github/linux/kernel/bpf/core.c:3087-3090):

c
/* Stub for JITs that only support cBPF. eBPF programs are interpreted. */
struct bpf_prog * __weak bpf_int_jit_compile(struct bpf_prog *prog)
{
    return prog;
}

/* Stub for JITs that support eBPF. */
void __weak bpf_jit_compile(struct bpf_prog *prog)
{
}

6.2 bpf_prog_select_runtime()

定义在 /Users/sphinx/github/linux/kernel/bpf/core.c:2546-2599:

c
struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)
{
    bool jit_needed = false;

    // 如果已有函数指针,说明已完成
    if (fp->bpf_func)
        goto finalize;

    // 检查是否需要 JIT
    if (IS_ENABLED(CONFIG_BPF_JIT_ALWAYS_ON) ||
        bpf_prog_has_kfunc_call(fp))
        jit_needed = true;

    if (!bpf_prog_select_interpreter(fp))
        jit_needed = true;

    // JIT 编译
    if (jit_needed)
        fp = bpf_int_jit_compile(fp);

finalize:
    *err = bpf_prog_lock_ro(fp);  // 锁定只读
    *err = bpf_check_tail_call(fp);  // 尾调用兼容性检查

    return fp;
}

6.3 x86_64 JIT 编译器

位置: /Users/sphinx/github/linux/arch/x86/net/bpf_jit_comp.c

关键函数:

c
struct bpf_binary_header {
    u32 size;              // 整个人员的大小
    u8  image[];          // JIT 编码的指令
};

static int bpf_jit_blind_constants(struct bpf_prog *fp)
// 混淆常量以防止信息泄漏

static int bpf_jit_emit_body(struct bpf_jit_comp *ctx)
// 发出 JIT 编译的指令

int bpf_int_jit_compile(struct bpf_prog *fp)
// 主 JIT 编译入口

6.4 JIT 安全措施

1. 代码锁定只读 (/Users/sphinx/github/linux/kernel/bpf/core.c:2587):

c
*err = bpf_prog_lock_ro(fp);  // 防止代码被修改

2. 常量盲化 (/Users/sphinx/github/linux/arch/x86/net/bpf_jit_comp.c):

c
static int bpf_jit_blind_constants(struct bpf_prog *fp)
{
    // 将敏感常量替换为随机数,计算时再恢复
    // 防止 JIT 代码泄漏敏感值
}

3. ENDBR 指令 (间接分支目标标记):

c
#ifdef CONFIG_X86_KERNEL_IBT
#define EMIT_ENDBR() EMIT(gen_endbr(), 4)
#else
#define EMIT_ENDBR()
#endif

7. 安全机制总结

7.1 BPF 安全机制层次图

+----------------------------------------------------------+
|                    用户空间                                |
|  bpf() syscall                                           |
+----------------------------------------------------------+
                          |
                          v
+----------------------------------------------------------+
|                    Capability 检查                         |
|  CAP_BPF, CAP_NET_ADMIN, CAP_PERFMON                     |
+----------------------------------------------------------+
                          |
                          v
+----------------------------------------------------------+
|                    程序类型检查                            |
|  is_net_admin_prog_type(), is_perfmon_prog_type()        |
+----------------------------------------------------------+
                          |
                          v
+----------------------------------------------------------+
|                    BPF 验证器 (核心)                       |
|  +----------------------------------------------------+  |
|  | check_cfg()     - 控制流图检查 (检测循环)            |  |
|  | check_subprogs() - 子程序检查                       |  |
|  | do_check()     - 指令逐条验证                        |  |
|  | check_helper_call() - 辅助函数调用检查               |  |
|  | check_mem_access() - 内存访问边界检查                |  |
|  +----------------------------------------------------+  |
+----------------------------------------------------------+
                          |
                          v
+----------------------------------------------------------+
|                    运行时安全                             |
|  +----------------------------------------------------+  |
|  | JIT 编译 + 常量盲化                                 |  |
|  |推测执行缓解 (Spectre)                               |  |
|  | 锁定只读内存                                        |  |
|  +----------------------------------------------------+  |
+----------------------------------------------------------+
                          |
                          v
+----------------------------------------------------------+
|                    执行环境                               |
|  +----------------------------------------------------+  |
|  | 栈大小限制: 512 字节                               |  |
|  | 指令数量限制: 100万条 (特权) / 4096条 (非特权)     |  |
|  | 跳转序列复杂度限制: 8192                           |  |
|  +----------------------------------------------------+  |
+----------------------------------------------------------+

7.2 安全检查点汇总

检查点位置说明
指令数量限制syscall.c:2932非特权用户最多 4096 条
复杂度限制verifier.c:21271最多处理 100 万条
跳转序列限制verifier.c:3023最多 8192 层
栈大小限制filter.h:99最多 512 字节
变量偏移限制bpf_verifier.h:16最大 512MB
辅助函数白名单verifier.c:11620每个程序类型不同
指针算术限制verifier.c:14510禁止越界指针运算
内存访问边界verifier.c:5854严格边界检查
控制流完整性verifier.c:18953检测循环和不可达代码

7.3 权限要求矩阵

程序/映射类型CAP_BPFCAP_NET_ADMINCAP_PERFMON
SOCKET_FILTER*--
CGROUP_SKB*--
SCHED_CLS-*-
XDP-*-
KPROBE--*
TRACEPOINT--*
MAP (基本)*--
MAP (网络)-*-
MAP (追踪)*--

7.4 BPF Token 机制

BPF Token (Linux 5.15+) 提供了更细粒度的权限控制:

c
BPF_TOKEN_CREATE  // 创建 Token
BPF_TOKEN_FD      // Token 关联到文件描述符

Token 允许:

  • 限制可以执行的 BPF 命令
  • 限制可以加载的程序类型
  • 限制可以创建的映射类型
  • 用户命名空间感知的权限检查

附录: 关键源码位置

组件路径说明
系统调用kernel/bpf/syscall.cBPF syscall 实现
验证器kernel/bpf/verifier.c安全验证核心
核心kernel/bpf/core.cJIT 和运行时
辅助函数kernel/bpf/helpers.cBPF 辅助函数实现
头文件include/linux/bpf.h核心数据结构
UAPI 头include/uapi/linux/bpf.h用户空间 API
x86 JITarch/x86/net/bpf_jit_comp.cx86_64 JIT 编译器
arm64 JITarch/arm64/net/bpf_jit_comp.cARM64 JIT 编译器
哈希映射kernel/bpf/hashtab.c哈希表映射实现
数组映射kernel/bpf/arraymap.c数组映射实现

基于 VitePress 构建