Linux内核中破损的KRETPROBES与OPTIMIZER简史
作者:pi3
在LKRG(Linux内核运行时守卫)的开发过程中,我发现:
- 自内核5.8版本起KRETPROBES机制存在故障(已在即将发布的内核中修复)
- 自内核5.5版本起OPTIMIZER未充分执行优化任务
技术背景:KPROBES与FTRACE
Linux内核提供两套强大的钩子框架——KPROBES和FTRACE。KPROBES作为经典框架最早发布于2.6.9内核(2004年10月),而FTRACE作为新式接口于2008年10月发布的2.6.27内核引入,具有更低的开销。其中:
- KPROBES:可在内核任意指令处插入探测点
- JPROBES:基于KPROBES实现,通过镜像原理访问被钩函数参数(2017年后已弃用)
- KRETPROBES:基于KPROBES的返回探针,支持在函数入口和返回路径执行自定义例程
当注册KPROBE时,内核会复制被探测指令并将首字节替换为断点指令(如x86架构的int3)。FTRACE则通过编译时在每个函数插入"长NOP"指令(GCC的-pg选项),注册钩子时将其替换为跳转到蹦床代码的指令。
LKRG的技术实现挑战
LKRG通过对Linux内核进行运行时完整性检查(类似微软PatchGuard技术),需要在内核多处放置钩子。当函数已被FTRACE插桩时,内核会启用"基于FTRACE的KPROBES"特殊机制——这种混合方案完全依赖FTRACE基础设施运作。
OPTIMIZER机制深度优化
内核开发者为了性能提升,积极将K*PROBES优化为FTRACE实现。现代内核中LKRG的所有KRETPROBES都被转换为FTRACE实现,但当优化不可行时仍会回退到传统KPROBES方案。
故障现象与根因分析
ALT Linux的Vitaly Chikunov报告运行FTRACE压力测试时,LKRG检测到.text段损坏。经过数月研究,发现两个关键问题:
问题一:KRETPROBES完全失效
自内核5.8版本起,非优化的KRETPROBES全部失效。根本原因在于中断处理逻辑变更:
- 5.7及之前版本:
ist_enter()
函数确保NMI处理时不触发RCU告警 - 5.8+版本:
exc_int3()
直接调用nmi_enter()
,导致pre_handler_kretprobe()
中的in_nmi()
检测始终返回真值
相关代码提交:
|
|
问题二:OPTIMIZER优化失效
编译器在函数末尾生成的INT3指令填充(0xcc)导致优化检测失败:
|
|
OPTIMIZER在can_optimize()
函数中检测到INT3操作码即放弃优化:
|
|
链接器脚本变更(commit 7705dc85)将默认填充从0x9090(NOP)改为0xcccc(INT3),导致该问题在新内核中普遍出现。
修复方案与社区贡献
问题已向内核维护者报告并获得修复:
- KRETPROBES修复:调整NMI处理逻辑,确保在KPROBE处理期间正确维持状态
- OPTIMIZER增强:忽略函数末尾的INT3填充,仅检测有效代码区的断点
相关补丁已提交至稳定版本树和LTS内核分支。通过LKRG的开发工作,我们帮助识别并修复了Linux内核中两个重要问题。
致谢
Adam