(取消)打补丁回调

Livepatch (取消)打补丁回调为热补丁模块提供了一种机制,当内核对象被(取消)打补丁时执行回调函数。它们可以被视为一种高级功能,将热补丁的能力扩展到包括:

  • 安全更新全局数据

  • 对 init 和 probe 函数的“补丁”

  • 为原本无法打补丁的代码打补丁(即汇编代码)

在大多数情况下,(取消)打补丁回调需要与内存屏障以及内核同步原语(如互斥锁/自旋锁)甚至 stop_machine() 结合使用,以避免并发问题。

1. 动机

回调不同于现有的内核设施

  • 当禁用和重新启用补丁时,模块的 init/exit 代码不会运行。

  • 模块通知程序无法阻止待打补丁的模块加载。

回调是 klp_object 结构的一部分,其实现特定于该 klp_object。其他热补丁对象可能会或可能不会被打补丁,这与目标 klp_object 的当前状态无关。

2. 回调类型

可以针对以下热补丁操作注册回调

  • 打补丁前 (Pre-patch)
    • 在 klp_object 打补丁之前

  • 打补丁后 (Post-patch)
    • 在 klp_object 打完补丁且在所有任务中处于活动状态之后

  • 取消打补丁前 (Pre-unpatch)
    • 在对 klp_object 取消打补丁之前(即打过补丁的代码处于活动状态),用于清理打补丁后的回调资源

  • 取消打补丁后 (Post-unpatch)
    • 在对 klp_object 取消打补丁之后,所有代码已恢复且没有任务在运行打过补丁的代码,用于清理打补丁前的回调资源

3. 工作原理

每个回调都是可选的,省略其中一个并不妨碍指定其他回调。然而,热补丁核心以对称的方式执行处理程序:打补丁前回调有一个对应的取消打补丁后回调,打补丁后回调有一个对应的取消打补丁前回调。只有在其对应的打补丁回调被执行的情况下,取消打补丁回调才会执行。典型的用例是将获取并配置资源的打补丁处理程序与拆卸并释放这些相同资源的取消打补丁处理程序配对。

回调只有在其宿主 klp_object 被加载时才会执行。对于内核内的 vmlinux 目标,这意味着当启用/禁用热补丁时,回调将总是执行。对于打补丁的目标内核模块,仅当目标模块已加载时,回调才会执行。当模块目标被加载或卸载时,其回调仅在热补丁模块已启用时才会执行。

如果指定了打补丁前回调,预期它会返回一个状态码(成功返回 0,出错返回 -ERRNO)。错误状态码向热补丁核心指示当前 klp_object 的打补丁操作是不安全的,并终止当前的打补丁请求。(如果未提供打补丁前回调,则假定转换是安全的。)如果打补丁前回调返回失败,内核的模块加载器将

  • 如果热补丁是在目标代码之后加载的,则拒绝加载热补丁。

    或者

  • 如果热补丁已经成功加载,则拒绝加载模块。

如果某个 klp_object 打补丁失败(由于 pre_patch 回调失败或任何其他原因),则不会为该 klp_object 执行任何打补丁后、取消打补丁前或取消打补丁后的回调。

如果补丁转换被逆转,则不会运行任何取消打补丁前处理程序(这遵循前面提到的对称性——取消打补丁前回调仅在其对应的打补丁后回调已执行时才会发生)。

如果对象确实成功打上了补丁,但由于某种原因补丁转换从未开始(例如,如果另一个对象打补丁失败),则只会调用取消打补丁后回调。

4. 用例

演示回调 API 的示例热补丁模块可以在 samples/livepatch/ 目录中找到。这些示例已修改用于 kselftests,可以在 lib/livepatch 目录中找到。

全局数据更新

打补丁前回调对于更新全局变量非常有用。例如,commit 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 函数不能直接打热补丁,但可以通过打补丁前/后回调来实现类似的更新。

commit 48900cb6af42 (“virtio-net: drop NETIF_F_FRAGLIST”) 改变了 virtnet_probe() 初始化其驱动程序的 net_device 特性的方式。打补丁前/后回调可以遍历所有此类设备,对它们的 hw_features 值做出类似的更改。(该值的客户端函数可能需要进行相应更新。)