Linux内核中破损的KRETPROBES和OPTIMIZER简史
在LKRG开发过程中,我发现:
- 自内核5.8起KRETPROBES功能失效(将在新内核中修复)
- 自内核5.5起OPTIMIZER未充分履行职责
初探KPROBES与FTRACE
Linux内核提供两大钩子框架——KPROBES和FTRACE。KPROBES作为经典框架最早出现于2.6.9版本(2004年10月),而FTRACE作为新式接口于2008年10月随内核2.6.27引入,其开销相对更小。
K*PROBES家族包含:
- KPROBES:可在内核任意指令处插入断点
- JPROBES:基于KPROBES实现,通过镜像原理访问被钩函数参数(2017年后已弃用)
- KRETPROBES:基于KPROBES实现,允许在函数入口和返回路径执行自定义例程(不可用于任意指令)
FTRACE工作原理截然不同:编译时在每个函数头部插入"长NOP"指令(GCC的-pg选项),注册钩子时将其替换为跳转指令指向蹦床代码。
Linux内核运行时守护者LKRG
LKRG作为运行时内核完整性检查工具(类似微软PatchGuard),通过KRETPROBES实现内核钩子植入,用于检测内核篡改和漏洞利用行为。
FTRACE插桩函数上的LKRG KPROBE
当函数已被FTRACE插桩时注册K*PROBES,内核会启用"基于FTRACE的KPROBES"——这种特殊类型完全依赖FTRACE基础设施,遵循FTRACE规则(如禁用FTRACE将导致钩子失效)。
OPTIMIZER机制
为提升性能,内核积极将K*PROBES优化为FTRACE实现。若优化失败则回退到传统KPROBES机制。在现代内核中,LKRG的所有KRETPROBES均被转换为FTRACE实现。
LKRG误报事件
ALT Linux的Vitaly Chikunov报告称,运行FTRACE压力测试时LKRG误报.text段损坏。经排查发现两个关键问题:
问题一:KRETPROBES自内核5.8起失效
内核5.8对中断处理逻辑进行重大调整:exc_int3()
函数开始调用nmi_enter()
,而pre_handler_kretprobe()
会通过in_nmi()
检查避免NMI上下文执行。这导致所有非优化KRETPROBES被跳过。
根本原因:提交0d00449c7a28a1514595630735df383dec606812及后续修改改变了中断处理流程。
现状:该问题已向维护者报告并修复,补丁将回溯到稳定版本树。
问题二:OPTIMIZER自内核5.5起优化不足
内核5.9中LKRG钩子未能优化为FTRACE实现,原因在于编译器在函数末尾添加的INT3填充指令触发了优化器保护机制:
|
|
深层原因:链接器脚本变更(提交7705dc8557973d8ad8f10840f61d8ec805695e9e)将默认填充字符从0x9090(NOP)改为0xcccc(INT3)。
解决方案:Masami Hiramatsu已提交修复补丁,经验证可正常运作。
总结
通过LKRG的开发工作,我们成功识别并协助修复了Linux内核中的两个重要问题。这再次证明开源社区协作对系统安全的重要性。