Skip to content

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:

  1. Presence of stack probe code patterns
  2. Compiler flags in DWARF debug info
  3. 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

  1. Large stack allocation: Attacker triggers huge stack frame (VLA, alloca)
  2. Skip guard page: Allocation jumps over single guard page
  3. Land on heap: Stack pointer now points to heap memory
  4. Write to "stack": Actually writes to heap structures
  5. 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

CFLAGS += -fstack-clash-protection

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

References