AD3006: EnableNonExecutableStack¶
Summary¶
| Property | Value |
|---|---|
| ID | AD3006 |
| Name | EnableNonExecutableStack |
| Category | Security |
| Severity | Error |
| Applies to | ELF (Linux/Unix) |
Description¶
This check ensures that non-executable stack is enabled. A common type of exploit is the stack buffer overflow. An application receives, from an attacker, more data than it is prepared for and stores this information on its stack, writing beyond the space reserved for it.
This can be designed to cause execution of the data written on the stack. One mechanism to mitigate this vulnerability is for the system to not allow the execution of instructions in sections of memory identified as part of the stack.
How It Works¶
The rule examines the PT_GNU_STACK segment in the ELF program headers:
- Checks for presence of
PT_GNU_STACK - Verifies the
PF_X(execute) flag is NOT set - Confirms the segment specifies non-executable permissions
Why This Matters¶
Non-executable stack protection is the single most impactful mitigation against classic buffer overflow exploits. It uses hardware features to make the stack incapable of executing code, blocking the most common exploitation technique.
The Classic Attack It Prevents¶
Stack buffer overflow exploitation traditionally works like this:
void vulnerable() {
char buf[64];
gets(buf); // No bounds checking!
}
// Attacker sends:
// [NOP sled][shellcode][return addr pointing to buffer]
// Execution flow:
// 1. Function returns to attacker's address
// 2. Jump into NOP sled on stack
// 3. Execute shellcode
// 4. Attacker wins
With NX stack, step 3 causes a SIGSEGV instead of shellcode execution.
Hardware Protection¶
NX is enforced by the CPU's memory management unit:
| CPU | Feature | Page Table Bit |
|---|---|---|
| AMD64 | NX (No-Execute) | Bit 63 |
| Intel 64 | XD (Execute Disable) | Bit 63 |
| ARM64 | XN (Execute Never) | Varies |
| ARM32 | XN | Domain-based |
Why It's Effective¶
| Property | Benefit |
|---|---|
| Hardware enforcement | Cannot be bypassed by software |
| Zero overhead | No runtime performance cost |
| Every instruction check | CPU checks on each fetch |
| Page granularity | Entire stack protected |
Attack Technique Evolution¶
1990s: Stack shellcode → Blocked by NX
2000s: Return-to-libc → Uses existing code
2010s: ROP chains → Uses code snippets
2020s: JOP, COOP → More sophisticated
NX forced attackers to develop much more complex techniques.
The PT_GNU_STACK Segment¶
ELF specifies stack permissions through PT_GNU_STACK:
Good: PT_GNU_STACK with RW (Read-Write only)
Flags: 0x6 (PF_R | PF_W)
Bad: PT_GNU_STACK with RWX (Executable)
Flags: 0x7 (PF_R | PF_W | PF_X)
Bad: No PT_GNU_STACK (legacy default = executable)
Common Causes of Executable Stack¶
| Cause | Solution |
|---|---|
| GCC nested functions | Trampolines—avoid or use alternatives |
| Hand-written assembly | Add .note.GNU-stack section |
| Old object files | Recompile with modern toolchain |
| JIT compilers (vulnerable implementation) | Use proper mprotect lifecycle |
Nested Function Trampolines¶
GCC's nested functions can cause executable stack:
Fix: Avoid nested functions or use -fno-trampolines.
- NX protection: Hardware-enforced non-executable memory
- Shellcode prevention: Stack-based shellcode won't execute
- Classic exploit mitigation: Defeats traditional buffer overflow exploits
- Standard protection: Expected on all modern systems
Resolution¶
GCC/Clang¶
# Mark stack as non-executable
gcc -Wl,-z,noexecstack source.c -o binary
# Combine with other security flags
gcc -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now source.c -o binary
CMake¶
Makefile¶
Fix Assembly Files¶
If assembly files are making the stack executable, add:
When to Suppress¶
This rule should rarely be suppressed. Exceptions might include:
- Nested functions: GCC trampolines may need executable stack
- JIT compilers: Managing executable memory differently
- Very legacy code: Specific compatibility requirements
Caveats¶
- Same as AD3002 - this rule covers the same protection
- Assembly files without
.note.GNU-stackcause executable stack - All object files must be compiled correctly
Fixing Common Issues¶
Assembly Files¶
# Check which objects are causing the issue
for f in *.o; do
if readelf -l "$f" 2>/dev/null | grep -q "GNU_STACK.*RWE"; then
echo "Executable stack: $f"
fi
done
Nested Functions (GCC)¶
Avoid nested functions that require trampolines:
// Bad - may require executable stack
void outer(void) {
void inner(void) { /* ... */ }
inner();
}
// Good - use regular function
static void inner(void) { /* ... */ }
void outer(void) {
inner();
}