ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenEuler内核热升级实战:不重启修复安全漏洞

OpenEuler内核热升级实战:不重启修复安全漏洞 凌晨两点监控平台推送了一条内核安全公告当前生产环境的内核存在一个可被本地利用的提权漏洞建议尽快升级内核。但手头这些机器上跑着数据库、消息队列、在线业务别说重启连短暂断连都可能触发值班电话连环响。这种时候如果系统支持内核热升级就不需要等业务低峰期、不需要申请变更窗口、不需要在凌晨三四点一个人蹲在机房盯着重启进度条。这篇文章就围绕 OpenEuler 的内核热升级来写。我会把手上的实操笔记整理出来包括热升级的原理、前置条件、补丁准备、加载验证、回滚方案以及我在实际环境里踩过的坑。无论你是刚接触 OpenEuler 的运维新人还是已经在生产环境里摸爬滚打多年的系统工程师这篇文章应该都能给你一些可以落地的东西。1. 内核热升级到底解决什么问题1.1 传统内核升级的痛点Linux 内核的安全性非常重要。一旦内核被发现有漏洞常规做法是下载新版本内核软件包、更新 grub 配置、重启系统。整个过程本身并不复杂复杂的是“重启”。一台运行中的服务器背后往往挂着大量业务依赖尤其是数据库、缓存、消息中间件这类的有状态服务重启一次意味着服务中断调用方超时会触发重试风暴。进程全部重新拉起缓存预热需要时间期间性能下降明显。如果有主从架构重启还可能触发主从切换甚至脑裂风险。大批量服务器需要错峰操作运维成本极高。所以在很多团队里一个内核 CVE 的修复周期可能拖上几个星期甚至几个月核心原因不是不想修而是“修一次太痛了”。1.2 热升级的核心价值内核热升级也叫内核热补丁或 Livepatch核心思路是不需要重启系统直接在内核运行的过程中把有问题的函数替换成修复后的版本让补丁立即生效。听起来像在做心脏手术——不能停跳还要把有病变的部分换掉。没错技术复杂度就在这。但一旦跑通价值非常明显对比维度传统升级内核热升级系统重启需要不需要业务中断有无补丁生效时间分钟到小时级秒级到分钟级适用场景大版本升级、内核功能变更安全补丁、关键 bugfix回滚难度改 grub、切旧内核卸载补丁模块或重启恢复优点很直观但它不是万能的。热升级主要针对函数级修复如果漏洞修复需要改变数据结构、修改函数签名、调整全局变量布局那热升级就无能为力了。这一点后文会展开细说。1.3 OpenEuler 对内核热升级的支持情况OpenEuler 作为面向数字基础设施的开源操作系统在内核热升级这块有自己的实践和工具链支持。它基于内核社区的 livepatch 机制同时集成了 kpatch 相关工具方便用户直接生成和管理热补丁。不过说实话OpenEuler 不同小版本对内核热升级的支持程度是有差异的不是所有版本开箱即用。实际操作前需要确认两件事内核版本是否编译了 livepatch 相关配置项。系统是否提供了配套的 kpatch 工具链。这两个检查步骤放到下一章详细讲。2. 动手之前环境准备与前置检查2.1 确认内核版本和系统版本在开始任何热升级操作前先确认当前系统环境和内核版本。拿我自己常用的环境举例登录后第一件事就是执行cat /etc/openEuler-release uname -r输出大致是这样的openEuler release 22.03 LTS 5.10.0-60.18.0.50.oe2203.x86_64这一步有两个目的一是确认架构类型x86_64 和 aarch64 的补丁包不能混用二是记下完整内核版本字符串后续构建或下载补丁包时必须完全匹配。2.2 检查内核配置项热升级依赖内核开启某些配置。查询方式是在系统里直接看当前内核的配置grep -E CONFIG_LIVEPATCH|CONFIG_KALLSYMS|CONFIG_KALLSYMS_ALL|CONFIG_FTRACE /boot/config-$(uname -r)正常情况下应该能看到类似这样的输出CONFIG_LIVEPATCHy CONFIG_KALLSYMSy CONFIG_KALLSYMS_ALLy CONFIG_FTRACEy如果CONFIG_LIVEPATCH不是y说明当前内核没有启用热补丁支持后面的步骤就无从谈起。另一个和热升级强相关的配置是CONFIG_HAVE_LIVEPATCH这是架构层面的支持开关。一般官方发布的内核都会开启但我遇到过有人在自编译内核时把这些配置关掉的情况导致后面热补丁加载直接报operation not supported。2.3 安装 kpatch 工具链OpenEuler 的 kpatch 工具链分为两部分kpatch管理热补丁的用户态工具负责加载、卸载、查看补丁模块。kpatch-build构建热补丁的工具通常需要配合内核源码和 debug 信息包使用。在 OpenEuler 上安装比较简单dnf install -y kpatch kpatch-build安装完成后确认版本kpatch version这里有个小插曲我在内网环境第一次安装时仓库里没有 kpatch-build只有 kpatch。原因是没有启用源里的source仓库或者AppStream源配置不完整。后来把源补全、执行dnf makecache之后才装上。所以如果你也遇到装不全的情况先检查源而不是怀疑工具本身。2.4 确保网络和基础依赖构建热补丁时需要下载内核源码包、安装编译依赖因此需要提前确认源可达。如果是在隔离网络环境需要把相关 RPM 包提前同步到本地。基础依赖包括dnf install -y gcc make patch bison flex openssl-devel elfutils-libelf-devel rpm-build注意rpm-build在构建阶段会用到因为 kpatch-build 内部需要将源码打成一个临时 RPM 并进行编译。少了它会在解析源包时直接报错。2.5 关于内核源码和 debug 包构建自定义热补丁时kpatch-build 需要两份关键材料当前内核对应的源码包kernel-source-version.rpm当前内核对应的 debug 信息包kernel-debuginfo-version.rpm这两者的版本号必须和当前运行内核完全一致。OpenEuler 的源里会根据内核版本提供对应的包搜索方式dnf list --available | grep kernel-source dnf list --available | grep kernel-debuginfo如果找不到可能是源里的版本已经更新当前运行内核的配套包被移到了 archive 源。这时候需要去历史源里翻找或者手动下载 RPM 后本地安装。这一步是整个流程里最容易卡住的地方也是我几次构建失败的根源。3. OpenEuler 内核热升级的完整实操流程3.1 获取补丁包直接下载还是自己构建内核热升级补丁的获取有两种主要途径使用发行版或厂商发布的热补丁 RPM 包。自己准备补丁文件并构建热补丁模块。对企业用户来说如果系统服务商提供了现成的热补丁包直接下载安装是最省事、最稳妥的方案。毕竟这些补丁包经过严格测试版本匹配问题已经被处理过了。如果是自己维护内核或者需要针对特定 CVE 做快速响应那就得走构建路线。自己在构建时需要先准备好补丁文件。通常来源是内核社区邮件列表里的修复 patch或者从高版本内核中提取的对应改动。举个例子如果当前内核是5.10.0-60.18.0.50.oe2203.x86_64要修复某个文件系统相关的 bug可以到内核社区找对应的 fix commit然后用git format-patch或直接保存 diff 生成.patch文件。3.2 使用 kpatch-build 构建热补丁构建热补丁是整套流程的核心步骤。这里用一个简化的例子来说明。假设我已经准备好了补丁文件fix-bug.patch内核版本和当前运行系统完全一致。构建命令大致是kpatch-build -v /usr/lib/debug/lib/modules/$(uname -r)/vmlinux \ -s /usr/src/kernels/$(uname -r) \ -t fix-bug.patch \ -n fix-bug参数含义-v指定带有 debug 信息的 vmlinux 文件路径。-s指定内核源码目录。-t指定补丁文件。-n指定生成的热补丁模块名称也就是最终.ko模块的名字。构建过程会执行以下步骤校验内核源码和 vmlinux 是否匹配。将补丁文件应用到源码树。对源码进行编译生成新内核对象。对比新旧内核对象提取差异函数。生成热补丁模块通常输出到当前目录下文件名类似fix-bug.ko。整个过程可能持续十分钟到半小时不等取决于内核源码量和服务器性能。机器配置较低时出现过编译 OOM 的情况建议构建时给虚拟机或容器分配足够的内存至少在 4GB 以上。3.3 加载热补丁模块得到.ko文件后需要把它加载进内核。kpatch 的加载命令是kpatch load fix-bug.ko加载成功后可以通过以下命令查看补丁状态kpatch list输出会显示已经加载的补丁名称、模块路径以及状态信息。类似Loaded patch modules: fix-bug [enabled]如果想确认补丁在内核里的具体状态可以查看 sysfs 接口cat /sys/kernel/livepatch/fix-bug/enabled输出1表示补丁已启用0表示已禁用。3.4 验证补丁是否真的生效补丁加载成功不等于修复生效还需要验证。最直接的方式是通过dmesg查看内核日志dmesg | tail -n 30正常会看到类似这样的日志livepatch: enabling patch fix-bug livepatch: fix-bug: starting patching transition livepatch: fix-bug: patching complete这里有一个很重要的概念livepatch 的补丁应用过程是“平滑过渡”的不是瞬间完成。原因是一个正在运行的内核里同一个函数可能有多个线程正在执行。直接改函数入口地址已经执行到中间的线程不会受影响新进入的线程才会走新代码。内核需要等待所有正在执行的旧代码线程退出才算完成补丁切换。这个机制叫“一致性模型”。所以如果你打上补丁后立刻查看可能会看到transition字样说明还在切换中。需要等几秒钟等所有 CPU 上的核心状态都收敛状态才会变为patching complete。3.5 让热补丁开机自动生效生产环境最稳妥的做法是补丁加载后让它能跨重启保持。kpatch 提供了一条命令kpatch install fix-bug.ko这条命令会把补丁模块安装到系统的补丁管理目录并注册为 systemd 服务。下次开机时系统会自动加载该模块。如果想卸载补丁并停止自动加载kpatch uninstall fix-bug注意kpatch unload和kpatch uninstall是两回事。unload是立即从当前运行的内核中移除补丁uninstall是移除开机自启动的注册关系。实际回滚操作中经常需要组合使用。4. 热补丁的回滚与异常恢复4.1 常规回滚操作如果某个热补丁导致系统异常比如触发内核 panic 或者特定进程崩溃需要立即回滚。回滚的第一步是禁用补丁echo 0 | sudo tee /sys/kernel/livepatch/fix-bug/enabled或者直接卸载模块kpatch unload fix-bug卸载后内核会回到修补前的函数执行路径。这个过程同样存在一个过渡期内核会等待所有正在执行新补丁函数的线程退出然后再切换到旧函数。日志里会看到reverting相关字样。4.2 卸载失败的处理但实际场景中可能会出现卸载失败的情况原因通常是有进程长时间占用某个内核锁导致一致性模型无法收敛。这时候有几个处理思路等待更长时间有时只是业务高峰期线程切换慢。排查是否有进程处于 D 状态也就是不可中断睡眠这种状态会阻碍补丁回滚。实在等不到只能安排一次有计划的节点重启重启后补丁自然失效。我的经验是不要在业务高峰强行回滚。某个核心路径的补丁在卸载时如果遇到大量并发业务过渡时间会显著拉长值班人员容易误判为“卡死”然后手忙脚乱启重启。实际上再等一两分钟可能就收敛了。4.3 回滚后的内核状态确认回滚后不能只看命令是否执行成功还要确认内核真的回到了旧代码路径。可以通过对比函数地址来实现cat /proc/kallsyms | grep function_name如果补丁还在生效该函数地址会指向补丁模块的地址范围如果回滚成功地址会恢复到内核主镜像地址范围。这个操作需要 root 权限同时部分内核会隐藏符号地址需要kptr_restrict0或使用 bpf 工具辅助确认。5. 常见问题与排查技巧实录问题现象可能原因解决思路kpatch-build报vmlinux not founddebuginfo 包未安装或路径指定错误检查/usr/lib/debug/lib/modules/$(uname -r)/vmlinux是否存在加载补丁时提示module verification failed当前内核开启了模块签名校验关闭 secure boot或使用发行版官方签名工具对模块签名加载补丁后业务进程出现轻微卡顿一致性模型切换期间线程调度短暂受影响观察 5 到 10 分钟一般会自动恢复若持续卡顿回滚补丁kpatch list能看到补丁但enabled显示 0补丁未完整应用或处于过渡状态查看 dmesg 中的 livepatch 日志确认是否卡在 transition构建时提示某个符号找不到补丁文件引用了内核源码里不存在的符号确认补丁文件与内核版本完全匹配不要跨小版本打补丁补丁加载成功但内核崩溃补丁本身有 bug或与第三方内核模块冲突回滚补丁并向补丁提供方反馈5.1 版本不匹配是最大的坑我在实际操作中遇到最多的问题就是内核版本号完全一致但源码包版本不一致。比如uname -r显示5.10.0-60.18.0.50.oe2203.x86_64但安装的 kernel-source 实际是5.10.0-60.18.0.60或略高的小版本。kpatch-build 在比对 vmlinux 和源码时会报出难以理解的错误信息比如kpatch: file not found或者section header不匹配。解决办法是查看 RPM 包的完整版本号rpm -q kernel-source kernel-debuginfo拿到输出后逐个和/boot/config-$(uname -r)里的版本号比对确保三者严格一致。5.2 第三方内核模块与热补丁的冲突这是很多人没意识到的问题。如果你在 OpenEuler 上装过 Docker 或一些需要加载内核模块的软件比如某些安全 agent它们可能引入了自己的内核模块。这些模块如果和热补丁修改的函数有交互就有可能在补丁加载时触发异常。我的建议是在生产环境打热补丁之前先确认系统里加载了哪些第三方模块lsmod | grep -v -E ^(Module|Size|Used)对于不在原厂内核模块列表里的模块要特别留意。如果补丁涉及 VFS、网络协议栈这些通用子系统第三方模块的冲突概率会更高。5.3 热补丁不是越频繁越好还有一点经验想分享内核热升级适合应对突发安全漏洞和关键缺陷但不适合作为日常内核更新的替代方案。原因有几点热补丁覆盖范围有限函数级修复不能替代完整内核版本的 bugfix。长期只打热补丁不升级内核会导致补丁累积管理成本上升。热补丁模块本身占用内存虽然单个补丁只有几十 KB 到几百 KB但累积多了也是一笔开销。部分热补丁可能在随后的内核重启升级后被“吸收”进新内核但这个过程需要人工确认和清理。所以我自己的维护思路是安全漏洞尽快打热补丁不等维护窗口。非紧急 bugfix攒一批在下一次计划维护窗口统一升级内核。每次打完热补丁记录补丁号、对应 CVE、业务影响形成台账。5.4 热升级对容器和虚拟化环境的影响前面提到 Docker 之类的容器场景这里单独说一下。OpenEuler 上跑着容器在宿主机打热补丁是安全的因为容器共享宿主机内核补丁作用于宿主机内核对容器内进程同样生效。但要注意容器内如果运行的是独立内核模块的 Runc、GVisor 这类方案热补丁的作用域就不一样了。Runc 容器和宿主机共享内核热补丁能覆盖到容器内系统调用链路GVisor 这种用户态内核则完全不受宿主机内核热补丁影响需要单独处理。虚拟机场景则要看虚拟化类型KVM 虚拟机宿主机热补丁不影响虚拟机内核客户机需要单独处理。容器场景容器与宿主机共享内核宿主机热补丁通常对容器生效。这些边界条件在做热升级前最好先想清楚否则容易产生“明明打了补丁为什么没效果”的困惑。6. 实操心得与经验总结写到这里把整个实操过程中的核心体会做个梳理。内核热升级是一套非常实用的机制它在 OpenEuler 上已经具备相对完善的工具链但从接触到熟练使用还是有几个关键点需要掌握。第一版本匹配是做热升级的第一原则。内核版本、源码包、debuginfo 包三者缺一不可、版本号必须完全一致。任何一处对不上后面的构建和加载阶段都会出问题。第二补丁加载后的等待环节要有耐心。livepatch 的过渡机制保证了业务不中断但代价就是补丁生效需要时间。不能像普通内核模块加载一样以为瞬间完成一定要看 dmesg 里的 transition 状态。第三回滚方案要提前想好不要等出了问题再翻文档。kpatch unload和自动启动的移除命令要提前写好、做好记录甚至可以在测试环境演练一遍。真在半夜出问题时你肯定不想临场查命令。第四也是最重要的一点热升级不是银弹。它解决的是“等不了重启窗口”的燃眉之急但内核大版本演进、功能更新、长期稳定性维护仍然需要有计划的内核升级流程来兜底。把热升级纳入整个内核生命周期管理体系才是正确的姿势。最后再分享一个小技巧如果你需要给多台同样内核版本的机器打同一个热补丁不需要每台机器都构建一遍。构建一次得到.ko文件后把它复制到其他机器直接kpatch load就行。我在 30 多台机器上分批打过同一个补丁整个过程很快效果也很稳定。希望这篇内容能帮你少踩一些坑把内核热升级真正用起来。
返回列表