仅 CAP_BPF 就无法在 BPF 程序中直接访问数据包?

No direct packet access in BPF program with just CAP_BPF?

直到 Linux 5.8 CAP_SYSADMIN 需要加载除最基本的 BPF 程序之外的任何程序。最近引入的 CAP_BPF 是一个受欢迎的补充,因为它允许 运行 软件以较少的权限利用 BPF。

某些类型的 BPF 程序可以访问包数据。 4.7 之前的方法是通过 bpf_skb_load_bytes() 助手。随着验证者变得更聪明,执行“直接数据包访问”成为可能,即通过遵循上下文结构中的指针来访问数据包字节。例如:

static const struct bpf_insn prog[] = {
    // BPF_PROG_TYPE_SK_REUSEPORT: gets a pointer to sk_reuseport_md (r1).

    // Get packet data pointer (r2) and ensure length >= 2, goto Drop otherwise
    BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1,
                offsetof(struct sk_reuseport_md, data)),
    BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_1,
                offsetof(struct sk_reuseport_md, data_end)),
    BPF_MOV64_REG(BPF_REG_4, BPF_REG_2),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 2),
    BPF_JMP_REG(BPF_JGT, BPF_REG_4, BPF_REG_3, /* Drop: */ +4),

    // Ensure first 2 bytes are 0, goto Drop otherwise
    BPF_LDX_MEM(BPF_H, BPF_REG_4, BPF_REG_2, 0),
    BPF_JMP_IMM(BPF_JNE, BPF_REG_4, 0, /* Drop: */ +2),

    // return SK_PASS
    BPF_MOV32_IMM(BPF_REG_0, SK_PASS),
    BPF_EXIT_INSN(),

    // Drop: return SK_DROP
    BPF_MOV32_IMM(BPF_REG_0, SK_DROP),
    BPF_EXIT_INSN()
};

需要明确确保访问的字节在范围内。否则验证者将拒绝该程序。

如果调用方有CAP_SYSADMIN,则上述程序加载成功。据说,CAP_BPF 也应该足够了,但事实并非如此 (Linux 5.13)。早期内核的行为类似。验证器输出如下:

Permission denied
0: (79) r2 = *(u64 *)(r1 +0)
1: (79) r3 = *(u64 *)(r1 +8)
2: (bf) r4 = r2
3: (07) r4 += 2
4: (2d) if r4 > r3 goto pc+4
R3 pointer comparison prohibited
processed 5 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

我知道任意指针比较受到限制,因为它揭示了内核内存布局。但是,将指向数据包数据偏移一定量的指针与指向数据包末端的指针进行比较是安全的。

我想找到一种无需授权即可加载程序的方法 CAP_SYSADMIN

  1. 有没有一种不触发指针比较错误的方式来编写边界检查?

    相关代码在check_cond_jmp_op()中。看起来即使使用最新的内核版本,也无法摆脱指针比较。

  2. 如果没有办法以让验证者满意的方式编写边界检查,我想知道取消限制是否在路线图上。

  3. 作为解决方法,我可以在 CAP_BPF 之上授予 CAP_PERFORM,移除指针比较的“禁运”。程序加载成功。我可能可以使用 seccomp 限制 perf_event_open() 和其他多余的位。不过感觉不太好。

Reproducer.

要在您的程序中进行直接数据包访问,除了 CAP_BPF 之外,您还需要 CAP_PERFMON。我不知道有什么办法。

为什么?

由于 Spectre 漏洞,有人能够对无限指针(即所有 except stack and map value pointers) can read arbitrary memory via speculative out-of-bounds loads.

因此需要禁止非特权用户进行此类操作。允许 CAP_BPF 用户执行这些操作实质上会授予 CAP_BPF 对任意内存的读取权限。由于这些原因,我怀疑将来会取消此限制。