diff --git a/study/kernel/00-DESCRIPTION/LIVE_PATCH.md b/study/kernel/00-DESCRIPTION/LIVE_PATCH.md index 38ecd2b..ede6c4c 100755 --- a/study/kernel/00-DESCRIPTION/LIVE_PATCH.md +++ b/study/kernel/00-DESCRIPTION/LIVE_PATCH.md @@ -1,4 +1,4 @@ -2 **子系统** +2 **LIVE_PATCH 内核热补丁** ===================== @@ -18,6 +18,19 @@ # 1 热补丁实现方案 ------- +## 1.1 热补丁方案需要解决的问题 +------- + +1. 函数热替换, 热补丁要完成的最本质的工作, 就是不重启而更新执行的函数, 怎么完成函数的替换. 修改函数的前几条指令, 将指令修改为跳转到新函数的指令可以完成这样的工作. 那有没有其他方法呢 ? + +2. 重定位问题, 正常来说驱动只能使用内核或者驱动所 EXPORT_SYMBOL 的符号, 这类符号在驱动插入的过程中, 进行重定位, 将符号指向了所引用符号的实际地址. 而热补丁则要求更宽松一些, 它需要能够直接访问被 patched 的函数中的为导出的符号信息, 这些符号正常内核 load_module 是不会进行重定位的. 只能热补丁自己来完成重定位. 这些符号的信息需要热补丁自己去甄别和重定位. + +3. 一致性问题, 内核不重启而执行新函数, 那么在系统运行过程中, 势必存在一个时间点, 之前执行了旧函数, 之后将执行新函数. 那么这个时间点怎么确认, 即怎么的模型下, 执行函数的热替换操作, 那么可以认为内核的前后是一致的, 不会出现逻辑错误, 更不会引入安全问题. 更重要的是, 如果多个被 patched 的函数之间有逻辑依赖, 一致性怎么保证. 举例来说, 假设两个函数一个执行 A++, 一个执行 A--, 每次必然成对执行. 最终 A 计数将能够正常归 0. 但是如果 patched 之后, 变成 A+=2, A-=2, 那么怎么保证不会出现, A++, A-=2 或者 A+=2, A-- 之类的执行序列. + + +## 1.2 已知热补丁方案 +------- + 在主线内核实现热补丁特性之前, 各个厂商就在社区进行了激烈的讨论, 并各自形成了自己的一套方案. | 方案 | 作者 | 实现 | 一致性模型 | 限制与约束 | @@ -25,7 +38,30 @@ | KGraft | SUSE 的 Jiri Kosina(JK) 和 Jiri Slaby(JS) 合作开发 | 基于 ftrace 方案实现, 采用类 RCU 更新机制 | 只要不会出现同一个 universe 里既调用了旧函数又调用了新函数的情况, 那么就是一致的
对于用户进程, 同一次系统调用是一次 universe
对于内核线程, 每次被唤醒, 判断是否被 stop 条件之后算是一个新的universe
对于中断, 同一次中断处理算是一次 universe | 仅支持 X86_64/s390, 未公开自动化热补丁制作工具 | | Kpatch | REDHAT | 基于 ftrace 方案, 使用 stop_machine + 栈检查来保证一致性 | 使用 stop_machine 机制停下所有 CPU, 然后对所有的进程进行栈检查, 只有在没有任何进程执行被 patched 的函数的情况下, 才认为是安全的 | stop_machine 机制对业务有中断影响, 大概带来 1ms-40ms 的延迟
仅支持 X86_64 | | kpatch without stop_machine | HITACHI 的 Masami Hiramatsu | 基于 kpatch 方案, 取消了 stop_machine一致性检查 | 不再需要 stop_machine, 引入全局的院子计数器 refcounter, 跟踪目标函数的调用情况, 只有在目标函数没有被执行时(refcounter == 0), 才可以安全的进行替换 | 基于 kpatch
一致性检查存在安全问题 | -| 直接跳转方案 | HUAWEI | 不基于 ftrace, 修改函数的前几条指令, 直接跳转到新函数 | 同 kpatch 类似, 采用 stop_machine + 栈检查来保证安全性 | 与 ftrace/kprobe 等同样修改函数指令的特性存在冲突 | +| LIVEPATCH Without Ftrace | HUAWEI | 基于直接跳转实现 livepatch, 不再基于 ftrace, 通过修改函数的前几条指令, 直接跳转到新函数 | 同 kpatch 类似, 采用 stop_machine + 栈检查来保证安全性 | 与 ftrace/kprobe 等同样修改函数指令的特性存在冲突 | + +## 1.3 热补丁方案总结 +------- + +总体来看: + +1. 实现思路主要有两类: + +| 实现方法 | 优点 | 缺点 | +|:------:|:----:|:---:| +| 基于 ftrace 的方案, 通过注册 ftrace, 修改函数的返回地址, ftrace 返回后, 直接跳转到新函数执行 | 跟 ftrace/kprobe 等同样需要修改函数指令的特性不冲突 | 依赖与 ftrace_regs 特性, 支持架构有限
ftrace 方案有性能问题, 虽然使用 x86_64 等架构的 ftrace trampoline 机制可以当前函数只注册了一个 ftrace 的时候直接使用 tracepoline 来优化性能, 但是使用场景受限, 且并不能解决 ftrace 所有的性能问题
基于的特性 ftrace 本身是一个调测特性, 稳定性和安全性都存在一定问题, 商用有一定风险 | +| 基于直接跳转的方案, 通过修改函数的前几条指令, 直接跳转到新函数 | 无性能问题 | 跟 ftrace/kprobe 等同样需要修改指令的方案存在冲突
涉及长跳转时, 可能需要改多条指令, 对函数的长度有要求限制
必须有可靠的栈回溯机制, 修改多条指令, 必须要求被 patched 的函数不能正在执行, 否则执行的指令前后不一致, 将出现严重问题 | + +2. 一致性模型主要有三种思路 + +| 实现方法 | 优点 | 缺点 | +|:------:|:----:|:---:| +| stop_machine consistency 通过 stop_machine 停住所有核进行一致性检查, 通过栈回溯检查被 patched 函数是否被执行 | 安全可靠, stop_machine 会强制所有核停下原来的工作, 在非抢占式内核, 必然发生进程切换, 则总能找到一个安全的时刻去执行热补丁操作 | stop_machine 会中断原来的业务, 对性能影响较大 | +| per-task consistency 通过标记或者引用技术, 标记出被 patched 的函数都没有执行, 或者进程处于安全上下文的状态 | 不中断原来业务的执行 | 一致性检查并不是完全可信的 | + + +## 1.4 内核社区的讨论 +------- 最终经过了激烈的讨论, 大家认为各家的方案都存在一些问题, 社区对于使用哪家的方案也一直犹豫不决. 最终 Redhat 主张先搞一个动态热补丁的通用框架, 基于 ftrace 方案, 完成函数热替换的最基本功能, 不保证安全性, 不使用任何安全性机制, 只通过通用的热补丁制作/注册和使用的接口. 作为一个过渡方案. 而各家 KGraft/Kpatch 基于这个框架完成自己的方案. 一致性模型待后期充分讨论和检验后再做决定. 这个提议的最大依据就是, 大部分的 CVE 补丁即使没有一致性保证, 也可以安全的以动态补丁的方式打上. 因此社区可以先合入这样一个通用的记住方案. 然后接下来在考虑各种一致性方案的问题, 可以选择一种最优的一致性方案, 也可以将各种方案都集成进来, 然后用户根据自己 patch 的特点, 选择不同的一致性模型. 这个提议得到了大多数人的支持, 并由 Redhat 的 Seth Jennings 负责实现这个通用的框架. @@ -35,49 +71,76 @@ - - # 3 一致性模型 consistency model ------- -一致性模型是非常复杂的, 在第一版的 LIVE_PATCH 支持的时候, 邮件列表中分析了几种一致性模型, 但是却没有结论, 参见 [`Re: [PATCH 0/2] Kernel Live Patching -`](https://lkml.org/lkml/2014/11/7/354). +一致性模型是非常复杂的, 在第一版的 LIVE_PATCH 支持的时候, 邮件列表中分析了几种一致性模型, 但是却没有结论, 参见 [`Re: [PATCH 0/2] Kernel Live Patching`](https://lkml.org/lkml/2014/11/7/354). 在经过漫长的讨论之后, 基于 PER-TASK 的混合一致性模型合入了主线. | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | |:----:|:----:|:--:|:----:|:---------:|:-----:| -| 2017/2/13 | Josh Poimboeuf | livepatch: hybrid consistency model | 基于 Kgraft 的PER TASK 的状态检查和基于 Redhat 可靠的栈检查的混合一致性模型 | v4 ☑ 4.12-rc1 | [PatchWork](https://lore.kernel.org/patchwork/cover/760164), [LKML](https://www.lkml.org/lkml/2017/2/13/831) | +| 2017/2/13 | Josh Poimboeuf | [livepatch: hybrid consistency model](https://lore.kernel.org/patchwork/cover/760164) | 基于 Kgraft 的PER TASK 的状态检查和基于 Redhat 可靠的栈检查的混合一致性模型 | v4 ☑ 4.12-rc1 | [PatchWork](https://lore.kernel.org/patchwork/cover/760164), [LKML](https://www.lkml.org/lkml/2017/2/13/831) | -这个一致性模型被称为 混合一致性模型 "hybrid consistency model", 因为他融合了 +这个一致性模型被称为 **混合一致性模型 "hybrid consistency model"**, 因为他融合了 * Kgraft 的 per-task 一致性模型, 保证同一个 universe 里不同时调用了旧函数又调用了新函数的情况 -* reliable stacktrace 可靠的栈检查, 保证被 patched 的函数不被执行 +* reliable stacktrace 可靠的栈检查, 检查被 patched 的函数不被执行. + + +将这几种互补的一致性方法结合, 来确定什么时候可以安全地修补任务 + +1. 第一个也是最有效的方法是对睡眠任务进行堆栈检查。如果给定任务的堆栈中没有受影响的函数,则该任务将被修补。在大多数情况下,这将在第一次尝试时修补大部分或所有的任务。否则它会周期性地不断尝试。这个选项只有在体系结构有可靠的堆栈时才可用(HAVE_RELIABLE_STACKTRACE)。 + +2. 第二种方法(如果需要的话)是内核退出切换。当任务从系统调用、用户空间IRQ或信号返回到用户空间时,它就被切换。它在以下情况下是有用的: + * a) 修补I/O 绑定的用户任务,这些任务在受影响的函数上处于休眠状态。在这种情况下,您必须发送SIGSTOP和SIGCONT来强制它退出内核并进行修补。 + + * b) 修补cpu绑定的用户任务。如果任务是高度cpu限制的,那么它将在下次被IRQ中断时得到修补。 + + * c) 在将来,它可以用于为还没有HAVE_RELIABLE_STACKTRACE的架构应用补丁。在这种情况下,您必须向系统上的大多数任务发出信号。然而,这还不支持,因为目前没有办法在没有HAVE_RELIABLE_STACKTRACE的情况下修补线程。 + +3. 对于空闲任务 "swapper"(IDLE) ,因为它们永远不会退出内核,所以它们在空闲循环中有一个 klp_update_patch_state()调用,允许它们在CPU进入空闲状态之前被修补。 ## 3.1 per-task consistency model ------- +混合一致性模型中使用了 per-task 的一致性模型, 参见 [livepatch: change to a per-task consistency model](https://lore.kernel.org/patchwork/patch/760175). ## 3.2 reliable stacktrace ------- +可靠栈检查则进一步对 per-task 的一致性模型做了补充. 他检查函数是否正在运行, -# 4 JUMP_LABEL 支持 + +# 4 重定位信息 +------- + +## 4.1 重定位段 +------- + +## 4.2 归一的重定位信息 +------- + +## 4.3 JUMP_LABEL 支持 ------- kpatch 的实现一直是根据内核的进展而演进的, 对 JUMP_LABEL 的不断重试也体现除了这个问题的难搞. -* [Livepatch does not handle static keys #931](https://github.com/dynup/kpatch/pull/931) +1. 最开始 kpatch-build 不支持 JUMP_LABEL, [Livepatch does not handle static keys #931](https://github.com/dynup/kpatch/pull/931) -* [Add jump label support #937](https://github.com/dynup/kpatch/pull/937) +2. 为 kpatch-build 引入了 JUMP_LABEL 支持 [Add jump label support #937](https://github.com/dynup/kpatch/pull/937) -* [Revert "create-diff-object: add jump label support"](https://github.com/dynup/kpatch/pull/944) +3. 发现内核热补丁对 JUMP_LABEL 的支持存在问题, 因此 kpatch 回退了之前的提交 [Revert "create-diff-object: add jump label support"](https://github.com/dynup/kpatch/pull/944) -* [kpatch-build should warn about static keys #946](https://github.com/dynup/kpatch/issues/946) +4. 内核社区正在解决这个问题, kpatch-build 对 JUMP_LABEL 报告 WARN, 待内核解决后, 再行支持 [kpatch-build should warn about static keys #946](https://github.com/dynup/kpatch/issues/946) + +对 JUMP_LABEL 的支持主要存在如下几点问题: + +1. 如果 JUMP_LABEL 所使能的符号是属于内核或者被 patched 的驱动的, 那么 最终主线内核通过这个补丁解决了对 JUMP_LABEL 的支持问题 [livepatch: Apply vmlinux-specific KLP relocations early](https://lore.kernel.org/patchwork/cover/1233223). @@ -93,7 +156,6 @@ kpatch 的实现一直是根据内核的进展而演进的, 对 JUMP_LABEL 的 |:----:|:----:|:--:|:----:|:---------:|:----:| | 2017/08/31 | Joe Lawrence | [livepatch: introduce shadow variable API](https://lore.kernel.org/patchwork/cover/827343) | 增加 SHADOW API 支持结构体中引入新的变量 | v6 ☑ 4.15-rc1 | [PatchWork](https://lore.kernel.org/patchwork/cover/827343) | -livepatch: introduce shadow variable API # 6 架构支持 -------