原子替换 & 累积补丁¶
Livepatch 之间可能存在依赖关系。如果多个补丁需要对同一个函数进行不同的更改,那么我们需要定义安装补丁的顺序。并且来自任何较新 livepatch 的函数实现必须在较旧的函数之上进行。
这可能会成为维护的噩梦。特别是当更多补丁以不同的方式修改同一个函数时。
一个优雅的解决方案是使用名为“原子替换”的功能。它允许创建所谓的“累积补丁”。它们包括所有旧 livepatch 中所有想要的更改,并在一个过渡中完全替换它们。
用法¶
可以通过在 struct klp_patch
中设置 “replace” 标志来启用原子替换,例如
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
.replace = true,
};
然后所有进程都被迁移为仅使用新补丁中的代码。一旦过渡完成,所有旧补丁都会自动禁用。
Ftrace 处理程序会从不再被新累积补丁修改的函数中透明地移除。
因此,livepatch 作者可能仅维护一个累积补丁的源代码。这有助于在添加或删除各种修复或功能时保持补丁的一致性。
用户可以在完成过渡后仅保留系统上安装的最后一个补丁。这有助于清楚地了解实际使用的代码。此外,livepatch 可以被视为修改内核行为的“正常”模块。唯一的区别是它可以在运行时更新,而不会破坏其功能。
特性¶
原子替换允许
在升级其他函数的同时,原子地还原先前补丁中的某些函数。
消除不再修补的函数的核心重定向可能造成的性能影响。
减少用户对 livepatch 之间依赖关系的困惑。
限制:¶
一旦操作完成,就没有直接的方法可以原子地逆转它并恢复被替换的补丁。
一个好的做法是在任何已发布的 livepatch 中设置 .replace 标志。然后重新添加较旧的 livepatch 等同于降级到该补丁。只要 livepatch 不在(取消)补丁回调或
module_init()
或module_exit()
函数中进行额外的修改,这是安全的,请参见下文。还要注意,只有在过渡不是强制的情况下,才能删除并再次加载被替换的补丁。
只执行_新_累积 livepatch 中的(取消)补丁回调。将忽略来自被替换补丁的任何回调。
换句话说,累积补丁负责执行任何必要的动作,以正确替换任何旧的补丁。
因此,用较旧的累积补丁替换较新的累积补丁可能是危险的。旧的 livepatch 可能无法提供必要的回调。
在某些情况下,这可能被视为一种限制。但在许多其他情况下,它使生活更轻松。只有新的累积 livepatch 才知道添加/删除哪些修复/功能,以及平稳过渡需要哪些特殊操作。
在任何情况下,如果调用所有已启用补丁的回调,那么考虑各种回调及其交互的顺序将是一场噩梦。
没有对影子变量进行特殊处理。Livepatch 作者必须创建自己的规则,以了解如何将它们从一个累积补丁传递到另一个累积补丁。特别是,他们不应在
module_exit()
函数中盲目删除它们。一个好的做法可能是在后取消补丁回调中删除影子变量。只有在 livepatch 被正确禁用时才会调用它。