原子替换和累积补丁
在 livepatch 之间可能存在依赖关系。如果多个补丁需要对相同的函数进行不同的更改,那么我们需要定义补丁安装的顺序。并且来自任何更新的 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 被正确禁用时才会调用它。