Linux内核中KRETPROBES与OPTIMIZER故障的简要解析

本文详细分析了Linux内核自5.8版本以来KRETPROBES机制失效及OPTIMIZER优化不足的问题,探讨了其与FTRACE框架的交互影响,并介绍了相关修复方案。

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操作码填充:

1
2
3
ffffffff81305296:       cc                      int3
ffffffff81305297:       cc                      int3
...

OPTIMIZER的can_optimize()函数检查到INT3操作码时错误认为其他子系统已放置断点,从而放弃优化:

1
2
3
/* Another subsystem puts a breakpoint */
if (insn.opcode.bytes[0] == INT3_INSN_OPCODE)
    return 0;

填充变更原因:链接器默认填充字符从0x9090改为0xcccc(提交7705dc8557973d8ad8f10840f61d8ec805695e9e)

解决方案:Masami Hiramatsu准备了相应修复补丁,已验证有效。

总结

通过LKRG的开发工作,我们帮助识别并修复了Linux内核中的两个重要问题。这些修复将提高内核钩子机制的可靠性和性能优化效果。

感谢Adam的工作和贡献

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计