栈金丝雀

关注
义项:栈溢出防护机制

栈金丝雀(stack canary)是程序运行时放置在栈上的一个校验值,用来发现栈缓冲区溢出。编译器把该值插在函数的局部缓冲区与保存的返回地址之间,函数返回前检查它是否被改写;一旦不一致,程序转入失败处理并终止,使攻击者无法借被覆盖的返回地址劫持控制流[1][2]。它属于编译器层面的内存安全缓解手段,通常与地址空间布局随机化、不可执行栈等机制配合使用[3]。名称借自矿工用金丝雀预警矿井中有毒气体的旧俗[4]。

百科 图文
目录
  1. 定义
  2. 原理
  3. 发展历程
  4. 应用
  5. 局限
  6. 参见

定义

栈金丝雀是保存在函数栈帧中的一个哨兵数据,用来判断栈上的缓冲区是否被越界写入。它一般由编译器自动加入,不需要改动源代码,位置通常落在容易被溢出的局部数据与函数控制信息之间[5][3]。

函数返回时,程序会把栈上保存的金丝雀与初始化时的参考值作比较。两者不一致说明栈内容已被异常修改,程序随即中止,而不会沿用可能被篡改的返回地址继续执行[6][2]。

这一名称借自矿业旧俗:矿工把对瓦斯等气体敏感的金丝雀带下矿井,凭鸟的异常反应提前察觉危险[4]。在内存中承担类似预警职责的校验值,因而被叫作金丝雀[7]。

原理

启用该保护的函数在进入时先把金丝雀写入栈帧,再执行正常逻辑;退出前读回该位置并与参考值比对[3][8]。参考值通常在进程启动时生成并保存在受保护的位置,函数通过线程局部存储等方式读取[9]。

校验过程可以写成一个判断:函数返回前比较栈上保存值 $c_{stack}$ 与参考值 $c_{ref}$,当 $c_{stack} \ne c_{ref}$ 时即判定栈已被破坏[1]。


graph TD

A[低地址:局部变量与缓冲区] --> B[金丝雀]

B --> C[保存的帧指针]

C --> D[返回地址]

D --> E[高地址]

对于从低地址向高地址连续写入的溢出,攻击者要想改写返回地址,就不得不先经过金丝雀所在的位置[2]。这样溢出在得手之前就会留下痕迹,检查失败后程序转入记录或终止流程[4]。

常见取值策略有 3 类。终止符金丝雀用字符串结束符、换行、回车、文件结束符等字节组合成固定值,让基于字符串拷贝的溢出提前停下;随机金丝雀在进程初始化时取一个随机数;随机异或金丝雀把随机值与返回地址等随运行变化的值相异或,以增加预测难度[1][9]。

不同平台的具体形式不同。以常见的Linux实现为例,x86-64 下金丝雀为 8 字节,i386 下为 4 字节,最低字节置 0,使其同时具备终止符的作用[9][1]。

发展历程

缓冲区溢出攻击因 1988 年的莫里斯蠕虫事件而受到广泛关注,该蠕虫通过改写栈上的返回地址来控制系统[5][6]。

1998 年,美国俄勒冈研究生院的研究团队在 USENIX 安全研讨会上发表 StackGuard,把金丝雀思路做成 GCC 的编译器扩展:在函数序言中插入校验值,在收尾代码中验证,无需改动程序源码即可提供保护[5][8][10]。

此后出现若干思路不同的实现。StackShield 把返回地址复制到独立的全局返回栈,返回时再恢复[9]。IBM 提出的 ProPolice 除插入金丝雀外还重排局部变量,把字符数组放到更靠近金丝雀的位置,以减少相邻指针被覆盖的机会[10][9]。微软 在其 Visual C++ .NET 编译器中以 /GS 选项提供类似机制,并把该值称为 cookie[8]。

GCC 4.1 收录了 ProPolice 思路的重实现,并提供 -fstack-protector 系列编译选项[11]。此后该保护在主流 Linux 发行版分发的二进制中被普遍启用,有调查显示桌面端约 85% 的二进制打开了这一选项[1]。

应用

栈金丝雀属于软件层面的防护,由编译器生成相应代码,多数主流编译器均已支持[6]。GCC 用 -fstack-protector、-fstack-protector-strong、-fstack-protector-all、-fstack-protector-explicit 等选项控制受保护函数的范围,微软工具链则使用 /GS[9][12][3]。

在移动平台上,Android NDK 编译的本地库默认启用栈金丝雀,开发者自行编译的 C/C++ 库则需要显式加上相关选项[12]。一些嵌入式工具链同样提供堆栈保护选项[13]。

除栈以外,金丝雀的思想也被用于堆等其他内存区域[14]。实际部署中,它一般与影子栈、地址空间布局随机化、不可执行栈等措施叠加,构成多层防御[15][3]。

局限

金丝雀要到函数返回时才做检查,属于事后检测而非阻止。它发现异常时,位于金丝雀之前的局部数据可能早已被破坏,只是控制流尚未被劫持[9][4]。它主要保护返回地址、帧指针等控制数据,对只篡改数据、不影响控制流的攻击无能为力[1]。

只有连续写入的溢出才会必然经过金丝雀。能够精确改写目标字节而不触碰中间内存的攻击可以绕开它[16][1]。同一函数内若有多个数组,离金丝雀较远的数组发生溢出,也可能不被察觉[1]。

校验值本身可能泄露。格式化字符串漏洞可以读出栈上的金丝雀,攻击者随后在溢出载荷中写回原值,就能通过检查[2][14]。

爆破同样构成现实威胁。32 位系统的地址空间较小,金丝雀可逐字节猜测[2][14];服务器程序反复派生的子进程沿用同一个金丝雀,攻击者便有机会多次试探[9]。

编译器的函数筛选与优化会缩小保护面,过小的数组或某些结构内的缓冲区可能不被插桩[9][12]。部分项目出于性能考虑不愿全面开启,认为开销与收益不成比例[12]。栈金丝雀无法取代地址空间布局随机化、不可执行栈或内存安全的编程语言,只能作为多层防御中的一环[3]。

参见

  • 缓冲区溢出 —— 栈金丝雀要检测的主要攻击形式。

  • 返回地址 —— 栈金丝雀保护的关键控制数据。

  • StackGuard —— 最早把金丝雀思想实现为编译器扩展的方案。

  • 影子栈 —— 用独立存储保护返回地址、与栈金丝雀互补的机制。

  • 地址空间布局随机化 —— 常与栈金丝雀搭配使用的内存安全缓解措施。

  • 格式化字符串漏洞 —— 可用来读出金丝雀值、从而绕过该保护的漏洞类型。

参考资料

  1. arxiv.org 上的网页 . arxiv.org [引用日期2026-09-29]
  2. incibe.es 上的网页 . incibe.es [引用日期2026-09-29]
  3. Stack Canary . cyberglossary.org [引用日期2026-09-29]
  4. 第 部 セキュアな実行コードの生成・実行環境技術に関する調査 . ipa.go.jp [引用日期2026-09-29]
  5. Abstract - 7th USENIX Security Symposium, 1998 . usenix.org [引用日期2026-09-29]
  6. Stack Canaries . github.io [引用日期2026-09-29]
  7. ch13(PDF) . wcupa.edu [引用日期2026-09-29]
  8. giac.org 上的网页 . giac.org [引用日期2026-09-29]
  9. Stack Canaries . hacktricks.wiki [引用日期2026-09-29]
  10. StackGuard: Simple Stack Smash Protection for GCC . lip6.fr [引用日期2026-09-29]
  11. Gerald Pfeifer - [wwwdocs] ProPolice actually was merged . gnu.org [引用日期2026-09-29]
  12. MASTG-TEST-0223: Stack Canaries Not Enabled . owasp.org [引用日期2026-09-29]
  13. 实现堆栈保护来提高代码的安全性 . iar.com [引用日期2026-09-29]
  14. Lec 10: Canary . kaist.ac.kr [引用日期2026-09-29]
  15. acm.org 上的网页 . acm.org [引用日期2026-09-29]
  16. S0167404826002324 . sciencedirect.com [引用日期2026-09-29]
词条评价
词条统计

浏览次数:0 次

阅读量:0 次 · 阅读完成量:0 次

最近更新:2026-09-29T12:49:09Z

历史版本

完成率 = 阅读完成量 ÷ 阅读量,分母是阅读量不是浏览次数 —— 关了 JS 的、秒退的都在浏览次数里、不在阅读量里。 详细口径在后台的「数据统计」页。

本条目引用的词条
栈帧 编译器 返回地址 线程局部存储 Linux GCC IBM 微软 Android 影子栈 地址空间布局随机化 格式化字符串漏洞 缓冲区溢出 返回地址 StackGuard 影子栈 地址空间布局随机化 格式化字符串漏洞
红色的还不存在。红链不是错误——它标出"这个概念被引用了但还没人写"。