AD3005: EnableStackClashProtection¶
Summary¶
| Property | Value |
|---|---|
| ID | AD3005 |
| Name | EnableStackClashProtection |
| Category | Security |
| Severity | Warning |
| Applies to | ELF (Linux/Unix) |
Description¶
Stack clash protection prevents the stack from 'clashing' with other memory regions (like the heap) by probing stack pages as they are allocated. This prevents attackers from using large stack allocations to skip over guard pages.
How It Works¶
The rule checks for evidence of stack clash protection:
- Presence of stack probe code patterns
- Compiler flags in DWARF debug info
- Known stack probe symbols
Stack clash protection works by: 1. Inserting probes for each page of stack allocation 2. Ensuring guard pages are touched if crossed 3. Triggering SIGSEGV if stack extends into protected regions
Why This Matters¶
Stack clash attacks bypass traditional stack protections by exploiting the gap between allocated and used stack memory. A single large allocation can jump over guard pages, corrupting adjacent memory regions and enabling privilege escalation.
The Stack Clash Vulnerability¶
In 2017, Qualys disclosed CVE-2017-1000364 affecting virtually all Linux systems:
Normal Stack Growth:
┌───────────────┐
│ Stack │ ← Grows down page-by-page
├───────────────┤
│ Guard Page │ ← Touched, triggers expansion or SIGSEGV
├───────────────┤
│ Heap │
└───────────────┘
Stack Clash Attack:
┌───────────────┐
│ Stack │
├───────────────┤
│ Guard Page │ ← SKIPPED by large alloca()
├───────────────┤
│ Heap │ ← CORRUPTED!
└───────────────┘
Attack Mechanism¶
- Large stack allocation: Attacker triggers huge stack frame (VLA, alloca)
- Skip guard page: Allocation jumps over single guard page
- Land on heap: Stack pointer now points to heap memory
- Write to "stack": Actually writes to heap structures
- Control hijacking: Corrupted heap metadata leads to code execution
Real-World Impact¶
| CVE | Target | Impact |
|---|---|---|
| CVE-2017-1000364 | Linux kernel | Local privilege escalation |
| CVE-2017-1000365 | glibc ld.so | Container escape |
| CVE-2017-1000366 | glibc | Privilege escalation via sudo |
| CVE-2017-1000367 | sudo | Root via stack clash |
How Stack Clash Protection Works¶
// Without protection
void func(size_t n) {
char buf[n]; // VLA - single SP decrement
// SP jumps directly, may skip guard page
}
// With -fstack-clash-protection
void func(size_t n) {
char buf[n]; // VLA - probed allocation
// SP decremented page-by-page, each page touched
// Guard page definitely encountered if crossed
}
Performance Characteristics¶
| Allocation Type | Overhead |
|---|---|
| Small stack (<4KB) | None |
| VLA/alloca | Probe loop added |
| Large fixed arrays | One-time probe |
| Overall | <1% typical |
The protection is only active for large allocations where it's needed.
Comparison with Other Protections¶
| Protection | What It Prevents |
|---|---|
| Guard pages | Normal stack overflow |
| Stack canary | Buffer overflow |
| Stack clash protection | Guard page bypass |
Stack clash protection ensures guard pages actually work.
Compiler Support¶
| Compiler | Flag | Since |
|---|---|---|
| GCC | -fstack-clash-protection |
GCC 8 |
| Clang | -fstack-clash-protection |
Clang 11 |
- Stack Clash CVE: Mitigates CVE-2017-1000364 and similar
- Guard page bypass: Prevents jumping over guard pages
- Memory safety: Ensures stack doesn't corrupt heap
- Defense in depth: Complements other stack protections
Performance Considerations¶
Stack clash protection has minimal overhead for typical applications:
| Scenario | Overhead |
|---|---|
| Normal function calls | None |
| Small allocations (<4KB) | None |
| Large stack frames (>4KB) | Single probe |
| VLA/alloca | Probe loop |
| Overall typical | <1% |
Workload impact:
| Workload Type | Overhead |
|---|---|
| Compute-bound | <0.5% |
| I/O-bound | Negligible |
| Recursive algorithms | 1-2% |
| Heavy VLA usage | 2-5% |
Why overhead is low: - Probing only affects large allocations - Most functions have small stack frames (<4KB) - Probe cost is one memory access per page - No overhead for functions without large allocations
When to profile: - Performance-critical recursive algorithms - Code with many Variable Length Arrays - Real-time systems with strict latency
Resolution¶
GCC/Clang¶
# Enable stack clash protection
gcc -fstack-clash-protection source.c -o binary
# Combine with other protections
gcc -fstack-clash-protection -fstack-protector-strong source.c -o binary
CMake¶
add_compile_options(-fstack-clash-protection)
# Check compiler support
include(CheckCCompilerFlag)
check_c_compiler_flag(-fstack-clash-protection HAVE_STACK_CLASH_PROTECTION)
if(HAVE_STACK_CLASH_PROTECTION)
add_compile_options(-fstack-clash-protection)
endif()
Makefile¶
When to Suppress¶
This rule may be suppressed for:
- Performance-critical code: Probing adds overhead for large allocations
- Older compilers: GCC < 8, Clang < 11 don't support this
- Kernel code: Different stack handling requirements
- Embedded systems: Limited stack, no guard pages
Caveats¶
- Requires GCC 8+ or Clang 11+
- Some performance overhead for functions with large stack frames
- May conflict with custom stack handling
The Stack Clash Attack¶
Normal Memory Layout:
┌─────────────────┐ High Address
│ Stack │ ↓ grows down
├─────────────────┤
│ Guard Page │ ← Should trigger SIGSEGV
├─────────────────┤
│ ... │
├─────────────────┤
│ Heap │ ↑ grows up
└─────────────────┘ Low Address
Stack Clash Attack:
1. Allocate huge stack frame (e.g., alloca(1MB))
2. Stack pointer jumps over guard page
3. Stack now overlaps heap
4. Write through stack → corrupt heap
Stack Clash Protection¶
With -fstack-clash-protection:
1. Large allocation broken into 4KB chunks
2. Each chunk is probed (touched)
3. If probe hits guard page → SIGSEGV
4. Attack prevented
Checking Stack Clash Protection¶
# Check for stack probe function
objdump -d binary | grep -E "stack_chk|probe"
# Check DWARF for compile flags
readelf --debug-dump=info binary | grep "stack-clash"
Compiler Support¶
| Compiler | Version | Support |
|---|---|---|
| GCC | 8+ | ✅ Full |
| Clang | 11+ | ✅ Full |
| ICC | - | ❌ No |