Linux Kernel 4.0 中的 live patching 是如何實現的?


Linux 4.0 中的 Live Patching 功能只算是個 infrastructure, 並且目前只在 X86 平台上可用, 其它平台的支持還在開發中。

  1. 一些背景

之所以說只是個 infrastructure, 是因為它只實現了熱補丁的策略(如何做下文會介紹),而在一個運行中系統中打熱補丁前後如何保證一致性的機制, 主線內核仍然不支持。 保持一致性是 Live Patching 的核心和關鍵所在。 試想,在一個循環中,如果實時補丁了一個在循環中調用的函數, 如果無法保證該函數版本的一致性,那不可避免地很可能遇到意外的情況,甚至是系統 crash。

事實上, Live Patching 是兩大 Linux 發行版商 Red Hat 和 SuSE 去年(2014)開始主推進入內核主線的功能, 並都拿出了各自的解決方案, 即 kPatch 和 kGraft, 這兩種方案都有各自解決一致性保證的解決方法:

  • kPatch 的做法是在熱補丁時, 調用 stop_machine() 介面把機器停住,然後檢查每個線程棧, 看要補丁的函數有無在運行,若沒有,則可安全打補丁; 若有,則退出。然後再繼續恢復機器運行。

  • kGraft 的做法是運用雙宇宙模型,即維護另一份要修改函數的版本,每個線程都會在安全的時候切換到新版本上,這個安全的地方可以是系統調用返回的時候,因為此時不在內核態,可以安全修補。這其實是借鑒了內核中的同步機制 RCU(Read Copy Update) 的做法。

這兩種方法都各有利弊,對兩種方案的取捨要涉及很多細節性的東西 ,所以各方要討論一個能很好支持兩種方案的優點的方案。4.0 中 算是基礎性工作, 它實現了熱補丁的功能, 但一致性的解決方案還要等這兩家之後基於此工作之上的解決方案。所以目前該功能只支持一些 trivial 的補丁,如不改變數據結構,不改變函數的返回值語義, 等。但這些也足以應付目前很多安全性補丁,很多 CVE 的 fix 就是加一兩行 sanity check 而已。

2. 如何熱補丁

兩個問題:

  1. 補丁的內核表示

  2. 補丁的替換

補丁的內核表示

補丁經由一個用戶態工具轉換(或手寫)成一個內核模塊, 然後可以實時插入到內核。[1] 是一個例子。

Live Patching Core 提供了API :

  • klp_register_patch: 在內核模塊中,主要工作就是把要修改的 patch 以 klp_patch 結構封裝起來, 然後作為參數調用該函數註冊。它的主要工作是對要補丁的對象(函數或變數)進行 sanity check, 如果這些對象是可重定位的,還要根據重定位類型修改相關數據結構(如何修改,是各體系架構相關的,也跟執行格式相關,如 ELF ). 注意,這只是註冊, 還沒有真正打補丁。見下節。
  • klp_unregister_patch: 上一個介面的反操作,在補丁模塊卸載時調用,參考例子。

補丁的替換

註冊成功後,要使用了,可以調用另一個 API:

  • klp_enable_patch: 主要執行符號查找, 重定位操作, 然後藉助內核一個機制來完成替換。

Live Patching 使用了現有的內核 tracing 的機制來完成替換功能。 ftrace 是 Linux 內核中重要的

進行 tracing 的工具,它最初是一個為了追蹤內核函數運行的工具,後來發展成一個 infrastructure, 除了追蹤內核函數運行,還實現了追蹤中斷/搶佔關閉時長, 調度/等待時長 , 預定義/動態載入事件,等等追蹤器 。 ftrace 的關鍵點是利用編譯器的 profile 功能,如 GCC 的 mcount, 它可以 hook 進內核函數入口, 調用 mcount, 而內核重載 mcount, 從而實現各種追蹤器功能。 所以, 目前 Live Patching 的工作簡單就是在 hook 的回調中,修改新的代碼的入口址就行。

3. 參考:

[1] 一個打 patch 的例子: kernel/git/torvalds/linux.git

[2] 進入 4.0 內核的 Live Patching 代碼: kernel/git/torvalds/linux.git


沒研究過Linux的live patching技術,但VxWorks上的熱補丁技術都已經用了好多年了,華為還是H3C的交換機上就有,而且是非x86平台的。

其實熱補丁的原理不複雜,只需要一張符號表,因為VxWorks里符號表是跟著image一起載入到板子上的,所以符號信息都是已知的,然後剩下的動作就是,先把整個系統里所有task全suspend,確認要打補丁的函數沒在使用,然後把函數內容清空,換成一個跳轉指令,跳轉到熱補丁的新函數上即可。

要說複雜,一點都不複雜。

但有一些基本的限制:

1、系統要有符號表,補丁函數必須在這個符號表裡;

2、系統的大部分進程能被暫停;

3、系統需要有調試器能監測任務狀態、查找符號表;

4、不是所有東西都能打補丁,比如調試器本身就不行(以及其它帶UNBREAKABLE標識的,包括調試器使用的整個任務棧);

5、熱補丁不保證和新鏡像的一致性;

6、好像不能在同一個函數上打多次補丁,有隱患;

反正原理上不複雜,只不過需要系統內有各種工具支撐,有了這些東西不管是什麼系統技術上不存在熱補丁的障礙。


livepatch 和 kpatch 差不多,kpatch 分為以下幾部分

  • kpatch-build: a collection of tools which convert a source diff patch to a patch module. They work by compiling the kernel both with and without the source patch, comparing the binaries, and generating a patch module which includes new binary versions of the functions to be replaced.

  • patch module: a kernel module (.ko file) which includes the replacement functions and metadata about the original functions.

  • kpatch core module: a kernel module (.ko file) which provides an interface for the patch modules to register new functions for replacement. It uses the kernel ftrace subsystem to hook into the original function"s mcount call instruction, so that a call to the original function is redirected to the replacement function.

  • kpatch utility: a command-line tool which allows a user to manage a collection of patch modules. One or more patch modules may be configured to load at boot time, so that a system can remain patched even after a reboot into the same version of the kernel.

所以,還是看 dynup/kpatch · GitHub 吧……

(好,考完一門繼續寫)

上面四個部分,4.0內核里的 livepatch 只是替代了第三個 kpatch core module,現在比較好的生成 patch module 的方法還是用 kpatch-build。

我估計題主想問的是 patch 這步的具體實現,那麼,其實很簡單。Linux kernel 里本來就有一種追蹤函數調用的機制,叫做 ftrace,可以在每個函數調用開始(prologue)前觸發特定代碼,而 kpatch 只是在 ftrace 結束時加上了一個 hook,通過修改棧上返回地址來跳轉到新的函數上,實現了用新函數來取代舊函數。

其實 kpatch 最複雜 / 精妙的地方在如何生成這樣的一個 patch module(上面第二個),kpatch 使用了一種對 elf 文件做 diff 的方式,對比新舊兩個內核生成補丁,這才是最黑科技的地方好嗎。。


推薦閱讀:

如何用c++監控windows和linux文件夾中文件的變化,有沒有什麼api可以讓系統在保存文件的時候通知程序?
如何學會使用 Linux 操作系統?
Arch Linux 怎麼安裝?
有非常豐富的 Linux/Unix 下工作的經驗指的是什麼?
Nginx 和 Apache 在 Windows 下的性能表現誰更好?

TAG:Linux | C編程語言 | Linux內核 |