关注了就能看到更多这么棒的文章哦~
Shadow-stack control in clone3()
By Jonathan Corbet
August 26, 2025
Gemini flash translation
https://lwn.net/Articles/1034442/
影子栈 (shadow stack) 是一种控制流完整性 (control-flow-integrity) 功能,旨在防御通过操纵线程的调用栈 (call stack) 来进行的攻击。内核在 6.6 版本中首次为 x86 架构 增加了硬件实现的影子栈支持;64 位 Arm 架构的支持则在 6.13 版本中跟进。然而,这项功能并未给用户空间 (user space) 提供太多控制权来管理新线程的影子栈分配;但 Mark Brown 的一份 补丁系列,在经历了多次尝试之后,或许终于能够改变这一现状。
顾名思义,影子栈是线程常规调用栈的一种副本,但其内容仅限于返回地址 (return address)。在支持影子栈的系统上,每次函数调用都会将返回地址压入普通栈和影子栈。当函数返回时,返回地址会从两个栈中弹出并进行比较;如果它们不匹配,则表明发生了某种形式的损坏,相关线程将被终止。
影子栈在系统的页表 (page tables) 中被特别标记,并且用户空间不可写入。影子栈的顶部还必须包含一个特殊的、由硬件生成的令牌 (token);这个令牌将其标识为一个真实的影子栈,并防止多个线程使用同一个影子栈。这些设置要求意味着线程的影子栈创建必须由内核完成。
当内核需要为线程创建影子栈时,一个迫切的问题是:这个栈应该有多大?进程初始影子栈的创建可以受到进程本身的影响,但对于进程此后可能创建的任何线程来说,情况并非如此。每当一个新的线程产生时,内核都会根据其常规栈的大小,为其分配一个相同大小的影子栈,作为对正确大小的最佳猜测。
当然,这个猜测可能与实际需求相去甚远。虽然调用栈上会压入相当多的信息——例如局部变量 (local variables) 和保存的寄存器 (saved registers)——但影子栈只保存返回地址,因此相同大小的影子栈很可能对线程来说过于庞大。如果进程创建许多线程,超大影子栈浪费的内存量可能会变得相当可观。此外,也存在一些(不那么常见)的情况,正如 2023 年的文章所描述的,等大影子栈可能会显得太小。
无论如何,让进程在为其创建的线程确定影子栈大小时拥有发言权是很有价值的。一段时间以来,Mark Brown 一直致力于在通过 `clone3()` 创建线程时控制影子栈分配的能力;这项工作在 2023 年 这里 已经报道过。当时,补丁集 (patch set) 处于第四个版本;现在它已是第 19 个迭代 (iteration),进行了多项更改,Brown 表示希望该系列补丁很快就能准备好合并。
该系列补丁的早期版本允许用户空间指定所需影子栈的地址和大小,但这遭到了其他开发者的反对。随后的尝试仅允许指定影子栈的大小,但被证明限制性过大。最终决定由父进程 (parent process) 根据自己的判断来创建影子栈。因此,从版本 5 开始,调用 `clone3()` 的进程必须首先请求内核为新线程创建一个影子栈,通过调用 `map_shadow_stack()`:
sstack=map_shadow_stack(unsigned long addr, unsigned long size, unsigned int flags);
`addr` 和 `size` 参数描述了新创建的栈应该放置在哪里以及它应该有多大;将 `addr` 设置为零则将放置决策留给内核。`flags` 参数必须是 `SHADOW_STACK_SET_TOKEN`,以使内核将特殊的令牌放置在栈的顶部;否则,生成的影子栈映射 (shadow-stack mapping) 无法用于新线程。此系统调用 (system call) 将返回栈实际映射到的虚拟地址 (virtual address)。
当需要调用 `clone3()` 时,`struct clone_args` 中新增的 `shadow_stack_token` 字段必须设置为指向影子栈令牌。这个要求可能有点反直觉;该指针指向的是令牌,而不是栈的基址 (base of the stack) 本身。因此,使用此功能的代码将类似于这样:
这段代码分配了一个单个的 4096 字节影子栈,让内核选择栈的放置位置;然后它将 `args->shadow_stack_token` 指向内核将存储在栈顶的令牌。如果一切顺利,通过随后的 `clone3()` 调用创建的线程将使用新创建的影子栈。错误处理 (Error handling) 显然已被省略。
Brown 在附函 (cover letter) 中表示:“我想现在大家对 ABI 都没意见了,x86 的实现也已经测试过了,所以希望能很快合并。” 到目前为止,还没有人站出来表示反对这个想法。在没有意外的情况下,这项长期开发中的 `clone3()` API 补充功能,可能最终会进入主线 (mainline)。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~