原子替换 & 累积补丁

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 正确禁用时才会调用它。