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重定位段,而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模块也可能没有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
用于修补vmlinux和模块9p、btrfs、ext4的补丁模块的readelf --sections
输出:
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 Symbols
活动补丁符号是由活动补丁重定位部分引用的符号。这些符号是从已修补对象的新版本的函数中访问的,其地址无法由模块加载程序解析(因为它们是局部的或未导出的全局符号)。由于模块加载程序仅解析已导出的符号,并非每个被新修补函数引用的符号都被导出,因此引入了活动补丁符号。它们还用于在无法立即知道符号地址的情况下使用,例如,当活动补丁修补尚未加载的模块时。在这种情况下,相关的活动补丁符号在目标模块加载时简单地被解析。无论如何,对于任何活动补丁重定位部分,该部分引用的所有活动补丁符号必须在活动补丁调用apply_relocate_add()之前得到解析。
活动补丁符号必须标记为SHN_LIVEPATCH,以便模块加载程序可以识别并忽略它们。活动补丁模块将这些符号保存在它们的符号表中,并且通过module->symtab使符号表可访问。
4.1 活动补丁模块的符号表
通常,模块的符号表的精简副本(仅包含“核心”符号)通过module->symtab(参见kernel/module/kallsyms.c中的layout_symtab())提供。对于活动补丁模块,加载模块时复制到内存中的符号表必须与编译补丁模块时生成的符号表完全相同。这是因为每个活动补丁重定位部分中的重定位引用它们相应的符号与它们的符号索引,原始符号索引(因此symtab排序)必须保留,以便apply_relocate_add()能够找到正确的符号。
例如,从活动补丁模块中取出的特定rela部分:
重定位部分'.klp.rela.btrfs.text.btrfs_feature_attr_show'在偏移0x2ba0处包含4个条目: 偏移量 信息 类型 符号值 符号名称 + 增量 000000000000001f 0000005e00000002 R_X86_64_PC32 0000000000000000 .klp.sym.vmlinux.printk,0 - 4
这个rela部分引用符号'.klp.sym.vmlinux.printk,0',并且符号索引被编码在'信息'中。这里它的符号索引是0x5e,即十进制的94,它指的是符号索引94。
而在这个补丁模块对应的符号表中,符号索引94指的就是那个符号:
94: 0000000000000000 0 NOTYPE GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.printk,0
4.2 活动补丁符号格式
活动补丁符号必须将它们的段索引标记为SHN_LIVEPATCH,以便模块加载程序可以识别它们并且不尝试解析它们。请参阅include/uapi/linux/elf.h以获取实际的定义。
活动补丁符号名称必须符合以下格式:
.klp.sym.objname.symbol_name,sympos ^ ^^ ^ ^ ^ ^ |_______||_____| |_________| | [A] [B] [C] [D]
- [A] 符号名称以字符串".klp.sym."作为前缀
- [B] 符号所属对象的名称(例如"vmlinux"或模块名称)紧随前缀之后
- [C] 符号的实际名称
- [D] 符号在对象中的位置(根据kallsyms)。这用于区分同一对象中的重复符号。唯一符号的符号位置为0。
示例:
活动补丁符号名称:
- .klp.sym.vmlinux.snprintf,0 - .klp.sym.vmlinux.printk,0 - .klp.sym.btrfs.btrfs_ktype,0
readelf --symbols
命令对于一个补丁模块的输出:
符号表'.symtab'包含127个条目: 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段访问
活动补丁模块的符号表可以通过module->symtab访问。
由于apply_relocate_add()需要访问模块的段头、符号表和重定位部分索引,因此ELF信息对于活动补丁模块是被保留的,并且通过模块加载程序通过module->klp_info(一个klp_modinfo结构)使其可访问。当一个活动补丁模块加载时,这个结构将被模块加载程序填充。