kpatch动态内核补丁基础设施详解
kpatch动态内核补丁基础设施详解
kpatch是一种Linux动态内核补丁基础设施,它允许你在不重启或重新启动任何进程的情况下为运行中的内核打补丁。它能让系统管理员立即为内核打上重要的安全补丁,而不必等待长时间运行的任务完成、用户注销或预定的重启窗口。它能在不牺牲安全性或稳定性的情况下,对正常运行时间进行更多控制。
WARNING: Use with caution! Kernel crashes, spontaneous reboots, and data loss may occur!
支持的架构
- x86-64
- ppc64le
- arm64
- s390u
安装
请参阅INSTALL.md。
快速入门
注意:虽然kpatch可以在任何发行版的任何最新Linux内核上运行,但kpatch-build已经过专门测试,并确认可以在Fedora和RHEL上运行。它还可以在Oracle Linux、Ubuntu、Debian和Gentoo上运行。
首先,使用diff、git或quilt对内核树进行源代码patch。
举个蹩脚的例子,让我们给/proc/meminfo打上补丁,用大写字母显示VmallocChunk,这样就能看得更清楚了:
$ cat meminfo-string.patch
Index: src/fs/proc/meminfo.c
===================================================================
--- src.orig/fs/proc/meminfo.c
+++ src/fs/proc/meminfo.c
@@ -95,7 +95,7 @@ static int meminfo_proc_show(struct seq_
"Committed_AS: %8lu kB\n"
"VmallocTotal: %8lu kB\n"
"VmallocUsed: %8lu kB\n"
- "VmallocChunk: %8lu kB\n"
+ "VMALLOCCHUNK: %8lu kB\n"
#ifdef CONFIG_MEMORY_FAILURE
"HardwareCorrupted: %5lu kB\n"
#endif
构建补丁模块:
$ kpatch-build meminfo-string.patch
Using cache at /home/jpoimboe/.kpatch/3.13.10-200.fc20.x86_64/src
Testing patch file
checking file fs/proc/meminfo.c
Building original kernel
Building patched kernel
Detecting changed objects
Rebuilding changed objects
Extracting new and modified ELF sections
meminfo.o: changed function: meminfo_proc_show
Building patch module: livepatch-meminfo-string.ko
SUCCESS
这将在当前目录下输出一个名为kpatch-meminfo-string.ko的补丁模块。现在将其应用到运行中的内核:
$ sudo kpatch load kpatch-meminfo-string.ko
loading patch module: livepatch-meminfo-string.ko
完成!内核现已打好补丁。
$ grep -i chunk /proc/meminfo
VMALLOCCHUNK: 34359337092 kB
补丁作者指南
不幸的是,实时补丁并不总是像前面的例子那么简单,稍有不慎就会有一些重大隐患。要进一步了解如何正确创建实时补丁,请参阅[Patch Author Guide]。
工作原理
kpatch在函数粒度上工作:用新函数替换旧函数。它有三个主要组件:
- kpatch-build:将源差异补丁转换为补丁模块的工具集合。它们的工作原理是编译内核时,同时编译有源补丁和无源补丁的内核,比较二进制文件,然后生成补丁模块,其中包括要替换的函数的新二进制版本。
- 补丁模块:内核实时补丁模块(.ko文件),包含替换函数和原始函数的元数据。加载后,它将向内核实时补丁基础架构(CONFIG_LIVEPATCH)注册,后者将执行补丁操作。
- kpatch工具:一种命令行工具,允许用户管理补丁模块集合。一个或多个补丁模块可配置为在启动时加载,这样即使系统重启到同一版本的内核中,也能保持补丁状态。
kpatch-build
"kpatch-build"命令将源代码级的差异补丁文件转换为内核补丁模块。大部分工作由kpatch-build脚本完成,该脚本使用名为create-diff-object的工具来比较更改的对象。
kpatch-build的主要步骤是:
- 为内核构建未剥离的vmlinux
- 为源代码树打补丁
- 重建vmlinux并监控哪些对象正在重建。这些就是"已更改的对象"。
- 用-ffunction-sections -fdata-sections重新编译每个已更改的对象,生成已修补的对象
- 恢复源代码树
- 用-ffunction-sections -fdata-sections重新编译每个已更改的对象,生成原始对象
- 对于每个更改后的对象,使用create-diff-object执行以下操作:
- 分析每个原始/修补对象对的可修补性
- 在输出对象中添加.kpatch.funcs和.rela.kpatch.funcs部分。kpatch核心模块会使用它来确定需要使用ftrace重定向的函数列表。
- 在输出对象中添加.kpatch.dynrelas和.rela.kpatch.dynrelas段。这将用于解决对非包含的本地和非导出的全局符号的引用。这些重定位将由kpatch核心模块解决。
- 生成包含新增和修改部分的输出对象
- 将所有输出对象连接成一个累积对象
- 生成补丁模块
限制
- 注意:许多限制都可以通过创造性的解决方案来解决。更多详情,请参阅Patch Author Guide。
- 不支持修改init函数(注释为__init)的补丁。如果补丁试图这样做,kpatch-build将返回错误。
- 不直接支持修改静态分配数据的补丁,kpatch-build会检测到并返回错误。这一限制可以通过使用回调或影子变量来克服,详见Patch Author Guide。
- 改变函数与动态分配数据交互方式的补丁可能是安全的,也可能是不安全的。kpatch-build无法验证这类补丁的安全性。用户需要了解补丁的作用,新函数与动态分配数据的交互方式是否与旧函数不同,以及在运行中的内核中原子地应用这样的补丁是否安全。
- 不支持修改vdso中函数的补丁。这些补丁在用户空间运行,ftrace无法钩住它们。
- 不支持修改缺少fentry调用的函数的补丁。这包括任何lib-y目标,这些目标被归档到lib.a库供以后链接(例如lib/string.o)。
- 目前,kpatch与使用ftrace和kprobes之间存在一些不兼容问题。更多详情,请参阅常见问题部分。
常见问题
Q. 这不就是病毒/rootkit注入框架吗?
kpatch使用内核模块替换代码。它需要CAP_SYS_MODULE能力。如果你已经拥有了这种能力,那么无论有没有kpatch,你都已经拥有了任意修改内核的能力。
Q. 怎样检测有人是否修补了内核?
如果当前应用了补丁,则可在/sys/kernel/livepatch中查看。
此外,如果先前已打上补丁,则会设置TAINT_LIVEPATCH标志。要测试这些标志,可使用cat /proc/sys/kernel/tainted并检查TAINT_LIVEPATCH(32768)的值是否已被OR’ed。
请注意,TAINT_OOT_MODULE标志(4096)也将被设置,因为补丁模块是在Linux内核源代码树之外构建的。
如果您的补丁模块是无符号的,TAINT_UNSIGNED_MODULE标志(8192)也将被设置。
Q. 这会破坏我的系统吗?
不会,只要补丁制作得小心谨慎。请参阅上文"限制"部分和补丁编写指南。
Q. 为什么不用kexec之类的工具?
如果你想避免硬件重启,但又能接受重启进程或使用CRIU,kexec是一个不错的选择。
Q. 如果应用程序不能处理重启,那就是设计错误。
这是一个好点……[系统重启]
Q. 哪些内核版本受支持?
kpatch需要gcc >= 4.8和Linux >= 4.0。
Q. 能否删除补丁?
可以。只需运行kpatch unload即可禁用和卸载补丁模块,并将功能恢复到初始状态。
Q. 能否应用多个补丁?
可以,但为了防止多个补丁模块之间出现意外的交互,建议补丁升级采用累积方式,这样每个补丁都是前一个补丁的超集。这可以通过在运行kpatch-build之前使用combinediff将新补丁与上一个补丁合并来实现。我们还建议使用livepatch原子"替换"模式,这是默认模式。
Q. 为什么kpatch-build检测到一个未被源补丁修改的更改函数?
这可能有多种原因,例如:
- 补丁更改了一个内联函数。
- 编译器决定内联一个已更改的函数,导致外层函数被重新编译。在内部函数是静态函数且只被调用一次的情况下,这种情况很常见。
- kpatch-build检测__LINE__宏使用时的一个错误。
Q. 支持内核模块的补丁吗?
- 支持。
Q. 能否修补树外模块?
可以!有一些要求,而且该功能仍处于起步阶段。
- 你需要使用--oot-module标志来指定当前机器上运行的模块版本。
- --oot-module-src必须与包含与正在运行的模块相同版本代码的目录一起传递,所有代码都已设置好,可以用make命令编译。例如,有些模块需要autogen.sh和./configure在运行时加上适当的标志,以匹配当前运行的模块。
- 如果out-of-tree模块的Module.symvers文件不在提供的源代码目录根目录中,则需要在该目录中创建一个指向其实际位置的软链接。
- 通常还需要传递--target标志,以指定适当的make目标名称。
- 这只针对每个补丁中的单个out-of-tree模块进行了测试,而不是针对依赖于单独构建的其他out-of-tree模块的out-of-tree模块。
示例调用:
kpatch-build --oot-module-src ~/test/ --target default --oot-module /lib/modules/$(uname -r)/extra/test.ko test.patch
Q. 支持新架构需要什么?
适配架构可分三个阶段进行:
- 在内核中添加CONFIG_HAVE_LIVEPATCH支持。对于某些架构,这可能就像启用CONFIG_DYNAMIC_FTRACE_WITH REGS一样简单。有了这种支持,您就可以制作基本的如samples/livepatch中的补丁一样的实时补丁了。Livepatch功能是有限制的,必须格外小心以避免某些陷阱。
- 添加kpatch-build(create-diff-object)支持。这样就能更容易地构建补丁,并避免一些陷阱。例如,https://github.com/dynup/kpatch/pull/1203添加了对s390x的支持。
- 在内核中添加CONFIG_HAVE_RELIABLE_STACKTRACE和objtool支持(如需要)。这样可以避免更多陷阱,并实现完整的实时补丁功能。
参与贡献
如果你有问题或反馈,请加入Libera上的#kpatch IRC频道并打招呼。
我们非常欢迎你的贡献。欢迎在github上打开问题或PR。对于大型PR,在编写大量代码之前,最好先在github问题/讨论或IRC上进行讨论。
许可证
kpatch采用GPLv2许可协议。
本程序是自由软件;你可以根据自由软件基金会发布的GNU通用公共许可证条款重新发布和/或修改本程序;可以是许可证的第2版,也可以是(由你选择的)任何后续版本。
本程序在发布时是希望其可用的,但不附带任何保证;甚至不附带适销性或特定用途适用性的默示保证。更多详情,请参阅GNU通用公共许可证。
你应该随本程序一起收到一份GNU通用公共许可证副本;如果没有,请写信给自由软件基金会(Free Software Foundation, Inc.), 51 Franklin Street, Five Floor, Boston, MA 02110-1301, USA。