AD3044: EnableShadowCallStack¶
Summary¶
| Property | Value |
|---|---|
| ID | AD3044 |
| Name | EnableShadowCallStack |
| Category | Security |
| Severity | Note |
| Applies to | ELF (Linux/Unix) - AArch64, RISC-V |
Description¶
Shadow Call Stack is a security feature that protects return addresses by storing them in a separate "shadow" stack. When a function is called, the return address is saved to both the regular stack and the shadow stack. On function return, the return address is loaded exclusively from the shadow stack, ignoring any modifications to the regular stack.
This provides strong protection against: - Return-Oriented Programming (ROP) - Attackers cannot redirect execution by overwriting return addresses - Stack buffer overflows - Even if the stack is corrupted, return addresses are protected - Use-after-return vulnerabilities - Shadow stack is separate from the main stack
SCS uses a dedicated register (x18 on AArch64) to point to the shadow stack, making it very efficient with minimal runtime overhead.
How to Fix¶
Clang¶
Compile with the -fsanitize=shadow-call-stack flag:
# AArch64
clang --target=aarch64-linux-gnu -fsanitize=shadow-call-stack -o binary source.c
# RISC-V
clang --target=riscv64-linux-gnu -fsanitize=shadow-call-stack -o binary source.c
GCC¶
GCC 10+ supports Shadow Call Stack:
Platform Support¶
| Architecture | Register Used | Status |
|---|---|---|
| AArch64 | x18 | Production ready |
| RISC-V | x18 (gp) | Supported |
| x86_64 | N/A | Not supported |
Performance Considerations¶
Shadow Call Stack is designed for production use with minimal overhead:
| Metric | Impact |
|---|---|
| Runtime overhead | <1% typical |
| Memory per thread | 4-8KB shadow stack |
| Register reservation | x18 dedicated |
| Code size increase | <1% |
Benchmark data (typical application):
| Workload | Overhead |
|---|---|
| Compute-bound | 0.1-0.5% |
| I/O-bound | Negligible |
| Call-heavy | 0.5-1% |
Why SCS is efficient: - Each function call adds only two instructions (save/restore on shadow stack) - x18 register is dedicated, avoiding memory indirection - Shadow stack has excellent cache locality
Trade-offs:
| Aspect | Impact |
|---|---|
| Register pressure | One less general register (x18 reserved) |
| Thread creation | Small additional memory allocation |
| ABI compatibility | All code must respect x18 reservation |
Considerations¶
- ABI compatibility: The x18 register must be reserved across all code. On Android, x18 is reserved system-wide.
- Thread-local storage: Each thread needs its own shadow stack.
- setjmp/longjmp: Requires special handling to maintain shadow stack consistency.
Applicability¶
This rule applies to: - AArch64 (ARM64) ELF binaries - RISC-V ELF binaries
This rule does not apply to: - x86/x86_64 binaries (use Intel CET Shadow Stack instead - AD3016) - ARM32 binaries
References¶
Related Rules¶
- AD3016: EnableIntelShadowStack - Intel CET Shadow Stack for x86_64
- AD3018: EnableArmPAC - Pointer Authentication for return address protection
- AD2047: PeEnableShadowCallStack - PE version of this rule