You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GCC新增-fsanitize=shadow-call-stack选项具体行为咨询

GCC 12.1 -fsanitize=shadow-call-stack 机制详解

这个Sanitizer是专门针对返回地址篡改类攻击(最典型的就是ROP栈溢出攻击)设计的防护机制,和传统的栈金丝雀保护逻辑完全不同,具体运行逻辑和生效规则如下:

核心运行逻辑

  • 程序启动时会单独分配一块和普通用户栈内存完全隔离的专属内存区域,也就是影子调用栈,这块内存只用来存储函数调用的返回地址,不存储局部变量、栈帧元数据等其他内容,常规的栈溢出漏洞根本无法覆盖到这块内存。
  • 编译阶段会自动对所有函数的出入口插桩:
    • 函数序言部分:bl跳转指令执行完进入函数后,会先把链接寄存器lr(AArch64下对应x30寄存器)里存的返回地址,写入r18寄存器指向的影子栈栈顶位置,然后移动影子栈指针。这也是强制要求加-ffixed-r18参数的核心原因:r18会被固定作为影子栈指针使用,编译器绝对不能把它分配给普通代码做通用寄存器,否则会直接打乱影子栈的内存结构。
    • 函数尾声部分:函数执行完成准备返回前,会直接从影子栈里把之前存的返回地址弹回lr寄存器,再执行ret指令跳转,不会信任普通栈上可能被篡改的返回地址数据。
  • 运行时如果出现影子栈数据错位、返回地址异常的情况,程序会直接触发段错误终止,不会跳转到攻击者构造的恶意地址执行代码。

实际生效的约束条件

  • 架构限制:GCC 12.1版本的该功能仅支持AArch64目标架构,其他架构开启该选项要么直接编译报错,要么不会生成任何防护插桩代码。
  • 全量编译要求:最终链接进二进制的所有代码,包括业务代码、静态依赖库、程序启动引导代码、动态链接器(动态链接场景下),都必须携带-ffixed-r18参数编译。只要存在未适配的旧代码把r18当通用寄存器使用,就会直接篡改影子栈指针,轻则程序随机崩溃,重则防护逻辑完全失效。
  • 运行环境适配:该版本的实现初期确实优先面向Linux内核场景做适配——内核本身就已经预留r18作为特殊用途寄存器,不需要额外改太多逻辑。如果要在用户态程序上使用,必须确认所用的libc(比如glibc、musl)、运行时环境已经完成了影子栈初始化、r18预留的适配工作,直接给普通用户态程序开这个选项编译大概率会直接崩溃,因为默认的libc根本不会初始化影子栈,r18的初始值是随机值,第一次写影子栈就会触发非法内存访问。

编译后代码的实际差异

开启该选项后生成的二进制和普通版本比,有几个非常明确的特征:

  • 不会生成-fstack-protector系列栈保护插入的金丝雀值计算、比对逻辑
  • 每个函数的开头会多出类似str x30, [x18], #8的汇编指令,负责把返回地址存到影子栈
  • 每个函数返回前会多出ldr x30, [x18, #-8]!的汇编指令,负责从影子栈恢复正确的返回地址
  • 整体性能开销极低,仅在函数出入口多了两次指针操作和内存访存,开销比传统栈金丝雀还要小。

内容的提问来源于stack exchange,提问作者DevShark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 19:56:49