资讯动态

mold 可执行栈(Executable Stack)警告详解:成因、安全原理与修复方案

发布时间:2026/9/15 5:10:13 来源:尧图企业网站定制
mold 可执行栈Executable Stack警告详解成因、安全原理与修复方案【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold导读本文围绕 mold 链接器在遇到“要求可执行栈”的输入文件时发出的this file may cause a segmentation fault because it requires an executable stack警告系统讲解可执行栈的历史成因、.note.GNU-stack标记节的运作机制、mold 与 GNU ld 在策略上的本质差异以及通过-z execstack/-z execstack-if-needed或改写代码消除警告的完整方案。读完本文你将能准确诊断该警告的触发条件并基于 mold 源码理解PT_GNU_STACK段的生成逻辑与安全取舍。警告信息mold 在提示什么当你使用 mold 链接一个包含特殊标记的目标文件时会看到如下输出mold: warning: foo.o: this file may cause a segmentation fault because it requires an executable stack. See https://github.com/rui314/mold/tree/main/docs/execstack.md for more info.这段警告由 src/input-files.cc#L411-L419 中的代码触发。mold 解析每个输入目标文件的节section时如果发现名为.note.GNU-stack的节带有可执行标志且链接命令行中既没有-z execstack也没有-z execstack-if-needed就会打印这条警告。它的本质含义是这个目标文件与 mold 的默认安全策略不兼容如果直接链接程序运行时可能因栈不可执行而崩溃段错误因此 mold 需要你明确做出选择。背景为什么栈必须不可执行在现代计算机上栈区域存放局部变量的内存区域不允许包含可执行代码。如果控制流跳转到栈区域CPU 会拒绝执行其中的任何代码程序通常会因段错误而终止。这是一项重要的安全机制。早期的 CPU 通常只要内存区域可读就会执行其中的代码因此栈天然是可执行的。这给恶意攻击者留下了极易利用的攻击面攻击者利用缓冲区溢出漏洞把可执行代码写入栈然后劫持控制流跳转到栈上的这段代码从而在远程服务器进程中执行任意代码。为了阻止这类攻击自 2000 年代初起栈区域不再默认可执行。在 Linux 上栈的可执行性由可执行文件中的一个比特位控制加载器loader尊重该比特位而这个比特位正是由链接器设置的。验证栈可执行性的方法链接完成后可以用readelf检查输出文件的程序头program header中PT_GNU_STACK段的标志# 栈不可执行正常状态 readelf --segments -W exe | grep GNU_STACK # GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10 # 栈可执行 # GNU_STACK ... RWE 0x10RW表示栈可读写但不可执行安全默认RWE表示栈可执行危险状态。.note.GNU-stackGNU 体系下的“栈标记节”GCC 至今仍保留着一个依赖可执行栈的特性因此它发明了一种向链接器传达“请把栈标记为可执行”的方式如果目标文件中包含带可执行标志的.note.GNU-stack节GNU 链接器就会静默地把输出文件的栈设为可执行。这正是问题所在。GNU ld 的这套行为存在安全隐患如果你不小心链接了一个带该标记节的目标文件整个栈区域会静默地变得可执行从而关闭系统的安全机制——而你甚至可能毫无察觉。在 mold 的源码中这个标记节被显式处理并忽略。参见 src/input-files.cc#L407-L421// .note.GNU-stack section controls executable-ness of the stack // area in GNU linkers. We ignore that section because silently // making the stack area executable is too dangerous. Tell our // users about the difference if that matters. if (name .note.GNU-stack !ctx.arg.relocatable) { if (shdr.sh_flags SHF_EXECINSTR) { if (!ctx.arg.z_execstack !ctx.arg.z_execstack_if_needed) Warn(ctx) *this : this file may cause a segmentation fault because it requires an executable stack. See https://github.com/rui314/mold/tree/main/docs/execstack.md for more info.; needs_executable_stack true; } continue; }这段代码揭示了三个关键设计决策忽略而非遵从mold 不会像 GNU ld 那样因.note.GNU-stack带可执行标志就自动把输出栈设为可执行显式警告当该节带有SHF_EXECINSTR可执行指令标志且用户未显式授权时打印警告提醒用户注意差异记录需求无论是否警告都会把needs_executable_stack置为true供后续-z execstack-if-needed逻辑使用该成员定义于 src/mold.h#L2059。注意-r可重定位模式下该检查会被跳过!ctx.arg.relocatable因为此时输出仍是目标文件不会生成最终可执行映像。触发条件通常来自 GCC 的嵌套函数什么代码会产生带可执行标志的.note.GNU-stack节最常见的来源是GCC 的 Nested Functions嵌套函数特性。GCC 的嵌套函数在函数内部定义函数且捕获外层函数的局部变量在 GNU C 中依赖“可执行栈 可执行蹦床trampoline”机制来实现闭包语义——编译器会把一段小的跳转代码动态生成到栈上。因此包含嵌套函数的编译单元会被标记为需要可执行栈。这也是为什么旧式动态生成代码如部分 JIT、信号处理技巧有时也会触发该警告。可以用下面的汇编输入精确复现cat EOF | gcc -c -xassembler -o a.o - .globl main main: ret .section .note.GNU-stack, x, progbits EOFx即给该节赋予可执行标志。测试 test/arch-x86_64-execstack-if-needed.sh 正是用这种方式构造输入来验证行为。mold 的策略安全优先绝不静默放行mold 对可执行栈的默认策略可以概括为一句话只有用户显式要求栈才是可执行的。该策略最终落实在输出文件程序头的生成处参见 src/output-chunks.cc#L289-L298// Add PT_GNU_STACK, which is a marker segment that doesnt really // contain any segments. It controls executable bit of stack area. { ElfPhdrE phdr {}; phdr.p_type PT_GNU_STACK; phdr.p_flags ctx.arg.z_execstack ? (PF_R | PF_W | PF_X) : (PF_R | PF_W); phdr.p_memsz ctx.arg.z_stack_size; phdr.p_align 1; vec.push_back(phdr); }可以看到PT_GNU_STACK段的标志完全由ctx.arg.z_execstack一个开关决定默认z_execstack false见 src/mold.h#L2759标志为PF_R | PF_W只读可写不可执行显式开启z_execstack true标志为PF_R | PF_W | PF_X可执行。PT_GNU_STACK本身是一个“不真正包含任何数据”的标记段它的唯一作用就是告诉内核加载器进程栈页应该用什么保护属性。因此使用 mold 时即使目标文件带有可执行栈标记mold也不会因此改变输出栈的属性——这正是它与 GNU ld 最大的行为差异。如何修复三种方案对比方案一显式传递-z execstack谨慎使用如果你明确知道自己需要可执行栈可以显式告诉 moldmold -z execstack -o exe a.o b.o # 通过编译器驱动时 gcc -B. -o exe a.o b.o -Wl,-z,execstack这会同时产生两个效果消除警告因为ctx.arg.z_execstack已为 truesrc/input-files.cc#L413 中的条件不再成立输出文件的PT_GNU_STACK标志变为RWE栈可执行。务必注意这将显著削弱程序的安全性——栈可执行意味着经典的栈溢出攻击路径重新打开。mold 文档docs/execstack.md对此的表述是“will significantly weaken your programs security”。在做出这个决定前请确认代码确实依赖可执行栈例如 GCC 嵌套函数、运行时代码生成并评估风险。测试 test/execstack.sh 验证了该行为$CC -B. -o $t/exe $t/a.o -Wl,-z,execstack readelf --segments -W $t/exe | grep GNU_STACK.* RWE # 栈可执行方案二-z execstack-if-needed按需启用mold 还提供了一个折中选项仅当某个输入文件确实要求可执行栈时才启用它gcc -B. -o exe a.o b.o -Wl,-z,execstack-if-needed其实现位于 src/main.cc#L531-L535// Handle -z execstack-if-needed. if (ctx.arg.z_execstack_if_needed) for (ObjectFileE *file : ctx.objs) if (file-needs_executable_stack) ctx.arg.z_execstack true;逻辑非常直观链接收尾阶段若任一输入目标文件此前被标记为needs_executable_stack则自动把z_execstack置为 true。这相当于把 GNU ld 的隐式行为“显式化”让用户能确认并接受这一后果而不是被静默影响。测试 test/arch-x86_64-execstack-if-needed.sh 验证了两种情形# 输入文件带可执行栈标记但默认链接 → 栈保持不可执行RW $CC -B. -o $t/exe $t/a.o /dev/null readelf --segments -W $t/exe | grep GNU_STACK.* RW # 加上 -z execstack-if-needed → 栈变为可执行RWE $CC -B. -o $t/exe $t/a.o -Wl,-z,execstack-if-needed readelf --segments -W $t/exe | grep GNU_STACK.* RWE 注意该选项与警告的相互作用当指定-z execstack-if-needed时src/input-files.cc#L413 中的警告条件!ctx.arg.z_execstack_if_needed不成立因此警告本身也会被抑制。方案三重写代码摆脱对可执行栈的依赖推荐如果你不希望传递-z execstack最稳妥的做法是改写代码使其不再依赖可执行栈。具体建议避免 GCC 嵌套函数改为显式传入上下文指针如 C 语言惯例中的void *ctx参数 函数指针或改用 C lambda / 结构体封装闭包检查内联汇编如果汇编代码中手工添加了.section .note.GNU-stack, x请确认是否真的需要执行位通常移除x标志即可审计信号处理与运行时代码生成凡是在栈上生成代码的技术都需要警惕。这种方案在消除警告的同时保持栈不可执行安全收益最大也是 mold 文档推荐的首选方向。相关选项与辅助手段-z noexecstack显式恢复默认与-z execstack对应mold 支持-z noexecstack显式恢复“栈不可执行”的默认行为参见 docs/mold.md 中关于-z execstack/-z noexecstack的说明以及 src/cmdline.cc#L1163-L1176 的解析逻辑。命令行中后出现的选项会覆盖先前的设置例如gcc -B. -o exe a.o -Wl,-z,execstack -Wl,-z,noexecstack # 最终 PT_GNU_STACK 为 RW不可执行这正是 test/execstack.sh 中验证过的组合行为。-z stack-size控制栈大小与可执行栈相关的另一个-z选项是-z stack-sizeN它设置PT_GNU_STACK段的p_memsz即内核为进程分配的栈大小解析位于 src/cmdline.cc#L1228-L1229并写入 src/output-chunks.cc#L295。注意它只影响栈的大小不影响可执行性。与--fatal-warnings联动mold 的--fatal-warnings选项可以把警告升级为错误见 docs/mold.md。在 CI 或安全敏感的构建中可以结合该选项强制团队处理可执行栈问题避免警告被忽视。验证与测试如何在你的项目里确认行为mold 仓库自带的测试覆盖了本主题的全部关键行为可作为你本地验证的参考测试脚本验证内容test/execstack.sh-z execstack产生 RWE-z execstack -z noexecstack回退为 RW默认链接为 RWtest/arch-x86_64-execstack-if-needed.sh带x标志的.note.GNU-stack输入在默认下输出 RW加-z execstack-if-needed后输出 RWEtest/arch-x86_64-warn-execstack.sh构造带x标志的输入验证警告文本may cause a segmentation fault/requires executable stack确实被打印建议你在实际项目中遇到该警告时按以下流程排查先用readelf --sections找出哪些输入目标文件带有可执行标志的.note.GNU-stack节定位产生该节的源码重点排查 GCC 嵌套函数、内联汇编确认该文件是否真的需要可执行栈——绝大多数情况下并不需要若确需使用-z execstack-if-needed比全局-z execstack影响面更小并在安全评审中记录该决策。总结可执行栈是历史上为了兼容旧代码而存在的安全隐患。mold 在execstack问题上的立场非常明确拒绝像 GNU ld 那样被.note.GNU-stack标记节静默左右把“栈可执行”的决定权完全交还给用户。理解这一机制不仅能帮你正确修复警告也能让你在链接层面的安全取舍上做出更明智的决策——记住警告的出现不是错误而是 mold 在提醒你这里有一个值得认真对待的安全决定。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价