Friday, June 26, 2015
There's no reason for C to have just a single stack
This patch adds the safe stack instrumentation pass to
LLVM
, which separates the program stack into a safe stack, which stores return addresses, register spills, and local variables that are statically verified to be accessed in a safe way, and the unsafe stack, which stores everything else. Such separation makes it much harder for an attacker to corrupt objects on the safe stack, including function pointers stored in spilled registers and return addresses. You can find more information about the safe stack, as well as other parts of or control-flow hijack protection technique in our OSDI paper on code-pointer integrity (http://dslab.epfl.ch/pubs/cpi.pdf) and our project website (http://levee.epfl.ch).
Via Hacker News, Protection against stack-based memory corruption errors using SafeStack ⋅ llvm-mirror/llvm@7ffec83
I'm really surprised this wasn't done sooner. There's nothing in the C
Standard that mandates how the call stack must be
implemented, but it seems that for the past forty years or so, the system
stack (the stack the CPU uses to
store return addresses for subroutine calls or state information when
handling interrupts) has been the default place to store the call stack,
which is the prime reason why buffer overflows are
so dangerous (because with a buffer overrun, the attacker can embed machine
code and cause the CPU to return
to the embedded machine code to do nefarious things). Sure, it
might appear that dedicating another CPU register to point to this secondary stack might be wasteful,
but most C compilers on modern systems already use a second CPU register to point to the primary
stack (to make it easier to generate stack frames when debugging or analyzing
a core
dump). Also, the system stack wouldn't have to be so big—even an 8K stack on a 64-bit machine would easily allow a call
depth (A
calls B
which calls C
which
calls D
is a call-depth of 4) of over 500 (technically, 1,024
but this would disallow other uses of the stack, such as temporarily saving
registers, or handling of interrupts) which should handle most programs (the
exception being badly written programs with unbounded recursion; sidenote to Google: good one,
you got me).
This patch, however, seems to still save the call stack in the system stack with the exeception of arrays or items whose address is taken, and it stores the pointer to the “unsafe stack” in a thread-specific variable. It turns out the performance loss isn't that bad as most routines don't have such problematic variables to begin with.