Linux内核中KRETPROBES与OPTIMIZER的故障排查实录

本文深入分析了Linux内核自5.8版本以来存在的KRETPROBES功能失效问题,以及自5.5版本起OPTIMIZER优化机制存在的缺陷。通过LKRG开发过程中的实际案例,揭示了中断处理逻辑变更和编译器填充指令导致的兼容性问题,并给出了官方修复方案。

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填充指令触发了优化器保护机制:

1
2
3
/* 检测到其他子系统设置的断点 */
if (insn.opcode.bytes[0] == INT3_INSN_OPCODE)
    return 0;

深层原因:链接器脚本变更(提交7705dc8557973d8ad8f10840f61d8ec805695e9e)将默认填充字符从0x9090(NOP)改为0xcccc(INT3)。

解决方案:Masami Hiramatsu已提交修复补丁,经验证可正常运作。

总结

通过LKRG的开发工作,我们成功识别并协助修复了Linux内核中的两个重要问题。这再次证明开源社区协作对系统安全的重要性。

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