Skip to content

AD5008: EnableClangSafeStackMachO

Summary

Property Value
ID AD5008
Name EnableClangSafeStackMachO
Category Security
Severity Warning
Applies to Mach-O executables compiled with Clang

Description

Clang SafeStack provides strong protection against stack buffer overflows by separating the stack into two regions:

  1. Safe stack: Contains return addresses, register spills, and safe local variables
  2. Unsafe stack: Contains local variables that may be accessed through pointers

This makes it much harder for attackers to overwrite return addresses via buffer overflows.

How It Works

The rule checks for SafeStack symbols:

  • __safestack_init
  • __safestack_unsafe_stack_ptr
  • __safestack_pointer_address

The rule also verifies: - The binary was compiled with Clang - It's not a shared library (SafeStack doesn't support DSOs) - It doesn't use ucontext.h functions (incompatible)

Why This Matters

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

The Fundamental Protection

Traditional Stack (vulnerable):
  ┌───────────────┐
  │ Return Addr   │  ← Overflow reaches here
  ├───────────────┤
  │ Saved RBP     │
  ├───────────────┤
  │ char buf[64]  │  ← Overflow starts
  └───────────────┘

SafeStack (protected):
  Safe Stack:       Unsafe Stack:
  ┌───────────┐     ┌───────────┐
  │ Return Addr│     │ char buf[]│  ← Overflow here
  ├───────────┤     │           │    Cannot reach
  │ Saved RBP  │     │           │    safe stack!
  └───────────┘     └───────────┘

Comparison with Other Protections

Protection Mechanism Bypass Possible
Stack canary Detection Yes (info leak)
ASLR Randomization Yes (info leak)
SafeStack Physical separation No path exists

What Goes Where

Safe Stack Unsafe Stack
Return addresses Arrays
Spilled registers Address-taken vars
Safe scalars Structs with arrays

macOS-Specific Considerations

Platform SafeStack Support
macOS x86_64 Clang supported
macOS ARM64 Limited support
iOS Not commonly used (PAC preferred)

When to Use SafeStack vs PAC

Platform Recommendation
Intel Mac SafeStack
Apple Silicon PAC (hardware)
Cross-platform Both where applicable

Performance Considerations

SafeStack has minimal runtime overhead:

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

Why overhead is low: - No runtime checks (unlike stack canaries) - Uses thread-local storage for unsafe stack pointer - Most variables remain on safe stack - Architectural protection requires no validation

Workload impact:

Workload Overhead
Compute-bound <0.5%
Server applications <1%
Buffer-intensive 1-2%

Comparison with alternatives on macOS:

Protection Overhead Hardware Req
Stack canary <1% None
SafeStack ~1% None
PAC <1% Apple Silicon

SafeStack is one of the lowest-overhead protections available.

  • Stronger than stack canaries: Prevents overwrites entirely, not just detects them
  • Low overhead: Typically 1-5% performance cost
  • Backward compatible: Works with existing code
  • Comprehensive: Protects all stack-based vulnerabilities

Resolution

Command Line

clang -fsanitize=safe-stack source.c -o binary

Xcode

Add to Other C Flags:

-fsanitize=safe-stack

CMake

target_compile_options(myapp PRIVATE -fsanitize=safe-stack)
target_link_options(myapp PRIVATE -fsanitize=safe-stack)

Makefile

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

When to Suppress

This rule may be suppressed for:

  • Shared libraries: SafeStack doesn't support DSOs
  • ucontext.h usage: getcontext/setcontext/makecontext/swapcontext are incompatible
  • Multi-compiler builds: All code must be compiled with Clang SafeStack
  • Signal handlers: Using sigaltstack() may cause issues
  • GCC-compiled code: Only Clang supports SafeStack

Compatibility Limitations

SafeStack is NOT compatible with:

#include <ucontext.h>

// These functions don't work with SafeStack:
getcontext(&ctx);
setcontext(&ctx);
makecontext(&ctx, func, 0);
swapcontext(&oldctx, &newctx);
  • AD5003 - Stack canary protection
  • AD3031 - ELF SafeStack check

References