Skip to content

AD3031: EnableClangSafeStack

Summary

Property Value
ID AD3031
Name EnableClangSafeStack
Category Security
Severity Warning
Applies to ELF (Linux/Unix)

Description

Clang's SafeStack is a security hardening mechanism that separates the stack into two regions: a "safe stack" for return addresses and spilled registers, and an "unsafe stack" for local variables that may be vulnerable to buffer overflows.

Note: This rule only applies to binaries that are: - Compiled exclusively with Clang/LLVM - Not using ucontext.h functions (getcontext, setcontext, makecontext, swapcontext) - Not shared libraries (DSOs)

How It Works

The rule checks for SafeStack-related symbols and structures:

  1. Presence of __safestack_init
  2. Presence of __safestack_unsafe_stack_ptr
  3. SafeStack-related sections in the binary

Why This Matters

SafeStack provides architectural protection against stack buffer overflows by physically separating vulnerable data from critical control flow information. Unlike canaries that detect overflows, SafeStack makes overflows unable to reach return addresses at all.

The Fundamental Problem

Traditional stack layout (unsafe):
  All data on same stack
  Return address ABOVE local buffers
  Overflow goes up → reaches return address

SafeStack layout:
  Two separate memory regions
  Return addresses on different stack
  Overflow CANNOT reach return addresses
  Architecturally impossible to corrupt

How SafeStack Protects

Buffer overflow attempt:

Traditional Stack:        SafeStack:
┌───────────────┐         Safe:           Unsafe:
│ Return Addr │ ← Hit!  ┌─────────────┐   ┌─────────────┐
├───────────────┤         │ Return Addr │   │ char buf[]│ ← Overflow
│ Canary      │         ├─────────────┤   │             │   stays here
├───────────────┤         │ Saved RBP   │   │             │
│ char buf[]  │ ← Start └─────────────┘   └─────────────┘
└───────────────┘         Protected!      Can overflow but
                                           can't reach safe stack

What Goes Where

Safe Stack Unsafe Stack
Return addresses Arrays
Spilled registers Variables with address taken
Safe scalars Structs containing arrays
Register-based vars Large local objects

Security Properties

Property Benefit
Physical separation No overflow path exists
No detection needed Attack structurally impossible
No canaries required Faster (no checks)
Works with heap sprays Stack isolation independent

Comparison with Other Protections

Protection Mechanism Bypass Possible
Stack canary Detection Yes (info leak)
ASLR Randomization Yes (info leak)
SafeStack Separation No (architectural)

Performance Considerations

SafeStack is one of the lowest-overhead stack protections available:

Metric Overhead
Runtime ~0.1% average
Code size Minimal
Memory Second stack per thread (~8KB default)

Why overhead is minimal: - No runtime validation checks (unlike canaries) - Unsafe stack access uses thread-local pointer (one register) - Most variables stay on safe stack (no change) - Only address-taken vars move to unsafe stack

Workload impact:

Workload Type Overhead
Compute-bound <0.1%
Buffer-heavy 0.5-1%
Network servers <0.5%

Comparison with canaries:

Protection Runtime Cost Detection
Stack canary Check on return Post-corruption
SafeStack Pointer indirection No check needed

SafeStack is faster than canaries because it doesn't need runtime checks.

Limitations

Limitation Reason
Clang only Not in GCC
No ucontext Breaks context switching
No shared libs Static linking preferred
Thread-local storage Required for unsafe stack pointer
  • Stack separation: Return addresses protected from overflows
  • Low overhead: ~0.1% performance impact
  • Backward compatible: Works with existing code
  • Targeted protection: Protects most critical data

SafeStack Architecture

Traditional Stack:
┌─────────────────┐
│ Return Address  │ ← Vulnerable
├─────────────────┤
│ Saved Registers │
├─────────────────┤
│ Local Array     │ ← Overflow starts here
└─────────────────┘

SafeStack:
Safe Stack:           Unsafe Stack:
┌─────────────────┐   ┌─────────────────┐
│ Return Address  │   │ Local Array     │ ← Overflow here
├─────────────────┤   │ (can't reach    │
│ Saved Registers │   │  return addr)   │
└─────────────────┘   └─────────────────┘

Resolution

Clang

# Enable SafeStack
clang -fsanitize=safe-stack source.c -o binary

# Combine with other protections
clang -fsanitize=safe-stack -fstack-protector-strong source.c -o binary

CMake

if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
    add_compile_options(-fsanitize=safe-stack)
    add_link_options(-fsanitize=safe-stack)
endif()

Makefile

CC = clang
CFLAGS += -fsanitize=safe-stack
LDFLAGS += -fsanitize=safe-stack

When to Suppress

This rule is automatically marked as not applicable when:

  • Non-Clang compilers: Binary was compiled with GCC, Rust, ICC, etc.
  • Uses ucontext.h: Binary calls getcontext, setcontext, makecontext, or swapcontext (incompatible with SafeStack)
  • Shared libraries: SafeStack does not support DSOs (dynamic shared objects)
  • Multi-compiler binaries: Binary contains code from both Clang and other compilers (e.g., GCC). SafeStack requires all code to be compiled with -fsanitize=safe-stack
  • No debug info: Cannot determine compiler without DWARF information

Manual suppression may be appropriate for:

  • Signal handlers using sigaltstack(): May not work correctly with SafeStack
  • Platform limitations: Not all platforms support SafeStack
  • Already using alternatives: Other stack protection mechanisms in place

Known Compatibility Limitations

According to the Clang SafeStack documentation:

  1. ucontext.h is not supported: Programs using getcontext(), setcontext(), makecontext(), or swapcontext() are incompatible with SafeStack

  2. DSOs not supported: Linking dynamic libraries with SafeStack is not currently supported

  3. Mixed compilation: SafeStack supports linking modules compiled with and without SafeStack, but for full protection, all code should be compiled with SafeStack

  4. Signal handlers: Signal handlers that use sigaltstack() must not use the unsafe stack (use __attribute__((no_sanitize("safe-stack"))))

  5. Mark-and-sweep GC: Garbage collection implementations must scan both safe and unsafe stacks

Caveats

  • Clang only: GCC does not support SafeStack
  • Platform support: Linux x86-64 has best support
  • Thread-local storage: Uses TLS for unsafe stack pointer
  • Some code patterns: May require adjustments

Supported Platforms

Platform Support
Linux x86-64 ✅ Full
Linux AArch64 ✅ Full
FreeBSD ✅ Full
macOS ⚠️ Limited
Windows ❌ No

How SafeStack Works

  1. At compile time:
  2. Compiler identifies safe vs unsafe allocations
  3. Return addresses, saved registers → safe stack
  4. Arrays, address-taken variables → unsafe stack

  5. At runtime:

  6. Two separate stacks maintained
  7. Unsafe stack pointer in TLS
  8. Buffer overflow on unsafe stack can't reach safe stack

Checking SafeStack

# Check for SafeStack symbols
nm binary | grep safestack

# Check for unsafe stack pointer
nm binary | grep unsafe_stack

# Disassemble to see two stack pointers
objdump -d binary | grep -E "safe|unsafe"

Comparison with Other Protections

Feature Stack Canary SafeStack Shadow Stack
Detects overwrite After the fact Prevents Prevents
Performance ~1% ~0.1% Hardware
Backward compat Yes Yes No
Compiler GCC/Clang Clang GCC/Clang

References