原子替换 & 累积补丁

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