Livepatch 模組 ELF 格式

本文件概述了 livepatch 模組必須遵循的 ELF 格式要求。

1. 背景和動機

以前,livepatch 需要單獨的特定於架構的程式碼來編寫重定位。但是,用於編寫重定位的特定於架構的程式碼已經存在於模組載入器中,因此以前的方法產生了冗餘程式碼。因此,livepatch 沒有複製程式碼並重新實現模組載入器已經可以執行的操作,而是利用模組載入器中的現有程式碼來執行所有特定於架構的重定位工作。具體來說,livepatch 重用模組載入器中的 apply_relocate_add() 函式來編寫重定位。本文件中描述的補丁模組 ELF 格式使 livepatch 能夠做到這一點。希望這將使 livepatch 更容易移植到其他架構,並減少將 livepatch 移植到特定架構所需的特定於架構的程式碼量。

由於 apply_relocate_add() 需要訪問模組的節頭表、符號表和重定位節索引,因此為 livepatch 模組保留了 ELF 資訊(參見第 5 節)。Livepatch 管理自己的重定位節和符號,本文件將對此進行描述。用於標記 livepatch 符號和重定位節的 ELF 常量是根據 glibc 的定義從特定於作業系統的範圍中選擇的。

為什麼 livepatch 需要編寫自己的重定位?

典型的 livepatch 模組包含函式的已修補版本,這些函式可以引用非匯出的全域性符號和未包含的本地符號。引用這些型別符號的重定位不能保持原樣,因為核心模組載入器無法解析它們,因此會拒絕 livepatch 模組。此外,我們不能應用影響在補丁模組載入時尚未載入的模組的重定位(例如,對未載入的驅動程式的補丁)。以前,livepatch 透過在生成的補丁模組 ELF 輸出中嵌入特殊的“dynrela”(動態 rela)節來解決這個問題。使用這些 dynrela 節,livepatch 可以解析符號,同時考慮其範圍和符號所屬的模組,然後手動應用動態重定位。但是,這種方法要求 livepatch 提供特定於架構的程式碼才能編寫這些重定位。在新格式中,livepatch 管理自己的 SHT_RELA 重定位節以代替 dynrela 節,並且 relas 引用的符號是特殊的 livepatch 符號(參見第 2 節和第 3 節)。特定於架構的 livepatch 重定位程式碼被呼叫 apply_relocate_add() 替換。

2. Livepatch modinfo 欄位

Livepatch 模組必須具有“livepatch” modinfo 屬性。有關如何操作,請參見 samples/livepatch/ 中的示例 livepatch 模組。

使用者可以使用 ‘modinfo’ 命令並查詢 “livepatch” 欄位的存在來識別 Livepatch 模組。核心模組載入器也使用此欄位來識別 livepatch 模組。

示例:

Modinfo 輸出

% modinfo livepatch-meminfo.ko
filename:               livepatch-meminfo.ko
livepatch:              Y
license:                GPL
depends:
vermagic:               4.3.0+ SMP mod_unload

3. Livepatch 重定位節

Livepatch 模組管理自己的 ELF 重定位節,以便在適當的時候將重定位應用於模組以及核心 (vmlinux)。例如,如果補丁模組修補了當前未載入的驅動程式,則 livepatch 將在驅動程式載入後立即將相應的 livepatch 重定位節應用於該驅動程式。

補丁模組中的每個“物件”(例如,vmlinux 或模組)都可以具有與其關聯的多個 livepatch 重定位節(例如,對同一物件中多個函式的補丁)。livepatch 重定位節和目標節(通常是函式的文字節)之間存在 1-1 對應關係,重定位應用於目標節。livepatch 模組也可能沒有 livepatch 重定位節,如示例 livepatch 模組所示(參見 samples/livepatch)。

由於為 livepatch 模組保留了 ELF 資訊(參見第 5 節),因此只需將適當的節索引傳遞給 apply_relocate_add() 即可應用 livepatch 重定位節,然後 apply_relocate_add() 使用該節索引訪問重定位節並應用重定位。

livepatch 重定位節中 rela 引用的每個符號都是一個 livepatch 符號。在 livepatch 可以呼叫 apply_relocate_add() 之前,必須解析這些符號。有關更多資訊,請參見第 3 節。

3.1 Livepatch 重定位節格式

Livepatch 重定位節必須使用 SHF_RELA_LIVEPATCH 節標誌進行標記。有關定義,請參見 include/uapi/linux/elf.h。模組載入器識別此標誌,並且會在補丁模組載入時避免應用這些重定位節。這些節還必須使用 SHF_ALLOC 進行標記,以便模組載入器不會在模組載入時丟棄它們(即,它們將與其他 SHF_ALLOC 節一起復制到記憶體中)。

livepatch 重定位節的名稱必須符合以下格式

.klp.rela.objname.section_name
^        ^^     ^ ^          ^
|________||_____| |__________|
   [A]      [B]        [C]
[A]

重定位節名稱以字串“.klp.rela.”作為字首。

[B]

重定位節所屬的物件(即“vmlinux”或模組名稱)的名稱緊隨字首之後。

[C]

此重定位節應用於的節的實際名稱。

示例:

Livepatch 重定位節名稱

.klp.rela.ext4.text.ext4_attr_store
.klp.rela.vmlinux.text.cmdline_proc_show

`readelf --sections` 輸出,適用於修補 vmlinux 和模組 9p、btrfs、ext4 的補丁模組

Section Headers:
[Nr] Name                          Type                    Address          Off    Size   ES Flg Lk Inf Al
[ snip ]
[29] .klp.rela.9p.text.caches.show RELA                    0000000000000000 002d58 0000c0 18 AIo 64   9  8
[30] .klp.rela.btrfs.text.btrfs.feature.attr.show RELA     0000000000000000 002e18 000060 18 AIo 64  11  8
[ snip ]
[34] .klp.rela.ext4.text.ext4.attr.store RELA              0000000000000000 002fd8 0000d8 18 AIo 64  13  8
[35] .klp.rela.ext4.text.ext4.attr.show RELA               0000000000000000 0030b0 000150 18 AIo 64  15  8
[36] .klp.rela.vmlinux.text.cmdline.proc.show RELA         0000000000000000 003200 000018 18 AIo 64  17  8
[37] .klp.rela.vmlinux.text.meminfo.proc.show RELA         0000000000000000 003218 0000f0 18 AIo 64  19  8
[ snip ]                                       ^                                             ^
                                               |                                             |
                                              [*]                                           [*]
[*]

Livepatch 重定位節是 SHT_RELA 節,但具有一些特殊特徵。請注意,它們使用 SHF_ALLOC (“A”) 進行標記,以便在將模組載入到記憶體中時不會丟棄它們,並且還使用 SHF_RELA_LIVEPATCH 標誌(“o” - 表示特定於作業系統)。

`readelf --relocs` 輸出,適用於補丁模組

Relocation section '.klp.rela.btrfs.text.btrfs_feature_attr_show' at offset 0x2ba0 contains 4 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
000000000000001f  0000005e00000002 R_X86_64_PC32          0000000000000000 .klp.sym.vmlinux.printk,0 - 4
0000000000000028  0000003d0000000b R_X86_64_32S           0000000000000000 .klp.sym.btrfs.btrfs_ktype,0 + 0
0000000000000036  0000003b00000002 R_X86_64_PC32          0000000000000000 .klp.sym.btrfs.can_modify_feature.isra.3,0 - 4
000000000000004c  0000004900000002 R_X86_64_PC32          0000000000000000 .klp.sym.vmlinux.snprintf,0 - 4
[ snip ]                                                                   ^
                                                                           |
                                                                          [*]
[*]

重定位引用的每個符號都是一個 livepatch 符號。

4. Livepatch 符號

Livepatch 符號是 livepatch 重定位節引用的符號。這些是從已修補物件的函式新版本訪問的符號,模組載入器無法解析其地址(因為它們是本地或未匯出的全域性符號)。由於模組載入器僅解析匯出的符號,並非新的已修補函式引用的每個符號都已匯出,因此引入了 livepatch 符號。它們也用於我們無法立即知道符號地址的情況,例如在載入補丁模組時。例如,當 livepatch 修補尚未載入的模組時,情況就是如此。在這種情況下,只需在目標模組載入時解析相關的 livepatch 符號。無論如何,對於任何 livepatch 重定位節,在 livepatch 可以呼叫該重定位節的 apply_relocate_add() 之前,必須解析該節引用的所有 livepatch 符號。

Livepatch 符號必須使用 SHN_LIVEPATCH 進行標記,以便模組載入器可以識別並忽略它們。Livepatch 模組將這些符號保留在其符號表中,並且可以透過 module->symtab 訪問該符號表。

4.1 Livepatch 模組的符號表

通常,模組符號表的精簡副本(僅包含“核心”符號)透過 module->symtab 提供(請參見 kernel/module/kallsyms.c 中的 layout_symtab())。對於 livepatch 模組,在模組載入時複製到記憶體中的符號表必須與編譯補丁模組時生成的符號表完全相同。這是因為每個 livepatch 重定位節中的重定位都使用它們的符號索引引用各自的符號,並且必須保留原始符號索引(以及符號表排序),以便 apply_relocate_add() 可以找到正確的符號。

例如,以 livepatch 模組中的這個特定的 rela 為例

Relocation section '.klp.rela.btrfs.text.btrfs_feature_attr_show' at offset 0x2ba0 contains 4 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
000000000000001f  0000005e00000002 R_X86_64_PC32          0000000000000000 .klp.sym.vmlinux.printk,0 - 4

這個 rela 引用符號 ‘.klp.sym.vmlinux.printk,0’,並且符號索引編碼在 ‘Info’ 中。在這裡,它的符號索引是 0x5e,即十進位制的 94,指的是符號索引 94。

在這個補丁模組的相應符號表中,符號索引 94 指的是那個符號

[ snip ]
94: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.printk,0
[ snip ]

4.2 Livepatch 符號格式

Livepatch 符號必須將其節索引標記為 SHN_LIVEPATCH,以便模組載入器可以識別它們並且不嘗試解析它們。有關實際定義,請參見 include/uapi/linux/elf.h。

Livepatch 符號名稱必須符合以下格式

.klp.sym.objname.symbol_name,sympos
^       ^^     ^ ^         ^ ^
|_______||_____| |_________| |
   [A]     [B]       [C]    [D]
[A]

符號名稱以字串 “.klp.sym.” 作為字首。

[B]

符號所屬的物件(即“vmlinux”或模組名稱)的名稱緊隨字首之後。

[C]

符號的實際名稱。

[D]

符號在物件中的位置(根據 kallsyms)。這用於區分同一物件中的重複符號。符號位置以數字表示(0、1、2...)。唯一符號的符號位置是 0。

示例:

Livepatch 符號名稱

.klp.sym.vmlinux.snprintf,0
.klp.sym.vmlinux.printk,0
.klp.sym.btrfs.btrfs_ktype,0

`readelf --symbols` 輸出,適用於補丁模組

Symbol table '.symtab' contains 127 entries:
   Num:    Value          Size Type    Bind   Vis     Ndx         Name
   [ snip ]
    73: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.snprintf,0
    74: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.capable,0
    75: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.find_next_bit,0
    76: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.si_swapinfo,0
  [ snip ]                                               ^
                                                         |
                                                        [*]
[*]

請注意,這些符號的 ‘Ndx’(節索引)是 SHN_LIVEPATCH (0xff20)。“OS”表示特定於作業系統。

5. 符號表和 ELF 節訪問

Livepatch 模組的符號表可以透過 module->symtab 訪問。

由於 apply_relocate_add() 需要訪問模組的節頭、符號表和重定位節索引,因此為 livepatch 模組保留了 ELF 資訊,並且模組載入器透過 module->klp_info 提供對該資訊的訪問,module->klp_info 是一個 klp_modinfo 結構。載入 livepatch 模組時,此結構由模組載入器填充。