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 模块时,此结构由模块加载器填充。