背景
系统有BUG
众所周知,计算机在运行的时候需要用到一个基础软件:操作系统。操作系统是一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序。这一组程序比较底层,负责计算机资源分配等等的基础功能。例如Windows、Unix、Mac OS以及Linux这一类操作系统,但是这些程序也是人开发出来的。既然是人开发出来的,那么有Bug就是在所难免的事情了。但是跟我们一般遇到的bug不一样,这可是系统的bug,搞不好系统会崩溃,会宕机,会让业务中断的。这样,系统的bug是不是就看上去比我们一般写出来的bug更可怕了……=_+
有Bug就要修
既然系统有Bug,我们总不能当一只鸵鸟把脑袋插到土里面去当看不到就完了吧。Bug还是在蓄势待发,看那一天给你来整一票大的。例如突然间给你整宕机啦、数据丢啦乃至这个Bug被人利用,把你所有资料看光光的。所以系统出Bug了,我们就要赶快修。
那系统的bug怎么修?
修复系统Bug我认为主要有两个方式:一种是冷修复;还有一种是热修复
1、冷修复:在修复操作系统的错误或者漏洞的时候,冷修复方式指的是在系统关系或者冲洗的情况下进行修复。其中一个比较常见的冷修复方式就是把漏洞修复更新到新版本的操作系统中,通过安装新版本系统,重启系统完成修复。这种修复的好处在于可以避免一些由于系统运行导致的干扰或者冲突。但是缺点也是显而易见,那就是需要重启系统。
但是对于很多业务而言,如果我出现了漏洞就要重启乃至更新系统,会迫使我的业务中断不说,升级了系统会不会带来我的业务环境的不稳定或者是不兼容?因此,冷修复方案在大多数情况下都会遇到不少的阻力。
2、热修复:冷修复需要重启系统,这种方案的应用在一定程度上有不少阻力,难道就没有修复方案了吗?那就要介绍一下操作系统的热修复了。热修复指的是在系统运行的情况下对系统的漏洞进行修复。在修复的过程中我们不需要关闭或者重启系统,也不会改变内核的版本,大大减少了对业务的影响。热修复中有用户态热修复和内核态热修复两种修复(至于什么是内核态什么是用户态,这里就不深究了),因为我们在讨论操作系统的BUG修复,所以我们就着重讨论内核态热修复(也叫内核热补丁)了。
内核热补丁是什么?
内核热补丁的是什么?
官方一点的说法,内核热补丁就是在不影响服务器运行或者在系统正常使用的情况下进行漏洞修补的一种方法,借助相应的工具就可以在不需要重启服务器和中断业务的情况下进行补丁的安装和更新。
但是实际上,我们日常使用的热补丁就是一个ko文件,这个文件通过插入模块的方式插入到系统中,在插入到系统以后,同时完成对目标函数的替换,从而完成对内核中存在问题或者漏洞的目标函数进行修复的功能。
内核热补丁有什么用呢?
内核态对于一般用户而言都是一个比较难触碰到的区域。因此,通过内核热修复(接下来我们都将使用热补丁来称呼)技术,一方面可以修复内核中存在漏洞或者错误的函数;另一方面我们甚至可以给予内核热补丁技术给我们的内核添加自定义的debug信息,这样就实现了我们动态观察内核行为提供了一种简便的方式了(这个用法是不是非常诱人@_@)
内核热补丁是如何对内核函数实现修复的?
内核热补丁是基于函数替换技术实现对内核出现问题的函数的修复。让我们来看看下面的这个图:
一开始,在调用函数A的时候,系统寻找到路径a下面的入口地址,通过这个地址来执行function_A;当我们对function_A执行了打上热补丁以后,当系统再次调用function_A的时候,指向的就不是原来的地址了,而是新的入口日志了,这样相当于是执行了一个新的函数。这个函数只需要在完成原来的函数的基础上同时修复函数的错误或者漏洞,那么给我们的感觉就是给这个函数打上了“补丁”从而修复了这个问题。
内核热补丁有哪些工具?
通过上面对热补丁的介绍,相信各位应该对热补丁有了一定的兴趣了。那么,要制作一个热补丁,目前业界有什么跟热补丁技术相关的工具呢?我这里调研了几款:Ksplice、Kgraft、Kpatch。下面我们来了解一下这三款的热补丁制作工具吧。
1、Ksplice
Ksplice是2008年创立,该技术是来自于麻省理工大学学生的研究,当时这个项目还在2009年赢得了MIT的10万美金的创业竞赛。起初Ksplice是开源的项目,后面被甲骨文Oracle收购了,现在是Oracle的产品,需要付费使用,或者跟Oracle的产品绑定使用。针对Ubuntu,Ksplice也有免费的社区方案提供使用。
根据Oracle官网上的介绍,在Oracle Linux 6、7、8、9下,Ksplice甚至可以在系统运行时修复glibc和openssl的漏洞。由于KSplice是跟Oracle产品绑定的收费产品,具体的细节我们就不好讨论了,相关的内容大家可以查阅Kpslice在Oracle上的官方文档。
2、Kgraft
Kgraft时SUSE发布的一个热补丁项目,Kgraft是一个偏向于内核态的热补丁工具。它可以针对底层的函数进行修改。Kgraft的补丁是在rpm包里面的一个ko文件,可以通过insmod来安装这个ko。安装这个ko的时候,会把整个内核函数替换成新的函数,从而完成对目标函数的修复。
SUSE提供了zypper工具为用户提供补丁信息。用户通过命令 zypper lifecycle来查看你的内核的支持有效期到什么时候,让用户可以提前规划更换内核的事情。
我个人认为,从suse官网上公布的信息来看,kgraft实际上也是基于ftrace实现热补丁的函数替换。其基本实现方式跟我们需要讨论的第三种热补丁Kpatch的实现方式非常类似。
tux > zypper lifecycle
KGraft为用户提供kgr的管理工具。可以使用kgr查看补丁状态、已安装的补丁等等的信息。
3、Kpatch
Kpatch是Redhat开源的一款热补丁管理工具。该管理工具跟Kgraft的比较类似,热补丁也是以一个ko的形式提供的。给用户提供一个包含这个ko的rpm包,用户通过安装这个rpm包即可完成对有漏洞的内核函数进行修复。Kpatch的实现原理跟KGraft的比较类似,都是基于ftrace实现对内核函数的替换。Kpatch是在github上的一个开源项目。但是目前该项目支持x86_64\ppc64le\s390的架构,尚不支持aarch64。
Anolis kpatch-build
除了github上的Kpatch以外,还有一个项目是龙蜥的kpatch项目,作为Redhat的Kpatch项目的下游开源项目。目前Kpatch弥补了上游中不支持aarch64的缺陷,允许制作aarch64架构的内核热补丁。在热补丁的配套工具使用上,我们还有livepatch-mgr的热补丁管理工具。
livepatch-mgr
目前livepatch-mgr支持龙蜥以及Alinux,通过livepatch-mgr,用户可以方便地发现自己机器中有哪些内核热补丁需要安装。通过这个工具,用户可以方便地安装、管理自己机器上的热补丁。[3]
内核热补丁怎么做?怎么用?
Anolis kpatch项目介绍
Anolis kpatch-build的项目地址为:https://gitee.com/anolis/kpatch-build.git
在该项目下,主要提供功能的主要是kpatch-build、kmod以及kpatch这三个目录。其他的主要是一些示例以及文档。
kpatch-build
kpatch-build中的kpatch-build脚本是制作热补丁的唯一入口。用过kpatch-build命令,可以将用户给的patch制作成一个ko,用于内核的热修复。其中包含了在制作热补丁的过程中依赖的各种核心文件。
kmod
kmod中主要提供的是kpatch.ko这个模块的代码以及提供了在kpatch.ko和livepatch机制下的hook函数的代码。
livepatch是一个内核特性,主要是在2014年以后引入内核的。在此之前,热补丁是通过kpatch.ko提供底层能力的。在旧版本上,先插入kpatch.ko,其他的热补丁将引用kpatch.ko所提供的功能。
其中需要注意的是,kpatch.ko是跟内核版本强相关的一个ko。
kpatch
kpatch文件夹下存放的是一个名为kpatch的脚本。这个脚本用于提供热补丁的管理能力。包含kpatch list、load、install等等的操作。
如何构建一个热补丁?
前面我们了解了热补丁是什么,以及热补丁的一些制作工具的介绍。相信大家这个时候已经对热补丁有了一定的了解了。那么,我们如何制作一个热补丁呢?接下来,我们将基于龙蜥开源kpatch-build的热补丁制作工具Kpatch来带着大家来制作一个可用的热补丁。
首先,我们在制作一个热补丁之前,需要准备的东西有:
1、制作目标内核版本热补丁的内核源代码(我们这里准备5.10.134-14.an8.x86_64的src.rpm包,传送门)
2、目标内核的kernel-debuginfo包(vmlinux,我们使用kernel-debuginfo-5.10.134-14.an8.x86_64包,传送门)
3、目标内核的kernel-devel包(.config,我们使用kernel-devel-5.10.134-14.an8.x86_64包,传送门)
4、kpatch项目(例子中使用的是龙蜥kpatch-build v1.0.0版本,传送门)
5、针对需要被打补丁的函数的patch文件,我们的patch文件描述如下:
diff -Nupr src.orig/fs/proc/meminfo.c src/fs/proc/meminfo.c
--- src.orig/fs/proc/meminfo.c 2021-01-28 04:47:10.916473090 -0500
+++ src/fs/proc/meminfo.c 2021-01-28 04:47:11.459467821 -0500
@@ -29,6 +29,8 @@ static void show_val_kb(struct seq_file
seq_write(m, " kB\n", 4);
}
+static int foo = 5;
+
static int meminfo_proc_show(struct seq_file *m, void *v)
{
struct sysinfo i;
@@ -139,6 +141,7 @@ static int meminfo_proc_show(struct seq_
show_val_kb(m, "FilePmdMapped: ",
global_node_page_state(NR_FILE_PMDMAPPED) * HPAGE_PMD_NR);
#endif
+ seq_printf(m, "kpatch: %d\n", foo);
#ifdef CONFIG_CMA
show_val_kb(m, "CmaTotal: ", totalcma_pages);
准备好上面的东西以后,我们就可以准备开始制作一个热补丁啦。
1、获取内核源码、vmlinux。我们通过解压内核的source和debuginfo,可以在这两个包里面找到我们需要的内核源码以及vmlinux:
rpm2cpio kernel-5.10.134-14.an8.src.rpm | cpio -dmi
完成对kernel source包的解压以后,我们可以看到里面有一个linux-5.10.134-14.an8.tar.xz的压缩包,解压这个压缩包里面就是我们的linux内核源码。
2、获取vmlinux以及config文件
同上面一样,我们通过命令:
rpm2cpio kernel-debuginfo-5.10.134-14.an8.x86_64.rpm | cpio -dmi
rpm2cpio kernel-devel-5.10.134-14.an8.x86_64.rpm | cpio -dmi
分别完成解压以后,vmlinux可以在usr/lib/debug/lib/modules/5.10.134-14.an8.x86_64下面找到;.config则可以在usr/src/kernels/5.10.134-14.an8.x86_64/下面找到(注意,需要ls -la才能看到.config)
3、我们可以下载了龙蜥的kpatch-build v1.0.0版的源码包,也能直接通过git clone的方式来获得kapatch-build的源码。
git clone git@gitee.com:anolis/kpatch-build.git
4、假设我们已经下载好了我们的kpatch-build源码,此时我们需要对kpatch-build进行编译,首先进入到kpatch-build的目录下,然后输入命令make;等待编译完成。
5、编译完成以后,我们就可以开始使用kpatch-build来构建我们自己的热补丁了。
找到我们kpatch-build的路径,使用命令:
${path}/kpatch-build -n test_hotfix -v vmlinux -c .config -s ${SOURCE} -o ${OUTDIR} ${PATCH}
-n:代表hotfix的名字
${path}:kpatch-build的路径,这里是到kpatch-build下的kpatch-build子目录中
${SOURCE}:源代码目录
${OUTDIR}:输出产物的目录
${PATCH}:patch文件
执行这一条指令,kpatch-build就会开始构建hotfix了。
在kpatch-build执行完以后在终端输出SUCCESS以后,就可以在${OUTDIR}下面找到test_hotfix.ko这个文件。这个test_hotfix.ko就是我们构建产生的热补丁文件了。
${OUTDIR}这种是shell脚本引用变量的方式
热补丁构建好了以后怎么使用?
假设上面的步骤完成后,你已经成功获得test_hotfix.ko这个热补丁文件了。那么我们应该怎么用呢?
内核版本检查
首先,热补丁本身跟内核是强相关的。因此,构建出来的热补丁只能用于构建时使用的对应内核源码的版本上。
上面我们构建时使用的内核源码是5.10.134-14.an8的内核源码,在x86下构建的,因此我们要检查一下本机的内核是否能够应用我们的这个热补丁:可以通过输入uname -r检查
插入热补丁
确定运行的内核版本与我们构建热补丁时使用的内核版本一致以后,我们就可以使用insmod命令插入这个ko文件
insmod test_hotfix.ko
当函数返回的时候,至少说明过程中没有出现可见的问题。
验证
根据我们上面的patch的功能,是在meminfo里面打印出kpatch: 5的功能。因此,要验证hotfix是否生效,我们只需要看看是不是成功输出这个即可。
在插入热补丁以后,我们通过命令:
grep "kpatch: 5" /proc/meminfo
如果输出如下:
那就说明了我们的这个hotfix不仅打上了,还生效了。
结尾
本文开始给大家介绍了应用的背景以及热补丁的概念。然后给各位介绍了一下目前比较流行的几款热补丁制作工具。最后,通过一个具体的例子带着大家制作了热补丁并验证其效果。相信大家看完这一篇文章,多少会对热补丁技术产生了一点兴趣。
其实热补丁本身通过修改运行中的系统中的函数而不需要重启就能生效,使其在许多业务不可中断的场景下发挥非常重要的作用。因此,热补丁是修复线上安全问题、稳定性问题以及我们日常运维的一个重要工具。
本文作为前言,后续我们将基于kpatch,对kpatch热补丁技术抽丝剥茧,层层打开来观察、学习热补丁的技术原理。我们将会从kpatch项目入手,讲解kpatch-build、kmod等重要组件的原理,以及内核livepatch的技术和与热补丁息息相关的elf文件格式等等的方面入手,全面理解热补丁方面的技术。
所以,如果各位对热补丁技术感兴趣的话,请盯准我的频道,跟一起探索热补丁的世界吧!
参考文献
[1]https://documentation.suse.com/sles/12-SP5/html/SLES-kgraft/index.html
[2]https://gitee.com/anolis/kpatch-build/tree/master
[3]https://help.aliyun.com/zh/ecs/user-guide/use-livepatch-mgr?spm=a2c4g.11186623.0.0.77fb6f6fOO8EKD