Livepatch 模块 ELF 格式

本文档概述了 livepatch 模块必须遵循的 ELF 格式要求。

1. 背景与动机

过去,livepatch 需要单独的、特定于架构的代码来编写重定位。然而,用于编写重定位的架构特定代码已经存在于模块加载器中,因此这种旧方法产生了冗余代码。因此,livepatch 没有重复编写代码并重新实现模块加载器已经能够做的事情,而是利用模块加载器中的现有代码来执行所有与架构相关的重定位工作。具体来说,livepatch 重用了模块加载器中的 apply_relocate_add() 函数来编写重定位。本文档中描述的补丁模块 ELF 格式使 livepatch 能够做到这一点。希望这能使 livepatch 更容易移植到其他架构,并减少将 livepatch 移植到特定架构所需的架构特定代码量。

由于 apply_relocate_add() 需要访问模块的段头表(section header table)、符号表和重定位段索引,因此为 livepatch 模块保留了 ELF 信息(参见第 5 节)。Livepatch 管理其自己的重定位段和符号,本文档对这些内容进行了描述。用于标记 livepatch 符号和重定位段的 ELF 常量是根据 glibc 的定义从操作系统特定(OS-specific)的范围中选取的。

为什么 livepatch 需要编写自己的重定位?

一个典型的 livepatch 模块包含函数的打补丁版本,这些函数可能会引用未导出的全局符号和未包含的局部符号。引用这些类型符号的重定位不能按原样保留,因为内核模块加载器无法解析它们,从而会拒绝该 livepatch 模块。此外,我们无法应用在补丁模块加载时尚未加载的模块的重定位(例如,对未加载的驱动程序的补丁)。过去,livepatch 通过在生成的补丁模块 ELF 输出中嵌入特殊的“dynrela”(动态 rela)段来解决这个问题。使用这些 dynrela 段,livepatch 可以在考虑其作用域以及符号属于哪个模块的同时解析符号,然后手动应用动态重定位。然而,这种方法要求 livepatch 提供架构特定的代码来编写这些重定位。在新格式中,livepatch 使用自己的 SHT_RELA 重定位段来代替 dynrela 段,并且 rela 引用的符号是特殊的 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 重定位段与重定位所应用的目标段(通常是函数的文本段)之间存在一一对应关系。livepatch 模块也可能没有 livepatch 重定位段,例如示例 livepatch 模块(参见 samples/livepatch)。

由于为 livepatch 模块保留了 ELF 信息(参见第 5 节),只需将适当的段索引传递给 apply_relocate_add() 即可应用 livepatch 重定位段,然后它使用该索引来访问重定位段并应用重定位。

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” - 表示 OS 特定)。

`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 重定位段,在该段可以调用 apply_relocate_add() 之前,必须解析该段引用的所有 livepatch 符号。

Livepatch 符号必须使用 SHN_LIVEPATCH 进行标记,以便模块加载器能够识别并忽略它们。Livepatch 模块将这些符号保留在其符号表中,并且可以通过 module->symtab 访问该符号表。

4.1 Livepatch 模块的符号表

通常,模块符号表的精简副本(仅包含“核心”符号)通过 module->symtab 提供(参见 kernel/module/kallsyms.c 中的 layout_symtab())。对于 livepatch 模块,在模块加载时复制到内存中的符号表必须与编译补丁模块时生成的符号表完全相同。这是因为每个 livepatch 重定位段中的重定位都通过其符号索引引用各自的符号,并且必须保留原始的符号索引(从而保留 symtab 的排序),以便 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 使其可访问,这是一个 klp_modinfo 结构体。当 livepatch 模块加载时,此 struct is 由模块加载器填充。