
内核加载seccomp:syscall_nr赋给了X寄存器?

最近 dbgbgtf 在编写ceccomp的代码,为了做和内核一样的bpf检查,他研究了内核对于
seccomp
系统调用传入的结构体做的检查。在检查过程中,他发现原本指令中的
LD|W|ABS
被内核替换成了LDX|W|ABS
,而这是从seccomp_data
中加载nr
等关键数据结构的指令,
这令他非常不解:难道syscall_nr其实在内核中一直是加载到X寄存器中,而非A寄存器中?
为了测试是否真正加载到X中,我们准备了如下"BPF Text"喂给ceccomp
来生成bpf用以加载到内核中:
1 | $A = $syscall_nr |
然后去trace内核中程序的输出结果(code path:
do_syscall_64 -> syscall_enter_from_user_mode -> syscall_enter_from_user_mode_work -> syscall_trace_enter -> __secure_computing
)
最终的结果很遗憾,是0,也就是说,X寄存器并没有被赋值。我们甚至大费周章地检查了运行bpf的代码,
并没有任何问题。
我提出blame一下内核源码,可以看出在修改之前,老代码并没有将LD转换成LDX。
既然“侧信道”不行,那还是得看加载bpf的代码。再次观察加载的过程,从bpf_prepare_filter
检查完用户输入的bpf,没有问题后,会调用bpf_jit_compile
/bpf_migrate_filter
来将用户的bpf转换成内核自己的代码,继续跟踪,查看bpf_convert_filter
,终于水落石出:
LD|ABS|W
会通向convert_bpf_extensions
该函数内的检查的abs的k都是有关 socket buffer
的,没有和seccomp相关的数据结构,如果是LDX|ABS|W
,那才是真正取得seccomp的数据。
1 | /* Access seccomp_data fields. */ |
BPF设计之初就是为了处理网络包的,seccomp只是借用了BPF的用法,因此内核开发者为了避免两者混淆做的 workaround无可厚非。
总结
看似内核将seccomp请求中的LD|ABS|W
换成了LDX|ABS|W
,但是在实际处理bpf的时候,
仍然是将有关数据加载到A寄存器中。有以上的代码不难发现,如果不换,那么加载的数据是
socket buffer而非 seccomp,因此为了避免干扰老代码,内核做了一个workaround,
把关于seccomp的加载绕了一道,使用LDX
来曲线救国。这也能解释为什么bpf加载数据只能用A寄存器,
不能使用X寄存器,即使bpf标准允许这么做:LD|W|ABS
和LDX|W|ABS
都有自己的含义,不能混用。
- 标题: 内核加载seccomp:syscall_nr赋给了X寄存器?
- 作者: RocketDev
- 创建于 : 2025-05-28 18:38:00
- 更新于 : 2025-05-30 03:13:00
- 链接: https://rocketma.dev/2025/05/28/LDorLDX/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。