Linux v5.0 安全特性深度解析:从只读线性映射到每任务栈金丝雀

本文详细解析了Linux内核v5.0版本中的多项安全增强特性,包括ARM64的只读线性映射、每任务栈金丝雀保护、Top Byte Ignore支持、隐式fall-through移除、refcount_t转换、用户空间指针认证和平台密钥环等关键技术改进。

Linux v5.0 安全特性

发布日期:2019年3月12日
分类:博客、Chrome OS、Debian、内核、安全、Ubuntu、Ubuntu-Server
作者:kees @ 下午4:04

前文回顾:v4.20

Linux内核v5.0于上周发布!通过审查变更,以下是我发现的一些有趣的安全相关特性:

只读线性映射(ARM64)

虽然x86架构早已支持只读线性映射(在CONFIG_X86_PTDUMP=y下通过/sys/kernel/debug/page_tables/kernel显示为“Low Kernel Mapping”),但Ard Biesheuvel现已将其引入ARM64。这意味着线性映射中包含可执行代码(例如模块、JIT等)的范围不再能被攻击者直接写入。在ARM64上,这可通过CONFIG_ARM64_PTDUMP=y下的/sys/kernel/debug/kernel_page_tables中的“Linear mapping”可见,您现在可以查看页面级粒度:

1
2
3
4
5
6
---[ Linear mapping ]---
...
0xffffb07cfc402000-0xffffb07cfc403000    4K PTE   ro NX SHD AF NG    UXN MEM/NORMAL
0xffffb07cfc403000-0xffffb07cfc4d0000  820K PTE   RW NX SHD AF NG    UXN MEM/NORMAL
0xffffb07cfc4d0000-0xffffb07cfc4d1000    4K PTE   ro NX SHD AF NG    UXN MEM/NORMAL
0xffffb07cfc4d1000-0xffffb07cfc79d000 2864K PTE   RW NX SHD AF NG    UXN MEM/NORMAL

每任务栈金丝雀(ARM)

ARM长期支持栈缓冲区溢出保护(当前通过编译器的-fstack-protector-strong选项)。然而,在ARM上,编译器使用全局变量__stack_chk_guard来比较金丝雀值。这意味着内核中的每个地方都需要使用相同的金丝雀值。如果攻击者能在一个任务中暴露金丝雀值,它可以在另一个任务的缓冲区溢出期间被欺骗。在x86上,金丝雀位于线程本地存储(TLS,在32位中定义为%gs:20,在64位中定义为%gs:40),这意味着由于%gs段指向每任务结构,可以为每个任务使用不同的金丝雀。为了解决ARM的这个问题,Ard Biesheuvel构建了一个GCC插件,用对struct thread_info中新金丝雀的每任务相对引用来替换全局金丝雀检查代码。如他在博客文章中所描述,该插件导致替换:

1
2
3
4
5
6
7
8
9
8010fad8:       e30c4488        movw    r4, #50312      ; 0xc488
8010fadc:       e34840d0        movt    r4, #32976      ; 0x80d0
...
8010fb1c:       e51b2030        ldr     r2, [fp, #-48]  ; 0xffffffd0
8010fb20:       e5943000        ldr     r3, [r4]
8010fb24:       e1520003        cmp     r2, r3
8010fb28:       1a000020        bne     8010fbb0
...
8010fbb0:       eb006738        bl      80129898 <__stack_chk_fail>

为:

1
2
3
4
5
6
7
8
9
8010fc18:       e1a0300d        mov     r3, sp
8010fc1c:       e3c34d7f        bic     r4, r3, #8128   ; 0x1fc0
...
8010fc60:       e51b2030        ldr     r2, [fp, #-48]  ; 0xffffffd0
8010fc64:       e5943018        ldr     r3, [r4, #24]
8010fc68:       e1520003        cmp     r2, r3
8010fc6c:       1a000020        bne     8010fcf4
...
8010fcf4:       eb006757        bl      80129a58 <__stack_chk_fail>

r2保存栈上的金丝雀,r3是要检查的已知良好金丝雀。在前者中,r3通过r4在固定地址(0x80d0c488,readelf -s vmlinux确认是全局__stack_chk_guard)加载。在后者中,它来自struct thread_info中的偏移0x24(pahole -C thread_info vmlinux确认是“stack_canary”字段)。

每任务栈金丝雀(ARM64)

每任务金丝雀的缺失在ARM64上也存在。Ard Biesheuvel通过与GCC开发者Ramana Radhakrishnan协调,添加了对基于寄存器的偏移选项的支持(具体为“-mstack-protector-guard=sysreg -mstack-protector-guard-reg=sp_el0 -mstack-protector-guard-offset=…”),以不同方式解决了这个问题。通过此功能,金丝雀可以相对于sp_el0找到,因为该寄存器持有指向包含金丝雀的struct task_struct的指针。我希望很快也能有一个可行的Clang解决方案(适用于此和32位ARM)。(值得注意的是,不幸的是,此支持尚未在发布的GCC版本中。预计在9.0版本中,可能在今年5月。)

Top Byte Ignore(ARM64)

Andrey Konovalov通过他的Top Byte Ignore(TBI)系列奠定了基础,这也将有助于支持ARMv8.3的指针认证(PAC)和ARMv8.5的内存标记(MTE)。虽然TBI在技术上与PAC冲突,但两者都依赖于使用内存地址中的“非VA空间”(虚拟地址)位,并使内核准备好处理忽略非VA位。PAC存储签名,用于检查栈上的返回地址或堆上存储的函数指针,两者都用于阻止控制流信息的覆盖。MTE存储“标签”(或根据您的方言,“颜色”或“版本”)以标记单独的内存分配区域,以阻止释放后使用和线性溢出。为了使这些工作,CPU必须置于某种形式的TBI寻址模式(尽管对于MTE,它将是“检查标签”模式),否则地址将解析到内存中完全错误的位置。即使没有PAC和MTE,此字节也可用于存储可由软件检查的位(这是Andrey系列其余部分所做的:添加此逻辑以加速KASan)。

进行中:隐式fall-through移除

内核中的一个活跃工作领域是移除所有switch语句中的隐式fall-through。虽然C语言有一个指示switch case结束的语句(“break”),但它没有指示执行应fall-through到下一个case语句的语句(仅缺少“break”用于指示应fall-through——但这并不总是情况),此类“隐式fall-through”可能导致错误。Gustavo Silva自至少v4.14以来一直是修复这些问题的推动力,仅此主题就有超过300个补丁(并因此发现和修复了超过20个缺失的break语句)。目标是能够向构建添加-Wimplicit-fallthrough,以便内核将来完全避免此类错误。从大约2300个警告,内核现已减少到约200个。还值得注意的是,在Stephen Rothwell的帮助下,通过他向引入新实例的任何树维护者发送警告电子邮件(例如,这是2月20日引入并在2月21日修复的错误),此错误已保持在linux-next之外。

进行中:refcount_t转换

还有持续的工作将引用计数器从atomic_t转换为refcount_t,以便它们可以获得溢出保护。自v4.15以来,Elena Reshetova、Trond Myklebust、Kirill Tkhai、Eric Biggers和Björn Töpel进行了18次转换。虽然有更复杂的情况,但最低目标是将来自scripts/coccinelle/api/atomic_as_refcounter.cocci的Coccinelle警告减少到零。截至v5.0,有131个警告,剩余大部分区域在fs/(49)、drivers/(41)和kernel/(21)。

用户空间PAC(ARM64)

Mark Rutland和Kristina Martsenko启用了内核对ARMv8.3 PAC在用户空间的支持。如先前关于PAC所述,这将使用户空间能够通过“签名”函数指针在存储到内存之前阻止各种函数指针覆盖。内核管理密钥(即选择随机密钥并设置它们),但检测和使用新CPU指令取决于用户空间。支持它的CPU的“paca”和“pacg”标志将在/proc/cpuinfo中可见。

平台密钥环

Nayna Jain引入了可信平台密钥环,用户空间无法更新。这可用于验证平台或启动时的事物,如固件、initramfs或kexec内核签名等。

SECCOMP_RET_USER_NOTIF

Tycho Andersen向seccomp添加了新的SECCOMP_RET_USER_NOTIF返回类型,允许进程监视器在seccomp过滤器被命中时通过文件描述符接收通知。这是比ptrace更轻量级的方法,用于代表进程执行任务。它特别适用于为非特权容器执行各种仅管理员任务(如挂载文件系统)。进程监视器可以执行请求的任务(在执行一些ToCToU舞蹈以读取进程内存中的系统调用参数后),或拒绝系统调用。

编辑: 添加了用户空间PAC和平台密钥环,由Alexander Popov建议
编辑: 试图澄清TBI vs PAC vs MTE
编辑: 添加了关于SECCOMP_RET_USER_NOTIF的详细信息

以上就是全部内容;如果我遗漏了任何内容,请告诉我。v5.1合并窗口已打开,所以我们继续前进! :)

© 2019 – 2020, Kees Cook。本作品根据知识共享署名-相同方式共享4.0国际许可协议授权。

评论关闭
尚无评论

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计