ShadowCallStack (SCS) is an LLVM instrumentation mode that protects against return address overwrites (like stack buffer overflows) by saving a function's return address to a separately allocated ShadowCallStack in the function prolog of nonleaf functions and loading the return address from the ShadowCallStack in the function epilog. The return address is also stored on the regular stack for compatibility with unwinders, but is otherwise unused. This ensures that attacks that modify the return address on the regular stack have no effect on program control flow.
On aarch64, the instrumentation makes use of the x18
register to reference the ShadowCallStack, meaning that references
to the ShadowCallStack don't have to be stored in memory.
This makes it possible to implement a runtime that avoids exposing
the address of the ShadowCallStack to attackers that can read
arbitrary memory.
Implementation
Android supports ShadowCallStack for both kernel and userspace.
Enable SCS for the kernel
To enable ShadowCallStack for the kernel, add the following line to the kernel config file:
CONFIG_SHADOW_CALL_STACK=y
Enable SCS in userspace
To enable ShadowCallStack in userspace components, add the following lines to a component's blueprint file:
sanitize: { scs: true }
SCS assumes that the x18
register is reserved to store the address of the
ShadowCallStack, and isn't used for any other purposes. While all system
libraries are compiled to reserve the x18
register, this is potentially
problematic if SCS is enabled for userspace components that interoperate with
in-process legacy code (for example, libraries that could be loaded by third-party
apps), which may clobber the x18
register. As such, we only recommend
enabling SCS in self-contained components that won't be loaded into legacy
binaries.
Validation
There are no CTS test specifically for SCS. Instead, make sure that CTS tests pass with and without SCS enabled to verify that SCS isn't impacting the device.