Systems Security & Exploitation

a shadow stack

Imagine keeping a private second copy of your to-do list locked in a drawer no one else can reach. Whenever you finish a task and want to know what is next, you check the public list AND your locked copy; if they disagree, someone tampered with the public one. A shadow stack is that locked second copy for return addresses: a separate, protected stack that records where each function should return, so a corrupted return address on the ordinary stack can be caught.

Here is the mechanism. The normal stack stores the return address inline, right where buffer overflows can reach it. A shadow stack keeps a parallel stack containing ONLY return addresses, in memory the program cannot accidentally or maliciously write through ordinary pointers. On every call, the return address is pushed to BOTH the normal stack and the shadow stack. On every return, the processor (or compiler-inserted code) compares the address about to be used from the normal stack against the top of the shadow stack; if they match, return proceeds; if they differ — because an overflow or arbitrary write changed the normal-stack copy — the program aborts. This is the 'backward edge' of control-flow integrity: it guarantees a function returns only to its genuine caller, which is precisely what ROP and stack smashing violate. Intel CET provides a hardware shadow stack; ARM and compilers offer software shadow call stacks.

It matters because it defeats the entire backward-edge attack surface — return-address overwrites and ROP chains, which depend on lying about return addresses — more robustly than a stack canary, since there is no secret to leak and no contiguous-overflow assumption. The honest caveats: a shadow stack protects RETURNS only, not forward-edge indirect calls (those need the rest of CFI), and its security rests on the shadow region being truly inaccessible to the attacker's write primitives; a sufficiently powerful arbitrary write that can reach the shadow stack itself would undermine it, which is why hardware enforcement is preferred.

call f: push return_addr -> normal stack AND shadow stack ret in f: addr = pop(normal stack) if addr != pop(shadow stack) -> abort // return address was tampered with else jump addr

Each return is cross-checked against the protected shadow copy; a mismatch means the return address was corrupted.

A shadow stack guards the BACKWARD edge (returns) only; forward-edge indirect calls still need CFI or PAC, and its safety assumes the attacker's write primitives cannot reach the shadow region itself.

Also called
shadow call stackreturn-address shadowbackward-edge CFI影子呼叫堆疊返回位址影子