(取消)打补丁回调
Livepatch (取消)打补丁回调提供了一种机制,用于在内核对象被 (取消)打补丁时执行回调函数。它们可以被视为一种强大的功能,扩展了 livepatch 的能力,包括:
- 对全局数据进行安全更新
- 对 init 和 probe 函数进行“打补丁”
- 对无法进行补丁的代码进行补丁(例如汇编)
在大多数情况下,(取消)打补丁回调通常需要与内存屏障和内核同步原语一起使用,比如互斥锁/自旋锁,甚至 stop_machine(),以避免并发问题。
1. 动机
回调与现有的内核设施不同:
- 当禁用和重新启用补丁时,模块的初始化/退出代码不会运行。
- 模块通知程序无法阻止待打补丁的模块加载。
回调是 klp_object 结构的一部分,它们的实现是特定于该 klp_object 的。其他 livepatch 对象可能会被打补丁,也可能不会,而与目标 klp_object 的当前状态无关。
2. 回调类型
可以为以下 livepatch 操作注册回调:
- 预打补丁Pre-patch
- 在 klp_object 被打补丁之前
- 后打补丁Post-patch
- 在 klp_object 被打补丁并在所有任务中激活后
- 预取消打补丁Pre-unpatch
- 在 klp_object 被取消打补丁之前(即,打补丁的代码是活动的),用于清理后打补丁回调资源
- 后取消打补丁Post-unpatch
- 在 klp_object 被取消打补丁后,所有代码已被恢复且没有任务在运行打补丁的代码,用于清理预打补丁回调资源
3. 工作原理
每个回调都是可选的,省略一个不会排除指定任何其他回调。但是,livepatching 核心以对称方式执行处理程序:预打补丁回调有一个后取消打补丁的对应部分,后打补丁回调有一个预取消打补丁的对应部分。只有在执行了相应的打补丁回调时,取消打补丁回调才会被执行。典型的用例是将一个获取和配置资源的打补丁处理程序与一个拆除和释放相同资源的取消打补丁处理程序配对使用。
只有在其主机 klp_object 被加载时才会执行回调。对于内核内的 vmlinux 目标,这意味着当启用/禁用 livepatch 时,回调将始终执行。对于补丁目标内核模块,只有在目标模块加载时,回调才会执行。当模块目标被(取消)加载时,只有在启用 livepatch 模块时,其回调才会执行。
如果预打补丁回调被指定,预期它会返回一个状态码(成功为 0,错误为 -ERRNO)。错误状态码表示给 livepatching 核心的当前 klp_object 的打补丁不安全,并且需要停止当前的打补丁请求。(当没有提供预打补丁回调时,过渡被认为是安全的。)如果预打补丁返回失败,内核的模块加载程序将:
- 拒绝加载 livepatch,如果 livepatch 在目标代码之后加载。
- 或者:
- 拒绝加载模块,如果 livepatch 已成功加载。
如果由于失败的预打补丁回调或其他原因导致对象无法打补丁,将不会为给定的 klp_object 执行后打补丁、预取消打补丁或后取消打补丁回调。
如果补丁转换被撤销,将不会运行任何预取消打补丁处理程序(这遵循前面提到的对称性 -- 只有在执行了相应的后打补丁回调时,预取消打补丁回调才会发生)。
如果对象成功打补丁,但由于某种原因补丁转换从未开始(例如,如果另一个对象未能打补丁),则只会调用后取消打补丁回调。
4. 使用案例
演示回调 API 的示例 livepatch 模块可以在 samples/livepatch/ 目录中找到。这些示例已经被修改以在 kselftests 中使用,并可以在 lib/livepatch 目录中找到。
全局数据更新
预打补丁回调可以用于更新全局变量。例如,75ff39ccc1bd ("tcp: make challenge acks less predictable") 改变了一个全局 sysctl,并且对 tcp_send_challenge_ack() 函数进行了补丁。
在这种情况下,如果我们非常谨慎,可能有意义的是在补丁完成后使用后打补丁回调来打补丁数据,这样 tcp_send_challenge_ack() 就可以首先更改为使用 READ_ONCE 读取 sysctl_tcp_challenge_ack_limit。
__init 和 probe 函数补丁支持
虽然 __init 和 probe 函数不能直接进行 livepatch,但可能可以通过预/后打补丁回调来实现类似的更新。
提交 48900cb6af42 ("virtio-net: drop NETIF_F_FRAGLIST") 改变了 virtnet_probe() 初始化其驱动的 net_device 特性的方式。预/后打补丁回调可以遍历所有这样的设备,对它们的 hw_features 值进行类似的更改。(该值的客户端函数可能需要相应地更新。)