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:
- Presence of
__safestack_init - Presence of
__safestack_unsafe_stack_ptr - 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¶
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, orswapcontext(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:
-
ucontext.h is not supported: Programs using
getcontext(),setcontext(),makecontext(), orswapcontext()are incompatible with SafeStack -
DSOs not supported: Linking dynamic libraries with SafeStack is not currently supported
-
Mixed compilation: SafeStack supports linking modules compiled with and without SafeStack, but for full protection, all code should be compiled with SafeStack
-
Signal handlers: Signal handlers that use
sigaltstack()must not use the unsafe stack (use__attribute__((no_sanitize("safe-stack")))) -
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¶
- At compile time:
- Compiler identifies safe vs unsafe allocations
- Return addresses, saved registers → safe stack
-
Arrays, address-taken variables → unsafe stack
-
At runtime:
- Two separate stacks maintained
- Unsafe stack pointer in TLS
- 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 |