SafeStack

Introduction

SafeStack is an instrumentation pass that protects programs against attacksbased on stack buffer overflows, without introducing any measurable performanceoverhead. It works by separating the program stack into two distinct regions:the safe stack and the unsafe stack. The safe stack stores return addresses,register spills, and local variables that are always accessed in a safe way,while the unsafe stack stores everything else. This separation ensures thatbuffer overflows on the unsafe stack cannot be used to overwrite anythingon the safe stack.

SafeStack is a part of theCode-Pointer Integrity (CPI) Project.

Performance

The performance overhead of the SafeStack instrumentation is less than 0.1% onaverage across a variety of benchmarks (see theCode-Pointer Integrity paper for details). This is mainlybecause most small functions do not have any variables that require the unsafestack and, hence, do not need unsafe stack frames to be created. The cost ofcreating unsafe stack frames for large functions is amortized by the cost ofexecuting the function.

In some cases, SafeStack actually improves the performance. Objects that end upbeing moved to the unsafe stack are usually large arrays or variables that areused through multiple stack frames. Moving such objects away from the safestack increases the locality of frequently accessed values on the stack, suchas register spills, return addresses, and small local variables.

Compatibility

Most programs, static libraries, or individual files can be compiledwith SafeStack as is. SafeStack requires basic runtime support, which, on mostplatforms, is implemented as a compiler-rt library that is automatically linkedin when the program is compiled with SafeStack.

Linking a DSO with SafeStack is not currently supported.

Known compatibility limitations

Certain code that relies on low-level stack manipulations requires adaption towork with SafeStack. One example is mark-and-sweep garbage collectionimplementations for C/C++ (e.g., Oilpan in chromium/blink), which must bechanged to look for the live pointers on both safe and unsafe stacks.

SafeStack supports linking statically modules that are compiled with andwithout SafeStack. An executable compiled with SafeStack can load dynamiclibraries that are not compiled with SafeStack. At the moment, compilingdynamic libraries with SafeStack is not supported.

Signal handlers that usesigaltstack() must not use the unsafe stack (see__attribute__((no_sanitize("safe-stack"))) below).

Programs that use APIs fromucontext.h are not supported yet.

Security

SafeStack protects return addresses, spilled registers and local variables thatare always accessed in a safe way by separating them in a dedicated safe stackregion. The safe stack is automatically protected against stack-based bufferoverflows, since it is disjoint from the unsafe stack in memory, and it itselfis always accessed in a safe way. In the current implementation, the safe stackis protected against arbitrary memory write vulnerabilities thoughrandomization and information hiding: the safe stack is allocated at a randomaddress and the instrumentation ensures that no pointers to the safe stack areever stored outside of the safe stack itself (see limitations below).

Known security limitations

A complete protection against control-flow hijack attacks requires combiningSafeStack with another mechanism that enforces the integrity of code pointersthat are stored on the heap or the unsafe stack, such asCPI, or a forward-edge control flow integritymechanism that enforces correct calling conventions at indirect call sites,such asIFCC with aritychecks. Clang has control-flow integrity protection scheme forC++ virtualcalls, but not non-virtual indirect calls. WithSafeStack alone, an attacker can overwrite a function pointer on the heap orthe unsafe stack and cause a program to call arbitrary location, which in turnmight enable stack pivoting and return-oriented programming.

In its current implementation, SafeStack provides precise protection againststack-based buffer overflows, but protection against arbitrary memory writevulnerabilities is probabilistic and relies on randomization and informationhiding. The randomization is currently based on system-enforced ASLR and sharesits known security limitations. The safe stack pointer hiding is not perfectyet either: system library functions such asswapcontext, exceptionhandling mechanisms, intrinsics such as__builtin_frame_address, orlow-level bugs in runtime support could leak the safe stack pointer. In thefuture, such leaks could be detected by static or dynamic analysis tools andprevented by adjusting such functions to either encrypt the stack pointer whenstoring it in the heap (as already done e.g., bysetjmp/longjmpimplementation in glibc), or store it in a safe region instead.

TheCPI paper describes two alternative,stronger safe stack protection mechanisms, that rely on software faultisolation, or hardware segmentation (as available on x86-32 and some x86-64CPUs).

At the moment, SafeStack assumes that the compiler’s implementation is correct.This has not been verified except through manual code inspection, and couldalways regress in the future. It’s therefore desirable to have a separatestatic or dynamic binary verification tool that would check the correctness ofthe SafeStack instrumentation in final binaries.

Usage

To enable SafeStack, just pass-fsanitize=safe-stack flag to both compileand link command lines.

Supported Platforms

SafeStack was tested on Linux, NetBSD, FreeBSD and macOS.

Low-level API

__has_feature(safe_stack)

In some rare cases one may need to execute different code depending onwhether SafeStack is enabled. The macro__has_feature(safe_stack) canbe used for this purpose.

#if __has_feature(safe_stack)// code that builds only under SafeStack#endif

__attribute__((no_sanitize("safe-stack")))

Use__attribute__((no_sanitize("safe-stack"))) on a function declarationto specify that the safe stack instrumentation should not be applied to thatfunction, even if enabled globally (see-fsanitize=safe-stack flag). Thisattribute may be required for functions that make assumptions about theexact layout of their stack frames.

All local variables in functions with this attribute will be stored on the safestack. The safe stack remains unprotected against memory errors when accessingthese variables, so extra care must be taken to manually ensure that all suchaccesses are safe. Furthermore, the addresses of such local variables shouldnever be stored on the heap, as it would leak the location of the SafeStack.

__builtin___get_unsafe_stack_ptr()

This builtin function returns current unsafe stack pointer of the currentthread.

__builtin___get_unsafe_stack_bottom()

This builtin function returns a pointer to the bottom of the unsafe stack of thecurrent thread.

__builtin___get_unsafe_stack_top()

This builtin function returns a pointer to the top of the unsafe stack of thecurrent thread.

__builtin___get_unsafe_stack_start()

Deprecated: This builtin function is an alias for__builtin___get_unsafe_stack_bottom().

Design

Please refer to theCode-Pointer Integrityproject page for more information about the design of the SafeStack and itsrelated technologies.

setjmp and exception handling

TheOSDI’14 paper mentions thaton Linux the instrumentation pass finds calls to setjmp or functions thatmay throw an exception, and inserts required instrumentation at their callsites. Specifically, the instrumentation pass saves the shadow stack pointeron the safe stack before the call site, and restores it either after thecall to setjmp or after an exception has been caught. This is implementedin the functionSafeStack::createStackRestorePoints.

Publications

Code-Pointer Integrity.Volodymyr Kuznetsov, Laszlo Szekeres, Mathias Payer, George Candea, R. Sekar, Dawn Song.USENIX Symposium on Operating Systems Design and Implementation(OSDI), Broomfield, CO, October 2014