本文地址:https://www.ebpf.top/post/linux-7-3-bpf-features

本文基于 Linux 7.3-rc7(2026-10-12)整理,正式版如有出入将另行更正。

1. 引子:373 个提交里的 BPF 全景

8 月 20 日,Linus 合并了 Daniel Borkmann 提交的 bpf-next-7.3 主 pull,一共 373 个提交。pull 说明里列出的五条主线是:

  1. 验证器报错重设计;
  2. kfunc / struct_ops 支持 arena 参数;
  3. 签名 loader 重做;
  4. ksock kfunc;
  5. helper 与 kfunc 参数校验统一。

除此之外,经 VFS 树合入的还有两块和 BPF 强相关的内容:binfmt_misc 的 BPF handler,以及 bpf_sock_read_xattr()。据 LWN 合并窗口统计(第一周、第二周),本周期 kfunc 删除 3 个、新增 32 个。

先用一张表看全貌:

方向 7.3 新增 主要受益者
exec 路径 binfmt_misc BPF handler Nix 等可重定位二进制、qemu-user 场景
socket bpf_sock_read_xattr()、ksock kfunc BPF LSM 策略、内核内直接上报遥测
kfunc 机制 KF_SPINLOCK_SAFE、__arena、参数校验统一 kfunc 与 struct_ops 作者、sched_ext
全局数据 SEC(".percpu") 需要每 CPU 计数的程序
验证器 结构化诊断报告 所有写 BPF 的人和 AI 智能体
签名 内核校验元数据 需要可信分发的发行版与平台

下面按"BPF 伸进了哪些新地方、机制如何演进、开发体验和安全"的顺序展开。

2. exec 路径可编程:binfmt_misc 的 BPF handler

2.1 痛点

Nix 风格的可重定位、hermetic 二进制,动态加载器的位置只能相对于二进制自身来确定。可 ELF 的 PT_INTERP 和 binfmt_misc 的解释器都是固定字符串,表达不了"相对路径",过去只能靠 patchelf 改写或者套 wrapper 脚本。

2.2 被否决的方案

最直观的做法是在 PT_INTERP 里展开 $ORIGIN,但它牵扯 AT_SECURE、LSM 和加载器注入风险,而且 bprm->file 可能根本没有可用路径,Christian Brauner 否决了这个方案;做一个可插拔的 ELF 解释器模块注册表的思路也没被接受。最终的答案是:把决策交给 BPF(合入提交,LWN 报道)。

2.3 struct_ops:match() 与 load()

1
2
3
4
5
struct binfmt_misc_ops {
    bool (*match)(struct linux_binprm *bprm);
    int  (*load)(struct linux_binprm *bprm);
    char name[BINFMT_MISC_OPS_NAME_MAX];
};
  • 两个回调都可睡眠,bprm 以可信 BTF 指针传入;
  • match 可以用 bpf_dynptr_from_file() 读文件任意位置,不再局限于预取的 256 字节 bprm->buf;
  • 一旦 match 命中,执行就归这个 handler:load 失败则 exec 失败,不会回落到后面的条目;返回 -ENOEXEC 时交给其余 binfmt。

2.4 新 kfunc

只允许在 load 中调用(验证器按程序挂载的 struct_ops 成员区分):

  • bpf_binprm_set_interp(bprm, path, path__sz)
  • bpf_binprm_set_interp_arg(...)
  • bpf_binprm_set_flags(bprm, flags)
  • bpf_binprm_select_interp()(后续补丁加入)

配合 bpf_path_d_path(&bprm->file->f_path) 就能拿到二进制自身的位置,进而拼出相对的加载器路径。

2.5 两种调度模式

相关提交:

  • ‘T’ 透明调度:二进制通过 AT_EXECFD 交给解释器,argv 保持原样,mm->exe_file 和 comm 仍是二进制本身,并置 AT_FLAGS_TRANSPARENT_INTERP;BPF 侧对应 BPF_BINPRM_TRANSPARENT。
  • ‘L’ 加载器替换:原生执行二进制,只替换 PT_INTERP,适用于本架构、带 PT_INTERP 的 ELF。

2.6 注册、预绑定解释器与 ‘D’

struct_ops attach 后,handler 按注册任务所在的 user namespace 登记,再用新的 ‘B’ 类型激活:

1
echo ':origin:B::::nix:' > /proc/sys/fs/binfmt_misc/register

为了防止有人通过挂载命名空间把解释器路径换掉,可以先用 ‘D’(registered disabled)以禁用状态注册条目、预绑定解释器,再写入 1 激活;首次激活后不可再添加解释器绑定(相关提交):

1
2
3
4
5
cd /proc/sys/fs/binfmt_misc
echo ':qemu:B::::qemu_user:D' > register
echo '+aarch64 /usr/bin/qemu-aarch64' > qemu
echo '+arm /usr/bin/qemu-arm' > qemu
echo 1 > qemu

预打开的数量按 ucounts 计入 /proc/sys/user/max_binfmt_misc_interpreters。

2.7 安全模型,以及一个越界读修复

计算出的解释器用 open_exec() 以调用者凭据打开,并作为下一级 binprm 走完整的 LSM 检查,也就是说程序"只能把调用者重定向到它本来就能 exec 的东西"。

不过 kfunc 本身也会出错:bpf_binprm_select_interp() 先 strnlen 再 strcmp,两步之间存在 TOCTOU,最多越界读 31 字节,Chris Mason 于 9 月 18 日修复。对写 kfunc 的人来说,这是个很好的提醒:来自 BPF 的缓冲区,长度检查和使用之间不能留窗口。

2.8 一个 handler 的工作流程

以 Nix 场景为例:match 回调读取 ELF 头或自定义 note,判断是否为目标二进制;load 回调用 bpf_path_d_path() 取得二进制自身路径,拼出 <dir>/../lib/ld-linux.so,再调用 bpf_binprm_set_interp() 设置解释器,并用 bpf_binprm_set_flags() 选择 ‘L’ 模式。用户态 attach struct_ops 后写入 ‘B’ 条目即可生效。完整可运行的写法可参考内核源码 tools/testing/selftests/exec/ 下的 binfmt_misc BPF 测试。

整条路径可以用下图概括:

图 1:binfmt_misc BPF handler 的 exec 流程(示意图,作者据合入提交自绘)

3. socket 新能力:xattr 标签与 ksock 发包

3.1 bpf_sock_read_xattr():给 socket 打标签

从 7.1 开始 socket 支持扩展属性,systemd 会给 socket 打 user.* xattr,用于限速、varlink registry 等。7.3 新增的 bpf_sock_read_xattr() 让 BPF LSM 能把这些标签读回来:

  • 输入 struct socket,从 sockfs inode 读出 user.* xattr 写入 dynptr,接口仿照 bpf_cgroup_read_xattr();
  • 注册为 KF_RCU,只开放给 BPF LSM 程序;
  • 底层走 simple_xattr_get() 的 RCU rhashtable 查找,不睡眠也不加锁,可睡眠和不可睡眠的 LSM 钩子都能用。

典型用法:用户态对 socket 执行 fsetxattr(fd, "user.tag", ...),BPF LSM 挂在 bind/connect 钩子上按标签放行或拒绝,相当于给 socket 加了一套由用户态定义的"标签策略"。

3.2 ksock kfunc:BPF 自己发 UDP

这组 kfunc 的目标是让 BPF 程序直接把遥测发到网络上,不经过用户态 agent。7.3 落地了 5 个 kfunc:bpf_ksock_create/connect/send/acquire/release。

  • 目前只支持 UDP;
  • create 和 connect 只能在 SYSCALL 程序中调用,send/acquire/release 在 SYSCALL 和 LSM 程序中都可用;
  • 对象引用计数后存入 kptr map,释放走 queue_rcu_work;
  • socket 操作照常经过 LSM 钩子,挂在 security_socket_sendmsg 上的程序禁止调用这组 kfunc,以免递归;create 会拷贝参数防 TOCTOU。

使用模式:先用 BPF_PROG_RUN 跑一个 SYSCALL 程序创建并连接 socket,存进 kptr map;之后 LSM 程序在事件发生时直接 send。

4. kfunc 机制演进:从隐式白名单到显式声明

4.1 KF_SPINLOCK_SAFE

持有 bpf_spin_lock 时能调哪些 kfunc,以前写死在验证器的 kfunc_spin_allowed() 白名单里,模块里的 kfunc 根本加不进去。7.3 改成声明式:

1
BTF_ID_FLAGS(func, my_kfunc, KF_SPINLOCK_SAFE)

graph、数值迭代器、res spin lock、arena、stream 等现有 kfunc 都补上了标记,白名单随之删除。这组补丁的作者是 Kaitao Cheng。

4.2 __arena / __arena__nullable:arena 指针直通

以前 arena 指针只能当裸标量传给 kfunc,每个 kfunc 自己"加基址再强转";struct_ops 回调收到的也是 u64。7.3 之后(kfunc 侧、struct_ops 侧):

1
2
3
4
/* 以前 */
__bpf_kfunc void foo(u64 p);          /* 内部手工换算 arena 基址 */
/* 7.3 */
__bpf_kfunc void foo(struct node *n__arena);
  • JIT 在调用点重定基:rN = kern_vm_start + (u32)rN。不需要边界检查,因为偏移落在带保护填充的映射里,访问未填充页由 arena 的 scratch page 兜底;
  • __arena 无条件重定基(NULL 也会变成基址),需要保留 NULL 用 __arena__nullable;
  • JIT 需声明 bpf_jit_supports_arena_args(),否则返回 -ENOTSUPP;
  • struct_ops 方向,trampoline 保存参数时做 ctx[slot] = (u32)(kaddr - kern_vm_start),程序拿到的是 PTR_TO_ARENA(仅单程序的间接 trampoline)。

sched_ext-for-7.3 已经用上了这套机制,arena 对象以带类型的指针跨越内核/BPF 边界。

4.3 helper 与 kfunc 参数校验统一

Amery Hung 的系列让 kfunc 参数完全按 BTF 分类,在 add-call 时生成一次 bpf_func_proto,helper 和 kfunc 从此走同一套校验逻辑。

5. 全局 per-CPU 数据:SEC(".percpu")

内核里有 DEFINE_PER_CPU(),BPF C 里一直没有,只能用 1 个条目的 percpu_array 凑合。7.3 配合 libbpf 1.8 之后,只要把全局变量放进 SEC(".percpu") 段,它就成了每个 CPU 一份的变量,程序里像普通全局变量一样读写,用户态通过骨架读出各 CPU 的值。

  • 底层仍是 percpu_array map,但 per-CPU 访问被内联成指令;
  • libbpf 会探测内核支持,bpftool 骨架支持该段;
  • 同一系列修复了拷贝 per-CPU map 值时泄露未初始化内核堆内存的问题;
  • 已知问题:Clang 17 上 1 字节 percpu 变量会崩,请用新版 Clang。

作者 Leon Hwang,合并时已是 v12(合入提交)。

6. 验证器报错重设计:让验证器“说人话”

写过 BPF 的人都见过这种报错:

R1 type=scalar expected=fp

然后只能在几千行逐指令日志里找原因。7.3 的这次重设计,说白了就是让验证器“说人话”:除了逐指令日志,还直接告诉你错在哪、为什么错、可以怎么改。在 AI 智能体写代码的场景下,这条反馈回路更成了瓶颈(呼应本号《智能体时代下的 BPF:自我变革迫在眉睫》)。

7.3 新增了 kernel/bpf/diagnostics.c,在原有 verbose 日志之后追加一份结构化报告:

Verification failed: <category>: <problem>
  Reason:        ...
  At:            源码行 + 指令
  Causal path:   压缩后的分支与状态事件
  Suggestion:    ...
  • 覆盖类别:Register Type Safety、Memory Safety、Resource Lifetime Safety、Call Type Safety、Execution Context Safety、Program Structure and Policy,目前只覆盖部分报错点,会逐步迁移;
  • 诊断历史放在上限 64MiB 的轮转缓冲区,每条因果路径保留最早和最新各 32 个事件;
  • merge 时为 v5、14 个补丁,作者 Kumar Kartikeya Dwivedi(合入提交)。

merge 说明里的真实例子:cpumask/test_populate_invalid_destination,旧报错就是上面那行 R1 type=scalar expected=fp;新报告会直接告诉你 bpf_cpumask_populate 的第一个参数需要 24 字节的 struct bpf_cpumask 内存,并定位到 cpumask_failure.c:234:8。

新旧对比一目了然:

对比项 内容
旧报错 R1 type=scalar expected=fp
新报告 指出 bpf_cpumask_populate 的第一个参数需要 24 字节的 struct bpf_cpumask 内存,并定位到 cpumask_failure.c:234:8

想自己看完整输出,可以在 7.3 的 selftests 里运行 ./test_progs -j1 -a cpumask/test_populate_invalid_destination -vv。

7. sched_ext:子调度器"功能完整"

根调度器可以把一个 cgroup 子树连同可撤销的 CPU 授权交给嵌套的子调度器。另外还有 rescue execution(任务饿死时用小额带宽兜底)、abort 路径 NMI 安全、移除 scx_bpf_cpu_rq(),scx_qmap 示例演示了分层调度(sched_ext 合入)。

8. 签名 BPF 程序:内核终于校验元数据

本号写过《内核 6.18 证书签名机制实测》。6.18 的方案只签 loader 指令,元数据 map 的哈希由 loader 在 BPF 里自己比对,内核从不校验元数据,LSM/完整性社区一直有意见。

7.3 改为:BPF_PROG_LOAD 时,在 LSM 准入钩子和验证器之前,内核把 fd_array 中独占、已冻结 map 的内容拼到指令之后一起做 PKCS#7 校验,即 insns || metadata_0 || ...。没有新增 UAPI,结果只有 BPF_SIG_UNSIGNED / BPF_SIG_VERIFIED 两种,gen_loader 也不再携带校验逻辑。文档见 Documentation/bpf/signing.rst(合入提交)。

两代方案的区别在于“谁来校验元数据、在什么时候校验”:

图 2:6.18 与 7.3 签名校验位置对比(示意图,作者据合入提交自绘)

9. 循环、mmap 与网络 helper:更多实用改进

  • bpf_for() 更快了:数值型 open-coded 迭代器被内联,每次迭代不再有 kfunc 调用开销(Puranjay Mohan),呼应本号《5种循环方式》。
  • mmap 型 array map 的 mmap 变为 O(1):内存改为惰性填充,mmap() 从与 map 大小成正比变为 O(1)(Song Liu)。
  • 网络 helper 增强:bpf_fib_lookup 支持双向 VLAN;bpf_redirect_peer() 支持 BPF_F_EGRESS;bpf_skb_adjust_room() 新增 BPF_F_ADJ_ROOM_DECAP_*。
  • 构建更省事:resolve_btfids 自动发现 kfunc 并生成 decl tag,减少对 pahole 的依赖。

此外还有各架构 JIT 增强(x86、s390、RISC-V)、timed may_goto、arena bitmap、jit_required 标志、tracing_multi link 信息进入 UAPI,以及 ringbuf 回绕、bpf_find_vma UAF 等一批修复。

10. 总结与展望:两条主线

7.3 的 BPF 有两条清晰的线:一是伸进更多内核路径(exec、socket 标签、直接发包),二是把隐式规则变成显式声明(KF_SPINLOCK_SAFE、__arena、统一参数校验、结构化报错)。后者对人和 AI 写 BPF 都是实打实的体验提升。

还在路上的:BPF 栈从 512 字节提到 2KiB、bpf_unwind 异常清理。用户态配套请升级 libbpf 1.8(USDT 优化要求 7.3+)。

延伸阅读

若还想顺着读,可看这几篇:

  1. ActPlane:把 Agent Harness Enforcement 下沉到内核 eBPF
  2. eBPF 进阶:5 种循环方式你都掌握了吗?
  3. eBPF 安全分发:内核 6.18 证书签名机制实测