Livepatch 系统状态变更
一些用户非常不愿意重新启动系统。这就需要提供更多的实时补丁,并在它们之间保持一定的兼容性。
通过累积实时补丁,维护更多的实时补丁要容易得多。每个新的实时补丁完全替换任何旧的实时补丁。它可以保留、添加甚至删除修复。并且通常可以安全地使用原子替换功能将任何版本的实时补丁替换为任何其他版本。
问题可能出现在影子变量和回调函数上。它们可能改变系统的行为或状态,以至于不再安全地返回并使用旧的实时补丁或原始内核代码。此外,任何新的实时补丁必须能够检测已安装的实时补丁所做的任何更改。
这就是实时补丁系统状态跟踪的用处所在。它允许:
- 存储操作和恢复系统状态所需的数据
- 使用更改 ID 和版本定义实时补丁之间的兼容性
1. 实时补丁系统状态 API
系统的状态可能会被多个实时补丁回调或新使用的代码修改。还必须能够找到已安装实时补丁所做的更改。
每个修改后的状态由 struct klp_state
描述,参见 include/linux/livepatch.h
。
每个实时补丁定义了 struct klp_states
数组。它们提到了实时补丁修改的所有状态。
实时补丁作者必须为每个 struct klp_state
定义以下两个字段:
id
:用于标识受影响的系统状态的非零数字。version
:描述给定实时补丁支持的系统状态变化的变体的数字。
可以使用两个函数来操作状态:
klp_get_state()
:获取与给定实时补丁和状态 ID 相关联的struct klp_state
。klp_get_prev_state()
:获取与给定特性 ID 和已安装实时补丁相关联的struct klp_state
。
2. 实时补丁兼容性
系统状态版本用于防止加载不兼容的实时补丁。检查是在启用实时补丁时进行的。规则如下:
- 任何全新的系统状态修改都是允许的。
- 已修改系统状态的相同或更高版本的系统状态修改对于已修改的系统状态是允许的。
- 累积实时补丁必须处理已安装实时补丁所做的所有系统状态修改。
- 非累积实时补丁允许触及已修改的系统状态。
3. 支持的场景
实时补丁和系统状态变更都有它们的生命周期。每个兼容的实时补丁必须支持以下场景:
- 当启用实时补丁时修改系统状态,且该状态尚未被正在替换的实时补丁修改。
- 接管或更新已被正在替换的实时补丁修改的系统状态。
- 当禁用实时补丁时恢复原始状态。
- 当转换被还原时,恢复先前的状态。它可能是原始系统状态,也可能是正在被替换的实时补丁所做的状态修改。
- 当发生错误且无法启用实时补丁时,移除任何已经进行的更改。
4. 预期的使用
系统状态通常由实时补丁回调修改。每个回调的预期角色如下:
pre_patch()
:在必要时分配state->data
。分配可能会失败,而pre_patch()
是唯一可以阻止加载实时补丁的回调。当数据已经由先前安装的实时补丁提供时,就不需要分配。
- 在转换完成之前,执行任何新代码所需的其他准备工作。例如,初始化
state->data
。 - 系统状态本身通常在
post_patch()
中进行修改,当整个系统能够处理它时。 - 在发生错误时清理自己的混乱。可以通过自定义代码或显式调用
post_unpatch()
来完成。
post_patch()
:在兼容时,从先前的实时补丁复制state->data
。
- 进行实际的系统状态修改。最终允许新代码使用它。
- 确保
state->data
具有所有必要的信息。 - 当不再需要时,释放来自替换实时补丁的
state->data
。
pre_unpatch()
:防止由实时补丁添加的代码依赖于系统状态更改。
- 还原系统状态修改。
post_unpatch()
:通过检查klp_get_prev_state()
区分转换反向和禁用实时补丁。
- 在转换反向时,恢复先前的系统状态。这可能意味着什么都不做。
- 移除任何不再需要的设置或数据。
注意:
pre_unpatch()
通常对post_patch()
执行对称操作。只有在禁用实时补丁时才会调用它。因此,它不需要关心任何先前安装的实时补丁。post_unpatch()
通常对pre_patch()
执行对称操作。它也可能在转换反向时被调用。因此,它必须处理先前安装的实时补丁的状态。