Information Security 7 min read

Shadow Call Stack (SCS) in Android: Mechanism, Requirements, and Implementation

Android’s Shadow Call Stack (SCS), silently enabled since Android R on AArch64 devices, stores return addresses in a protected register‑based stack separate from the regular stack, complementing stack canaries and requiring hardware support, while developers can activate it via -fsanitize=shadow-call-stack and avoid using X18 elsewhere.

OPPO Kernel Craftsman
OPPO Kernel Craftsman
OPPO Kernel Craftsman
Shadow Call Stack (SCS) in Android: Mechanism, Requirements, and Implementation

When discussing kernel stack‑overflow mitigations, many think of Stack Canary. This article introduces a newer, silently enabled security mechanism on Android devices: the Shadow Call Stack (SCS).

In a normal function call the return address is stored on the regular stack, which attackers can overwrite to hijack execution (e.g., via buffer overflow or ROP). SCS, built on LLVM, creates a secure stack frame that holds return addresses separately from the normal stack, preventing such attacks.

Since Android R, the AOSP Compatibility Definition Document (CDD) strongly recommends enabling CFI, SCS, and IntSan, and the AOSP kernel starting from version 5.4 enables SCS by default.

SCS requires hardware support. It is currently supported on architectures that provide the necessary instructions, such as Intel CET on x86_64 and Pointer Authentication on aarch64. In practice, only aarch64 is supported today; LLVM removed x86_64 support in version 9.0 due to performance and security concerns.

Enabling SCS can be done globally for the kernel or for a specific user‑space process by passing -fsanitize=shadow-call-stack to the linker. Individual functions that should not use SCS can be marked with __attribute__((no_sanitize("shadow-call-stack"))) .

SCS complements -fstack-protector to provide defense‑in‑depth. On Aarch64 it uses the X18 register to reference the shadow stack, avoiding any memory‑resident pointer that could be read by an attacker. The mechanism trusts the shadow stack and does not perform error handling.

Example demo (C and assembly) before and after enabling SCS:

Int foo() { return bar() + 1; }

Compiled without SCS (x86‑style assembly):

push %rax callq bar add $0x1,%eax pop %rcx retq

Compiled with SCS enabled on Aarch64:

str x30, [x18], #8 stp x29, x30, [sp, #-16]! mov x29, sp bl bar add w0, w0, #1 ldp x29, x30, [sp], #16 ldr x30, [x18, #-8]! ret

The red‑highlighted instructions are the core of SCS:

str x30, [x18], #8 – stores the return address from X30 into the memory location pointed to by X18 and then increments X18 by 8 bytes.

ldr x30, [x18, #-8]! – decrements X18 by 8 bytes and loads the saved return address back into X30.

Because SCS relies on the X18 register, developers must ensure that no third‑party libraries or legacy code use X18 for other purposes. OPPO collaborated with Qualcomm and MediaTek in 2020 to enable SCS early, reducing the risk of return‑address hijacking without compatibility issues.

In the OPPO FIND X3 project, reverse‑engineering of the boot image confirmed the presence of SCS by observing the characteristic shadow‑stack instructions.

Note: While SCS significantly raises the security bar, it does not protect against all attacks. Android developers should also employ input validation, memory‑safe programming practices, and proper permission management.

assemblyLLVMAndroid securityAarch64KernelShadow Call Stack
OPPO Kernel Craftsman
Written by

OPPO Kernel Craftsman

Sharing Linux kernel-related cutting-edge technology, technical articles, technical news, and curated tutorials

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.