原子替换 & 累积补丁¶
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()
函数中盲目地删除它们。一个好的做法可能是在 post-unpatch 回调中删除影子变量。只有在 livepatch 正确禁用时才会调用它。