kernel/livepatch 内核热补丁(Livepatch)机制与原理详解
源码路径:
rk3588/kernel-6.1/kernel/livepatch/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)
该目录实现 Kernel Live Patching(内核热补丁) 核心框架。在不重启系统、不卸载内核模块的前提下,通过 ftrace 动态跳转 将已运行内核中被 patch 的函数调用重定向到补丁模块中的新实现,并配合 一致性模型(Consistency Model) 保证所有任务安全迁移到新/旧代码。
定位:生产环境安全修复内核 bug 的基础设施;补丁以 livepatch 模块 形式加载,由管理员通过 sysfs 或模块 init 启用。
目录
- 一、源码目录结构
- 二、整体架构
- 三、核心数据结构
- 四、ftrace 跳转机制(patch.c)
- 五、补丁启用/禁用流程(core.c)
- 六、一致性模型与过渡(transition.c)
- 七、Shadow 变量(shadow.c)
- 八、系统状态与累积补丁(state.c)
- 九、符号解析与 KLP 重定位
- 十、Sysfs 管理接口
- 十一、动态模块补丁
- 十二、RK3588/ARM64 平台说明
- 十三、完整启用/禁用时序
- 十四、总结
- 附录:源文件清单
一、源码目录结构
1.1 编译依赖(Makefile)
1 | obj-$(CONFIG_LIVEPATCH) += livepatch.o |
| 配置项 | 说明 |
|---|---|
CONFIG_LIVEPATCH |
启用内核热补丁核心 |
CONFIG_HAVE_LIVEPATCH |
架构声明支持 livepatch(ARM64 未 select) |
CONFIG_DYNAMIC_FTRACE_WITH_REGS 或 CONFIG_DYNAMIC_FTRACE_WITH_ARGS |
ftrace 需支持寄存器/参数保存 |
CONFIG_KALLSYMS_ALL |
完整符号表(含未导出符号) |
CONFIG_HAVE_RELIABLE_STACKTRACE |
可靠栈回溯(一致性模型加速路径) |
!CONFIG_TRIM_UNUSED_KSYMS |
不可裁剪未使用 ksym |
Kconfig 依赖链:
1 | LIVEPATCH |
1.2 源文件
| 文件 | 行数 | 功能 |
|---|---|---|
core.c |
1291 | 补丁生命周期、符号查找、KLP 重定位、sysfs、模块钩子 |
transition.c |
666 | 一致性模型:任务 patch_state、栈检查、过渡完成 |
patch.c |
289 | ftrace 注册/注销、klp_ftrace_handler 跳转 |
shadow.c |
299 | Shadow 变量:RCU 哈希表 <obj, id> → 数据 |
state.c |
119 | klp_state 版本兼容、累积补丁检查 |
core.h |
59 | 内部 API、回调封装、patch 遍历宏 |
patch.h |
35 | patch/unpatch 对象声明 |
transition.h |
16 | 过渡函数声明 |
state.h |
9 | state 兼容检查声明 |
相关头文件:
| 文件 | 功能 |
|---|---|
include/linux/livepatch.h |
公共 API、klp_patch/klp_object/klp_func 定义 |
kernel/entry/common.c |
返回用户态时 klp_update_patch_state() |
kernel/sched/idle.c |
idle 循环中 klp_update_patch_state() |
Documentation/livepatch/ |
官方文档与示例 |
二、整体架构
2.1 设计思想
Livepatch 不修改内核 text 段,而是在函数入口通过 ftrace mcount 钩子 拦截调用,将指令指针重定向到补丁模块中的 new_func。多个补丁叠加时,同一函数维护 func_stack 栈,栈顶为当前生效补丁。
1 | 补丁模块加载 |
2.2 模块关系
1 | ┌─────────────────────────────────────────────────────────────┐ |
2.3 与相关子系统对比
| 机制 | 方式 | 是否需要重启 |
|---|---|---|
| Livepatch | ftrace 跳转 + 一致性模型 | 否 |
| kpatch / kgraft | 早期独立实现,已并入 livepatch | 否 |
| 模块 reload | 卸载/重载 .ko |
通常需停止使用者 |
| kexec | 新内核启动 | 近似重启 |
三、核心数据结构
定义于 include/linux/livepatch.h:
3.1 klp_func — 函数补丁项
1 | struct klp_func { |
patching 状态机:
| patched | transition | 含义 |
|---|---|---|
| 0 | 0 | 未 patch |
| 0 | 1 | 未 patch,过渡初始态 |
| 1 | 1 | 已 patch,部分任务可见 |
| 1 | 0 | 已 patch,全部任务可见 |
unpatch 时按相反顺序迁移。
3.2 klp_object — 内核对象(vmlinux 或模块)
1 | struct klp_object { |
3.3 klp_patch — 补丁顶层
1 | struct klp_patch { |
3.4 任务 patch 状态
每个 task_struct 有:
1 | int patch_state; /* KLP_UNDEFINED / KLP_UNPATCHED / KLP_PATCHED */ |
全局过渡变量(transition.c):
1 | struct klp_patch *klp_transition_patch; |
四、ftrace 跳转机制(patch.c)
4.1 klp_ops 与 func_stack
每个被 patch 的 原函数地址 对应一个 klp_ops:
1 | struct klp_ops { |
全局链表 klp_ops 管理所有已注册 ops。
4.2 klp_patch_func() 流程
1 | 1. klp_find_ops(old_func) — 是否已有 ops |
4.3 klp_ftrace_handler() — 核心跳转
1 | static void notrace klp_ftrace_handler(unsigned long ip, |
要点:
- 非过渡期:栈顶
new_func直接生效 - 过渡期:根据
current->patch_state选择新/旧实现 - NOP 函数:
new_func == old_func,handler 直接返回 - 使用
ftrace_test_recursion_trylock保证与klp_synchronize_transition()的 RCU 语义兼容
4.4 klp_unpatch_func()
- 若
func_stack仅一项:注销 ftrace、删除 ops - 否则:仅从 stack 移除该 func(RCU 延迟释放)
五、补丁启用/禁用流程(core.c)
5.1 klp_enable_patch() — 入口
补丁模块在 module_init() 中调用:
1 | int klp_enable_patch(struct klp_patch *patch) |
5.2 __klp_enable_patch()
1 | klp_init_transition(patch, KLP_PATCHED) |
5.3 __klp_disable_patch()
1 | klp_init_transition(patch, KLP_UNPATCHED) |
5.4 replace 模式(累积补丁)
patch->replace = true 时:
klp_add_nops()— 为旧补丁所有函数创建 NOP 占位- 启用新补丁后,
klp_complete_transition()中:klp_unpatch_replaced_patches()— 注销旧补丁 ftraceklp_discard_nops()— 移除动态 NOP
NOP 保证过渡期间 ftrace handler 始终有合法 stack 条目。
5.5 全局锁 klp_mutex
粗粒度互斥锁,保护所有 klp 数据结构。例外(无锁访问):
klp_ftrace_handler()— ftrace 上下文klp_update_patch_state()— 任务安全点
六、一致性模型与过渡(transition.c)
6.1 问题背景
patch 生效后,可能仍有任务 栈上持有旧函数帧 或 正在旧代码中执行。直接移除旧代码会导致 use-after-free 类崩溃。一致性模型确保:所有任务迁移到目标 patch_state 后才完成过渡。
6.2 迁移路径
| 路径 | 触发点 | 条件 |
|---|---|---|
| 栈检查切换 | klp_try_complete_transition() |
klp_have_reliable_stack() |
| 内核退出切换 | exit_to_user_mode_loop() |
TIF_PATCH_PENDING |
| idle 切换 | do_idle() 循环 |
klp_patch_pending(current) |
| 信号唤醒 | klp_send_signals() |
延迟工作重试超时 |
| 强制过渡 | sysfs force=1 |
管理员承担风险 |
6.3 klp_update_patch_state()
在 安全点(返回用户态、idle 循环)调用:
1 | void klp_update_patch_state(struct task_struct *task) |
与 kernel/entry/common.c 集成:
1 | if (ti_work & _TIF_PATCH_PENDING) |
6.4 栈检查 klp_check_stack()
对 sleeping 任务保存可靠栈回溯,检查是否包含:
- patching:旧函数或上一层 patched 函数地址范围
- unpatching:新函数地址范围
若命中 → 任务暂不能切换,延迟重试。
6.5 klp_synchronize_transition()
对 RCU 非 watching 路径(如 user_exit() 之前),不能用普通 synchronize_rcu():
1 | static void klp_synchronize_transition(void) |
6.6 过渡完成 klp_complete_transition()
1 | 1. replace 模式:unpatch 旧补丁 + discard NOPs |
6.7 反向过渡与强制
- klp_reverse_transition():sysfs
enabled在过渡中写反值,取消进行中的 enable/disable - klp_force_transition():对所有任务强制
klp_update_patch_state(),可能破坏一致性,标记patch->forced
七、Shadow 变量(shadow.c)
补丁常需为已有内核对象附加 私有扩展数据(如替换后的状态),Shadow 变量提供 <obj, id> → data 映射。
7.1 数据结构
1 | struct klp_shadow { |
7.2 API
| 函数 | 说明 |
|---|---|
klp_shadow_get(obj, id) |
RCU 读,获取已有 shadow |
klp_shadow_alloc(obj, id, size, gfp, ctor, data) |
分配;重复则 WARN 返回 NULL |
klp_shadow_get_or_alloc(...) |
存在则返回,否则分配 |
klp_shadow_free(obj, id, dtor) |
删除并 kfree_rcu |
klp_shadow_free_all(id, dtor) |
按 id 批量删除 |
并发:哈希表 RCU 读与 klp_shadow_free 并发安全;shadow 数据本身的互斥由调用者负责。
八、系统状态与累积补丁(state.c)
8.1 klp_state
1 | struct klp_state { |
用于描述补丁修改的 系统级语义状态(如数据结构布局变更)。
8.2 兼容性检查
1 | bool klp_is_patch_compatible(struct klp_patch *patch) |
规则:
| 补丁类型 | 要求 |
|---|---|
| replace(累积) | 必须处理所有已有 patch 修改过的 state,且 version >= 旧版 |
| 非累积 | 可叠加,允许 touch 已有 state |
辅助 API:
klp_get_state(patch, id)— 当前补丁的 stateklp_get_prev_state(id)— 过渡期间查已安装补丁的最新 state
九、符号解析与 KLP 重定位
9.1 符号查找 klp_find_object_symbol()
通过 kallsyms_on_each_symbol() 或 module_kallsyms_on_each_symbol() 查找:
- 支持
old_sympos消歧(同名符号第 N 个) sympos == 0要求符号唯一,否则报错
9.2 KLP 重定位段
补丁模块 ELF 含特殊段:
| 段名格式 | 用途 |
|---|---|
.klp.rela.vmlinux.* |
引用 vmlinux 未导出符号 |
.klp.rela.{module}.* |
引用目标模块符号 |
符号名格式:.klp.sym.{objname}.{symname},{sympos}
两阶段应用:
- klp 模块加载时:解析 vmlinux 重定位(
klp_apply_section_relocs) - 目标模块加载时:解析 module 重定位(
klp_init_object_loaded)
这支持 late module patching(先加载补丁,后加载被 patch 模块)。
十、Sysfs 管理接口
挂载于 /sys/kernel/livepatch/:
1 | /sys/kernel/livepatch/ |
| 属性 | 行为 |
|---|---|
enabled=0 |
触发 disable(若未在过渡) |
enabled=1 |
过渡中 reverse;已 disable 的 patch 不可 re-enable |
force=1 |
仅过渡中有效,强制所有任务切换 |
十一、动态模块补丁
11.1 klp_module_coming()
模块 MODULE_STATE_COMING 时:
1 | mod->klp_alive = true |
失败则 拒绝加载模块。
11.2 klp_module_going()
模块卸载/加载失败时:
1 | mod->klp_alive = false |
11.3 fork 继承 klp_copy_process()
子进程继承父进程 patch_state 与 TIF_PATCH_PENDING,由 tasklist_lock 与过渡操作串行化。
十二、RK3588/ARM64 平台说明
12.1 架构支持现状(Linux 6.1)
| 能力 | ARM64/RK3588 | 说明 |
|---|---|---|
HAVE_DYNAMIC_FTRACE |
✅ | arch/arm64/Kconfig select |
HAVE_DYNAMIC_FTRACE_WITH_REGS |
✅ | GCC/Clang 支持时 select |
HAVE_LIVEPATCH |
❌ | 未 select(x86_64/s390/powerpc 有) |
HAVE_RELIABLE_STACKTRACE |
❌ | 未 select(需 objtool/ORC 等) |
因此 RK3588 默认 defconfig 下 CONFIG_LIVEPATCH 不可选;即使手动开启,也缺少架构官方 livepatch 支持与可靠栈,一致性模型主要依赖 内核退出/idle 切换,过渡可能缓慢或需 force。
12.2 ftrace 与 IP 修改
ARM64 在 CONFIG_DYNAMIC_FTRACE_WITH_REGS 下,klp_ftrace_handler 通过 ftrace_instruction_pointer_set(fregs, new_func) 修改 pc,实现跳转。相关实现在:
arch/arm64/kernel/ftrace.carch/arm64/kernel/entry-ftrace.S
12.3 生产建议
- RK3588 嵌入式场景通常 不启用 livepatch;安全修复更常用 OTA 内核升级
- 若需实验:手动添加
select HAVE_LIVEPATCH并满足全部 Kconfig 依赖,充分测试过渡与模块 patch - 参考
samples/livepatch/与lib/livepatch/测试模块
十三、完整启用/禁用时序
13.1 启用时序
13.2 函数调用路径(运行时)
1 | 调用被 patch 函数 |
13.3 禁用时序(概要)
与启用对称:KLP_UNPATCHED 为目标态,初始态 KLP_PATCHED,完成后 klp_unpatch_objects() 注销 ftrace。
十四、总结
| 组件 | 职责 |
|---|---|
| core.c | 补丁生命周期、符号/KLP 重定位、sysfs、模块 coming/going |
| patch.c | ftrace ops 管理、运行时 IP 重定向、func_stack 多层补丁 |
| transition.c | 一致性模型:任务状态机、栈检查、安全点切换、强制/反向过渡 |
| shadow.c | 补丁私有 per-object 数据 |
| state.c | 累积补丁 state 版本兼容 |
Livepatch 的核心难点不在「跳转」(ftrace 已成熟),而在 一致性模型:保证任意时刻每个任务执行的是与其 patch_state 一致的代码版本。RK3588/ARM64 在 Linux 6.1 上 未官方声明 HAVE_LIVEPATCH,部署前需评估架构支持与测试覆盖。
附录:源文件清单
| 文件 | 行数 |
|---|---|
kernel/livepatch/core.c |
1291 |
kernel/livepatch/transition.c |
666 |
kernel/livepatch/patch.c |
289 |
kernel/livepatch/shadow.c |
299 |
kernel/livepatch/state.c |
119 |
kernel/livepatch/core.h |
59 |
kernel/livepatch/patch.h |
35 |
kernel/livepatch/transition.h |
16 |
kernel/livepatch/state.h |
9 |
kernel/livepatch/Kconfig |
21 |
kernel/livepatch/Makefile |
5 |
| 合计 | ~2807 |
关联源码(目录外):
| 文件 | 行数(约) | 说明 |
|---|---|---|
include/linux/livepatch.h |
257 | 公共 API |
kernel/entry/common.c |
— | 用户态返回路径 |
kernel/sched/idle.c |
— | idle 任务切换 |
arch/arm64/kernel/ftrace.c |
— | ARM64 ftrace 实现 |
正在加载留言…