Skip to content

第一百九十八章:实时补丁

大乘期

涉及内核源码:

林小源离开 KVM 的工坊,来到一座灯火通明的医院。医院的走廊里弥漫着消毒水的气味,墙上贴满了各种告示——"CVE-2024-XXXX 已修复"、"安全补丁已部署"。

"你来得正好。"一个穿白大褂的中年医师从手术室里走出来,摘下口罩。他的面容疲惫但专注,手里拿着一块刻着函数名的金属片。

"你是……livepatch?"

"实时补丁。"医师说,"服务器不能停机,但安全漏洞必须修复。我的工作就是在不重启系统的情况下,替换有问题的函数。"

他把金属片递给林小源。那金属片的正面刻着 original_func,背面刻着 patched_func

"原理很简单。"医师说,"每个内核函数的入口都有一个跳转点。我在跳转点上插入一条指令,让调用跳转到补丁函数,而不是原始函数。用的是 ftrace 的机制——ftrace 已经在函数入口埋了钩子,我只是借用一下。"

c
/*
 * 实时补丁 (Live Patching):
 *
 * 为什么需要:
 *   服务器不能重启
 *   安全漏洞需要及时修复
 *   减少停机时间
 *
 * 技术:
 *   kpatch (Red Hat)
 *   kGraft (SUSE)
 *   livepatch (内核主线)
 *
 * 原理:
 *   替换函数指针
 *   使用 ftrace 机制
 *   在函数入口处跳转
 *
 * 约束:
 *   不能修改数据结构
 *   不能改变函数签名
 *   需要兼容性
 *
 * 工具:
 *   kpatch-build — 构建补丁
 *   kpatch load — 加载补丁
 *
 * 安全性:
 *   补丁签名
 *   验证补丁来源
 */

/* 模拟函数补丁 */
typedef int (*func_ptr)(int);

/* 原始函数 */
int original_func(int x) {
    return x * 2;
}

/* 补丁函数 */
int patched_func(int x) {
    return x * 3;
}

/* 模拟函数指针表 */
func_ptr func_table[2] = {
    original_func,
    NULL
};

printf("=== 实时补丁 — 不重启更新 ===\n\n");

printf("实时补丁在不重启的情况下修复内核:\n\n");

printf("--- 原始函数 ---\n");
printf("func_table[0](5) = %d\n", func_table[0](5));

printf("\n--- 应用补丁 ---\n");
printf("替换函数指针:\n");
func_table[0] = patched_func;
printf("func_table[0](5) = %d\n", func_table[0](5));

printf("\n--- 实时补丁技术 ---\n");
printf("kpatch (Red Hat):\n");
printf("  构建补丁模块\n");
printf("  替换函数\n\n");
printf("kGraft (SUSE):\n");
printf("  per-task 策略\n");
printf("  逐步迁移\n\n");
printf("livepatch (内核主线):\n");
printf("  统一框架\n");
printf("  融合 kpatch 和 kGraft\n\n");

printf("--- 原理 ---\n");
printf("使用 ftrace 机制:\n");
printf("  在函数入口处跳转\n");
printf("  跳转到补丁函数\n\n");
printf("实现:\n");
printf("  注册 ftrace 回调\n");
printf("  修改函数入口\n");
printf("  跳转到新函数\n\n");

printf("--- 约束 ---\n");
printf("1. 不能修改数据结构:\n");
printf("   数据结构已存在\n\n");
printf("2. 不能改变函数签名:\n");
printf("   调用者期望相同接口\n\n");
printf("3. 需要兼容性:\n");
printf("   新旧函数行为兼容\n\n");

printf("--- 使用方法 ---\n");
printf("1. 构建补丁:\n");
printf("   kpatch-build patch.patch\n\n");
printf("2. 加载补丁:\n");
printf("   kpatch load patch.ko\n\n");
printf("3. 查看状态:\n");
printf("   kpatch list\n\n");

printf("--- 安全性 ---\n");
printf("补丁签名:\n");
printf("  验证补丁来源\n\n");
printf("模块验证:\n");
printf("  检查模块完整性\n");

医师领着林小源走进手术室。手术台上躺着一个内核模块,模块的表面有一个小小的裂缝——一个安全漏洞。

"看好了。"医师拿起手术刀,"我不动原始代码。我只是在函数入口处插入一条跳转指令。"

他把手术刀伸向模块表面,在裂缝旁边精确地切了一个小口。然后他把那块刻着 patched_func 的金属片嵌入切口中。金属片的边缘与模块表面完美贴合,裂缝被覆盖了。

"用 ftrace 机制。"医师说,"ftrace 已经在每个函数入口埋了一个 NOP 指令。我把它改成跳转指令,跳转到补丁函数。原始函数还在那里,只是不被调用了。"

"不需要修改原始代码?"

"不需要。"医师收起手术刀,"原始函数的代码不变,只是入口被重定向了。这就是实时补丁的优雅之处——最小侵入,最大效果。"

林小源注意到手术室的墙上挂着一张长长的清单,上面列着各种限制。

"约束。"医师说,"实时补丁不是万能的。"

他指着清单:"第一,不能修改数据结构。数据结构已经在内存中了,你不能改变它的布局。第二,不能改变函数签名。调用者期望 func(int) 返回 int,你不能改成 func(int, int)。第三,新旧函数的行为必须兼容——你不能把一个返回成功值的函数改成返回错误值。"

"所以只能修复简单的 bug?"

"对。"医师点头,"复杂的修改——添加新字段、改变逻辑——还是得重启。但安全漏洞往往是简单的逻辑错误,实时补丁刚好够用。"

他从口袋里掏出一块小印章:"还有安全性。补丁必须签名,验证来源。你不能让随便一个人往内核里注入代码。kpatch-build 构建补丁,kpatch load 加载补丁,kpatch list 查看状态。每一步都要验证。"

林小源看着那张清单,明白了约束的意义——不是限制,是保障。没有这些约束,实时补丁本身就会成为安全漏洞。


道藏笔记

内核启示

实时补丁在不重启的情况下修复内核。

技术:

  • kpatch (Red Hat)
  • kGraft (SUSE)
  • livepatch (内核主线)

原理:

  • 使用 ftrace 机制
  • 在函数入口处跳转
  • 跳转到补丁函数

约束:

  • 不能修改数据结构
  • 不能改变函数签名
  • 需要兼容性

安全性:

  • 补丁签名
  • 模块验证

实时补丁是修复——不重启,不停机。


破关试炼

实时补丁之试

服务器不能重启但要替换有问题的内核函数时,本章主线机制叫什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道