Linux内核中KRETPROBES与OPTIMIZER故障的简要解析
在LKRG(Linux内核运行时防护)开发过程中,我发现:
- 自内核5.8版本起KRETPROBES存在故障(已在后续内核中修复)
- 自内核5.5版本起OPTIMIZER未充分执行优化任务
KPROBES与FTRACE基础
Linux内核提供两种强大的钩子框架:KPROBES和FTRACE。KPROBES是经典框架(2004年10月引入),而FTRACE是较新的接口(2008年10月引入),开销更小。K*PROBES包含多种类型:
- KPROBES:可放置在内核任意指令处
- JPROBES:基于KPROBES实现,用于无缝访问被钩函数参数(2017年已弃用)
- KRETPROBES:利用KPROBES实现,允许在函数入口和返回路径执行自定义例程(不可放置于任意指令)
FTRACE工作原理完全不同:编译时在每个函数注入"长NOP"指令(GCC的-pg选项),注册FTRACE时将该指令替换为跳转到蹦床代码的JUMP指令。
LKRG与钩子机制
LKRG通过运行时完整性检查(类似微软PatchGuard)检测内核漏洞利用行为,需要在内核中放置多个钩子,其中KRETPROBES是关键组件。
当函数被FTRACE插桩且同时注册K*PROBES时,内核会使用"基于FTRACE的KPROBES"——这种特殊类型主要利用FTRACE基础设施,几乎与KPROBES本身无关。
OPTIMIZER机制
内核开发者为了性能优化,积极将K*PROBES转换为FTRACE实现。如果无法优化,则回退到经典KPROBES基础设施。在现代内核中,LKRG的所有KRETPROBES都被转换为某种FTRACE实现。
问题发现过程
ALT Linux的Vitaly Chikunov报告:运行FTRACE压力测试时,LKRG报告.text节损坏。经过数周研究,我发现两个关键问题:
问题一:KRETPROBES失效(自内核5.8起)
内核5.8中断处理逻辑变更:exc_int3()调用nmi_enter(),而pre_handler_kretprobe()在调用任何注册的KPROBE前通过in_nmi()检查是否处于NMI模式。由于此时已设置NMI标志,KRETPROBES处理程序直接返回而不执行钩子。
根本原因:提交0d00449c7a28a1514595630735df383dec606812及后续修改改变了中断处理逻辑。
解决方案:已向维护者报告并修复,补丁将回溯到稳定版本树。
问题二:OPTIMIZER优化不足(自内核5.9起)
内核5.9中,GCC在函数末尾生成INT3操作码填充:
|
|
OPTIMIZER的can_optimize()函数检查到INT3操作码时错误认为其他子系统已放置断点,从而放弃优化:
|
|
填充变更原因:链接器默认填充字符从0x9090改为0xcccc(提交7705dc8557973d8ad8f10840f61d8ec805695e9e)
解决方案:Masami Hiramatsu准备了相应修复补丁,已验证有效。
总结
通过LKRG的开发工作,我们帮助识别并修复了Linux内核中的两个重要问题。这些修复将提高内核钩子机制的可靠性和性能优化效果。
感谢Adam的工作和贡献