124b33044Szhangwei.. SPDX-License-Identifier: GPL-2.0 224b33044Szhangwei.. include:: ../disclaimer-zh_CN.rst 324b33044Szhangwei:Original: Documentation/security/self-protection.rst 424b33044Szhangwei 524b33044Szhangwei:翻译: 624b33044Szhangwei 724b33044Szhangwei 张巍 zhangwei <zhangwei@cqsoftware.com.cn> 824b33044Szhangwei 924b33044Szhangwei============ 1024b33044Szhangwei内核自我保护 1124b33044Szhangwei============ 1224b33044Szhangwei 1324b33044Szhangwei内核自我保护是指在Linux内核中设计与实现的各种系统与结构 1424b33044Szhangwei以防止内核本身的安全漏洞问题。它涵盖了广泛问题,包括去除 1524b33044Szhangwei整个类的漏洞,阻止安全漏洞利用方法,以及主动检测攻击尝 1624b33044Szhangwei试。并非所有的话题都在本文中涉及,但它应该为了解内核自我 1724b33044Szhangwei保护提供一个合理的起点,并解答常见的问题。(当然,欢迎提 1824b33044Szhangwei交补丁!) 1924b33044Szhangwei 2024b33044Szhangwei在最坏的情况下,我们假设一个非特权的本地攻击者对内核内存 2124b33044Szhangwei有任意读写访问权限。虽然在许多情况下,漏洞被利用时并不会 2224b33044Szhangwei提供此级别的访问权限,但如果我们能防御最坏情况,也能应对 2324b33044Szhangwei权限较低的攻击。一个更高的标准,且需要牢记的是保护内核免 2424b33044Szhangwei受具有特权的本地攻击者的攻击,因为root用户可以有更多权限。 2524b33044Szhangwei(尤其是当他们能够加载任意内核模块时) 2624b33044Szhangwei 2724b33044Szhangwei成功的自我保护的目标是:有效、默认开启、不需要开发者主动 2824b33044Szhangwei选择、没有性能影响、不妨碍内核调试、并且没有测试。虽然很 2924b33044Szhangwei难满足所有的这些目标,但明确提到这些目标非常重要,因为这 3024b33044Szhangwei些方面需要被探索、解决或接受。 3124b33044Szhangwei 3224b33044Szhangwei========== 3324b33044Szhangwei攻击面缩减 3424b33044Szhangwei========== 3524b33044Szhangwei 3624b33044Szhangwei防止安全漏洞最基本的防御方式是减少可以被用来重定向执行的 3724b33044Szhangwei内核区域。这包括限制用户公开使用的API、使内核API更难被错 3824b33044Szhangwei误使用、最小化可写内核内存区域等。 3924b33044Szhangwei 4024b33044Szhangwei严格的内核内存权限 4124b33044Szhangwei------------------- 4224b33044Szhangwei 4324b33044Szhangwei当所有内核内存都是可写的,攻击者可以轻松地重定向执行流。 4424b33044Szhangwei为了减少这种攻击目标的可用性,内核需要更严格的权限集来 4524b33044Szhangwei保护其内存。 4624b33044Szhangwei 4724b33044Szhangwei可执行代码和只读数据必须不可写 4824b33044Szhangwei~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4924b33044Szhangwei 5024b33044Szhangwei任何具有可执行内存的区域必须不可写,显然这也包括内核文本 5124b33044Szhangwei本身。我们还必须考虑其他地方:内核模块、JIT内存等,(在 5224b33044Szhangwei某些情况下,为了支持像指令替代、断点、kprobes等功能,这些 5324b33044Szhangwei区域会暂时被设置为可写。如果这些功能必须存在于内核中,它 5424b33044Szhangwei们的实现方式是:在更新期间将内存临时设置可写,然后再恢复 5524b33044Szhangwei为原始权限。) 5624b33044Szhangwei 5724b33044Szhangwei为了支持这一点,CONFIG_STRICT_KERNEL_RWX 和 5824b33044SzhangweiCONFIG_STRICT_MODULE_RWX 的设计旨在确保代码不可写,数据不 5924b33044Szhangwei可执行,以及只读数据既不可写也不可执行。 6024b33044Szhangwei 6124b33044Szhangwei大多数架构默认支持这些选项,且用户无法选择。对于一些像arm 6224b33044Szhangwei这种希望能够选择这些选项的架构,可以在架构Kconfig中选择 6324b33044SzhangweiARCH_OPTIONAL_KERNEL_RWX以启用Kconfig提示。 6424b33044SzhangweiCONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT决定在启用 6524b33044SzhangweiARCH_OPTIONAL_KERNEL_RWX时的默认设置。 6624b33044Szhangwei 6724b33044Szhangwei函数指针和敏感变量必须不可写 6824b33044Szhangwei~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6924b33044Szhangwei 7024b33044Szhangwei内核内存中有大量的函数指针,这些指针被内核查找并用于继续执行 7124b33044Szhangwei(例如,描述符/向量表、文件/网络等操作结构等)。这些变量的数 7224b33044Szhangwei量必须减少到最低限度 7324b33044Szhangwei 7424b33044Szhangwei许多像这样的变量可以通过设置为"const"来实现只读,从而使它们 7524b33044Szhangwei存放在内核的.rodata段而非.data段,从而获得内核严格内存权限的 7624b33044Szhangwei保护。 7724b33044Szhangwei 7824b33044Szhangwei对于在_init是仅初始化一次的变量,可以使用_ro_after_init属性 7924b33044Szhangwei进行标记。 8024b33044Szhangwei 8124b33044Szhangwei剩下的变量通常是那些更新频率较低的(例如GDT)。这些变量需要另 8224b33044Szhangwei一个机制(类似于上述提到的对内核代码所做的临时例外),以便在 8324b33044Szhangwei其余生命周期内保持只读状态。(例如,在进行更新时,只有执行 8424b33044Szhangwei更新的CPU线程会被授予对内存的不可中断写入访问权限。) 8524b33044Szhangwei 8624b33044Szhangwei将内核内存与用户空间内存分隔开 8724b33044Szhangwei~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8824b33044Szhangwei 8924b33044Szhangwei内核绝对不可以执行用户空间内存,同时,内核也不得在没有明确预 9024b33044Szhangwei期的情况下访问用户内存空间。这些规则可以通过一些硬件限制来支 9124b33044Szhangwei持(如x86的SMEP/SMAP,ARM的PXN/PAN)或通过仿真(如ARM的内存 9224b33044Szhangwei域)来强制执行。通过这种方式阻止用户空间内存的访问,攻击者就 9324b33044Szhangwei无法将执行和数据解析转移到易于控制的用户空间内存,从而迫使攻 9424b33044Szhangwei击完全在内核中进行。 9524b33044Szhangwei 9624b33044Szhangwei减少对系统调用的访问 9724b33044Szhangwei-------------------- 9824b33044Szhangwei 9924b33044Szhangwei对于64位系统,一种消除许多系统调用最简单的方法是构建时不启用 10024b33044SzhangweiCONFIG_CONPAT。然而,这种情况通常不可行。 10124b33044Szhangwei 10224b33044Szhangwei“seccomp”系统为用户空间提供了一种可选功能,提供了一种减少可供 10324b33044Szhangwei运行中进程使用内核入口点数量的方法。这限制了可以访问内核代码 10424b33044Szhangwei的范围,可能降低了某个特定漏洞被攻击者利用的可能性。 10524b33044Szhangwei 10624b33044Szhangwei一个改进的方向是创建有效的方法,仅允许受信任的进程访问例如兼 10724b33044Szhangwei容模式、用户命名空间、BPF创建和性能分析等功能。这将把内核入口 10824b33044Szhangwei点范围限制在通常可以被非特权用户空间进程访问的较常见集合中 10924b33044Szhangwei 11024b33044Szhangwei限制对内核模块的访问 11124b33044Szhangwei-------------------- 11224b33044Szhangwei 11324b33044Szhangwei内核绝不应允许非特权用户加载特定的内核模块,因为这可能为攻击者 11424b33044Szhangwei提供一个意外扩展的可用攻击面的方法。(通过已预定义子系统按需加 11524b33044Szhangwei载模块,如MODULE_ALIAS_*,被认为是“预期的”,但即便如此,也应对 11624b33044Szhangwei这些情况给予更多的关注。)例如,通过非特权的套接字API加载文件 11724b33044Szhangwei系统模块是没有意义的:只有root用户或物理本地用户应该触发文件系 11824b33044Szhangwei统模块的加载。(在某些情况下,这甚至可能存在争议。) 11924b33044Szhangwei 12024b33044Szhangwei为了防止特权用户的攻击,系统可能需要完全禁止模块加载(例如,通 12124b33044Szhangwei过单体内核构建或modules_disabled sysctl),或者使用签名模块(例 12224b33044Szhangwei如,CONFIG_MODULE_SIG_FORCE或通过LoadPin保护的dm-crypt),以防 12324b33044Szhangwei止root用户通过模块加载器加载任意内核代码。 12424b33044Szhangwei 12524b33044Szhangwei内存完整性 12624b33044Szhangwei---------- 12724b33044Szhangwei 12824b33044Szhangwei内核中有许多内存结构在攻击过程中被定期泛滥用以获取执行控制,迄今 12924b33044Szhangwei为止,最常见的是堆栈缓冲区溢出,在这种攻击中,堆栈上存储的返回地 13024b33044Szhangwei址被覆盖。除此之外,还有许多其他类型的攻击,防护措施也应运而生。 13124b33044Szhangwei 13224b33044Szhangwei堆栈缓冲区溢出 13324b33044Szhangwei-------------- 13424b33044Szhangwei 13524b33044Szhangwei经典的堆栈缓冲区溢出攻击是指超出栈上分配的变量预期大小,从而将一 13624b33044Szhangwei个受控值写入栈帧的返回地址。最常见的防御措施是堆栈保护 13724b33044Szhangwei(CONFIG_STACKPROTECTOR),它在函数返回前会验证栈上的“stack canary”。 13824b33044Szhangwei其他防御措施还包括影子堆栈等。 13924b33044Szhangwei 14024b33044Szhangwei堆栈深度溢出 14124b33044Szhangwei------------ 14224b33044Szhangwei 14324b33044Szhangwei一个不太容易被理解的攻击方式是利用bug触发内核通过深度函数调用或 14424b33044Szhangwei大的堆栈分配来消耗堆栈内存。通过这种攻击,攻击者可以将数据写入内 14524b33044Szhangwei核预分配堆栈空间之外的敏感结构。为了更好的防护这种攻击,必须进行 14624b33044Szhangwei两项重要的更改:将敏感的线程信息结构转移到其他地方,并在堆栈底部 14724b33044Szhangwei添加一个故障内存洞,以捕获这些溢出 14824b33044Szhangwei 14924b33044Szhangwei栈内存完整性 15024b33044Szhangwei------------ 15124b33044Szhangwei 15224b33044Szhangwei用于跟踪堆空闲列表的结构可以在分配和释放时进行完整性检查,以确保它 15324b33044Szhangwei们不会被用来操作其它内存区域。 15424b33044Szhangwei 15524b33044Szhangwei计算器完整性 15624b33044Szhangwei------------ 15724b33044Szhangwei 15824b33044Szhangwei内核中的许多地方使用原子计数器来跟踪对象引用或执行类似的生命周期管 15924b33044Szhangwei理。当这些计数器可能发生溢出时(无论是上溢还是下溢),这通常会暴露 16024b33044Szhangwei出使用后释放(use-after-free)漏洞。通过捕捉原子计数器溢出,这类漏 16124b33044Szhangwei洞就可以消失。 16224b33044Szhangwei 16324b33044Szhangwei大小计算溢出检测 16424b33044Szhangwei---------------- 16524b33044Szhangwei 16624b33044Szhangwei与计算器溢出类似,整数溢出(通常是大小计算)需要在运行时进行检测, 16724b33044Szhangwei以防止这类在传统上会导致能够写入内核缓冲区末尾之外的漏洞。 16824b33044Szhangwei 16924b33044Szhangwei概率性防御 17024b33044Szhangwei---------- 17124b33044Szhangwei 17224b33044Szhangwei尽管许多防御措施可以被认定是确定的(例如,只读内存不能写入),但 17324b33044Szhangwei有些确保措施仅提供统计防御,即攻击者必须收集足够的关于运行系统的 17424b33044Szhangwei信息才能突破防御。尽管这些防御并不完美,但它们确实提供了有意义的 17524b33044Szhangwei保护。 17624b33044Szhangwei 17724b33044Szhangwei栈保护、迷惑技术和其他秘密 17824b33044Szhangwei-------------------------- 17924b33044Szhangwei 18024b33044Szhangwei值得注意的是,像之前讨论的栈保护这样的技术,从技术上来说是统计性防 18124b33044Szhangwei御,因为它们依赖于一个秘密值,而这样的值可能会通过信息泄露漏洞而被 18224b33044Szhangwei发现。 18324b33044Szhangwei 18424b33044Szhangwei对于想JIT(及时翻译器)这样的情况,其中可执行内容可能部分由用户空间 18524b33044Szhangwei控制,也需要类似的秘密之来迷惑。 18624b33044Szhangwei 18724b33044Szhangwei至关重要的是,所使用的秘密值必须是独立的(例如,每个栈使用不同的栈 18824b33044Szhangwei保护值),并且具有高熵(例如,随机数生成器(RNG)是否正常工作?), 18924b33044Szhangwei以最大限度地提高其成功率。 19024b33044Szhangwei 19124b33044Szhangwei内核地址空间布局随机化(KASLR) 19224b33044Szhangwei------------------------------- 19324b33044Szhangwei 19424b33044Szhangwei由于内核内存的位置几乎总是攻击成功的关键因素,因此使内核内存位置变 19524b33044Szhangwei得非确定性会增加攻击的难度。(请注意,这反过来提高了信息泄露的价 19624b33044Szhangwei值,因为泄露的信息可以用来发现目标内存位置。) 19724b33044Szhangwei 19824b33044Szhangwei文本和模块基址 19924b33044Szhangwei-------------- 20024b33044Szhangwei 20124b33044Szhangwei通过在启动时重新设定内核的物理基地址和虚拟基地址 20224b33044Szhangwei(CONFIG_RANDOMIZE_BASE),那些需要利用内核代码的攻击将会受阻。此外 20324b33044Szhangwei通过偏移模块加载基地址,意味着即使系统每次启动时按相同顺序加载同一 20424b33044Szhangwei组模块,这些模块也不会与内核文本的其余部分公用一个基地址。 20524b33044Szhangwei 20624b33044Szhangwei堆栈基地址 20724b33044Szhangwei---------- 20824b33044Szhangwei 20924b33044Szhangwei如果进程之间内核堆栈的基地址不相同,甚至在不同系统调用之间也不相同, 21024b33044Szhangwei那么栈上或超出栈的目标位置就会变得更加难以确定。 21124b33044Szhangwei 21224b33044Szhangwei动态内存基址 21324b33044Szhangwei------------ 21424b33044Szhangwei 21524b33044Szhangwei很多内核的动态内存(例如kmalloc,vmalloc等)由于早期启动初始化的顺 21624b33044Szhangwei序,最终布局是相对确定的。如果这些区域的基地址在启动之间不相同,攻 21724b33044Szhangwei击者就无法轻易定位它们,必须依赖于针对该区域的信息泄露才能成功。 21824b33044Szhangwei 21924b33044Szhangwei结构布局 22024b33044Szhangwei-------- 22124b33044Szhangwei 22224b33044Szhangwei通过在每次构建时对敏感结构的布局进行随机化处理,攻击这必须将攻击调 22324b33044Szhangwei节到已知的内核版本,或者泄露足够的内核内存来确定结构布局,然后才能 22424b33044Szhangwei对其进行操作。 22524b33044Szhangwei 22624b33044Szhangwei防止信息泄露 22724b33044Szhangwei------------ 22824b33044Szhangwei 22924b33044Szhangwei由于敏感结构的位置是攻击的主要目标,因此防止内核内存地址和内核内存 23024b33044Szhangwei内容泄露非常重要(因为它们可能包含内核地址或者其他敏感数据,例如 23124b33044Szhangwei栈保护值)。 23224b33044Szhangwei 23324b33044Szhangwei内核地址 23424b33044Szhangwei-------- 23524b33044Szhangwei 23624b33044Szhangwei将内核地址打印到用户空间会泄露有关内核内存布局的敏感信息。在使用任 23724b33044Szhangwei何打印符号打印原始地址时,目前%px,%p[ad](和在某些情况下的%p[sSb]) 23824b33044Szhangwei时。使用这些格式符写入的文件需要限制为只有特权进程可读。 23924b33044Szhangwei 24024b33044Szhangwei在4.14及以前的内核版本中,使用%p格式符打印的是原始地址。从4.15-rcl 24124b33044Szhangwei版本开始,使用%p格式符打印的地址会在打印前进行哈希处理。 24224b33044Szhangwei 24324b33044Szhangwei[*]如果启用KALLSYMS并且符号查找失败,则打印原始地址;如果没有启用 24424b33044SzhangweiKALLSYSM,则会直接打印原始地址。 24524b33044Szhangwei 24624b33044Szhangwei唯一标识符 24724b33044Szhangwei---------- 24824b33044Szhangwei 24924b33044Szhangwei内核内存地址绝不可能用作向用户空间公开的标识符。相反,应该使用原子 25024b33044Szhangwei计数器,IDR(ID映射表)或类似的唯一标识符。 25124b33044Szhangwei 25224b33044Szhangwei内存初始化 25324b33044Szhangwei---------- 25424b33044Szhangwei 25524b33044Szhangwei复制到用户空间的内存必须始终被完全初始化,如果没有显式地使用memset() 25624b33044Szhangwei函数进行初始化,那就需要修改编译器,确保清除结构中的空洞。 25724b33044Szhangwei 25824b33044Szhangwei内存清除 25924b33044Szhangwei-------- 26024b33044Szhangwei 26124b33044Szhangwei在释放内存时,最好对内存内容进行清除处理,以防止攻击者重用内存中以前 262*57fbad15SKees Cook的内容。例如,在系统调用返回时清除堆栈(CONFIG_KSTACK_ERASE), 26324b33044Szhangwei在释放堆内容是清除其内容。这有助于防止许多未初始化变量攻击、堆栈内容 26424b33044Szhangwei泄露、堆内容泄露以及使用后释放攻击(user-after-free)。 26524b33044Szhangwei 26624b33044Szhangwei目标追踪 26724b33044Szhangwei-------- 26824b33044Szhangwei 26924b33044Szhangwei为了帮助消除导致内核地址被写入用户空间的各种错误,需要跟踪写入的目标。 27024b33044Szhangwei如果缓冲区的目标是用户空间(例如,基于seq_file的/proc文件),则应该自 27124b33044Szhangwei动审查敏感值。 272