ShadowCallStack

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.

Enabling SCS for the kernel

To enable ShadowCallStack for the kernel, add the following line to the kernel config file:

CONFIG_SHADOW_CALL_STACK=y

Enabling 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 applications), 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.