You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
8.9 KiB
211 lines
8.9 KiB
=========
|
|
SafeStack
|
|
=========
|
|
|
|
.. contents::
|
|
:local:
|
|
|
|
Introduction
|
|
============
|
|
|
|
SafeStack is an instrumentation pass that protects programs against attacks
|
|
based on stack buffer overflows, without introducing any measurable performance
|
|
overhead. 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 that
|
|
buffer overflows on the unsafe stack cannot be used to overwrite anything
|
|
on the safe stack.
|
|
|
|
SafeStack is a part of the `Code-Pointer Integrity (CPI) Project
|
|
<https://dslab.epfl.ch/proj/cpi/>`_.
|
|
|
|
Performance
|
|
-----------
|
|
|
|
The performance overhead of the SafeStack instrumentation is less than 0.1% on
|
|
average across a variety of benchmarks (see the `Code-Pointer Integrity
|
|
<https://dslab.epfl.ch/pubs/cpi.pdf>`__ paper for details). This is mainly
|
|
because most small functions do not have any variables that require the unsafe
|
|
stack and, hence, do not need unsafe stack frames to be created. The cost of
|
|
creating unsafe stack frames for large functions is amortized by the cost of
|
|
executing the function.
|
|
|
|
In some cases, SafeStack actually improves the performance. Objects that end up
|
|
being moved to the unsafe stack are usually large arrays or variables that are
|
|
used through multiple stack frames. Moving such objects away from the safe
|
|
stack increases the locality of frequently accessed values on the stack, such
|
|
as register spills, return addresses, and small local variables.
|
|
|
|
Compatibility
|
|
-------------
|
|
|
|
Most programs, static libraries, or individual files can be compiled
|
|
with SafeStack as is. SafeStack requires basic runtime support, which, on most
|
|
platforms, is implemented as a compiler-rt library that is automatically linked
|
|
in 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 to
|
|
work with SafeStack. One example is mark-and-sweep garbage collection
|
|
implementations for C/C++ (e.g., Oilpan in chromium/blink), which must be
|
|
changed to look for the live pointers on both safe and unsafe stacks.
|
|
|
|
SafeStack supports linking statically modules that are compiled with and
|
|
without SafeStack. An executable compiled with SafeStack can load dynamic
|
|
libraries that are not compiled with SafeStack. At the moment, compiling
|
|
dynamic libraries with SafeStack is not supported.
|
|
|
|
Signal handlers that use ``sigaltstack()`` must not use the unsafe stack (see
|
|
``__attribute__((no_sanitize("safe-stack")))`` below).
|
|
|
|
Programs that use APIs from ``ucontext.h`` are not supported yet.
|
|
|
|
Security
|
|
--------
|
|
|
|
SafeStack protects return addresses, spilled registers and local variables that
|
|
are always accessed in a safe way by separating them in a dedicated safe stack
|
|
region. The safe stack is automatically protected against stack-based buffer
|
|
overflows, since it is disjoint from the unsafe stack in memory, and it itself
|
|
is always accessed in a safe way. In the current implementation, the safe stack
|
|
is protected against arbitrary memory write vulnerabilities though
|
|
randomization and information hiding: the safe stack is allocated at a random
|
|
address and the instrumentation ensures that no pointers to the safe stack are
|
|
ever stored outside of the safe stack itself (see limitations below).
|
|
|
|
Known security limitations
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
A complete protection against control-flow hijack attacks requires combining
|
|
SafeStack with another mechanism that enforces the integrity of code pointers
|
|
that are stored on the heap or the unsafe stack, such as `CPI
|
|
<https://dslab.epfl.ch/proj/cpi/>`_, or a forward-edge control flow integrity
|
|
mechanism that enforces correct calling conventions at indirect call sites,
|
|
such as `IFCC <https://research.google.com/pubs/archive/42808.pdf>`_ with arity
|
|
checks. Clang has control-flow integrity protection scheme for :doc:`C++ virtual
|
|
calls <ControlFlowIntegrity>`, but not non-virtual indirect calls. With
|
|
SafeStack alone, an attacker can overwrite a function pointer on the heap or
|
|
the unsafe stack and cause a program to call arbitrary location, which in turn
|
|
might enable stack pivoting and return-oriented programming.
|
|
|
|
In its current implementation, SafeStack provides precise protection against
|
|
stack-based buffer overflows, but protection against arbitrary memory write
|
|
vulnerabilities is probabilistic and relies on randomization and information
|
|
hiding. The randomization is currently based on system-enforced ASLR and shares
|
|
its known security limitations. The safe stack pointer hiding is not perfect
|
|
yet either: system library functions such as ``swapcontext``, exception
|
|
handling mechanisms, intrinsics such as ``__builtin_frame_address``, or
|
|
low-level bugs in runtime support could leak the safe stack pointer. In the
|
|
future, such leaks could be detected by static or dynamic analysis tools and
|
|
prevented by adjusting such functions to either encrypt the stack pointer when
|
|
storing it in the heap (as already done e.g., by ``setjmp``/``longjmp``
|
|
implementation in glibc), or store it in a safe region instead.
|
|
|
|
The `CPI paper <https://dslab.epfl.ch/pubs/cpi.pdf>`_ describes two alternative,
|
|
stronger safe stack protection mechanisms, that rely on software fault
|
|
isolation, or hardware segmentation (as available on x86-32 and some x86-64
|
|
CPUs).
|
|
|
|
At the moment, SafeStack assumes that the compiler's implementation is correct.
|
|
This has not been verified except through manual code inspection, and could
|
|
always regress in the future. It's therefore desirable to have a separate
|
|
static or dynamic binary verification tool that would check the correctness of
|
|
the SafeStack instrumentation in final binaries.
|
|
|
|
Usage
|
|
=====
|
|
|
|
To enable SafeStack, just pass ``-fsanitize=safe-stack`` flag to both compile
|
|
and 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 on
|
|
whether SafeStack is enabled. The macro ``__has_feature(safe_stack)`` can
|
|
be used for this purpose.
|
|
|
|
.. code-block:: c
|
|
|
|
#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 declaration
|
|
to specify that the safe stack instrumentation should not be applied to that
|
|
function, even if enabled globally (see ``-fsanitize=safe-stack`` flag). This
|
|
attribute may be required for functions that make assumptions about the
|
|
exact layout of their stack frames.
|
|
|
|
All local variables in functions with this attribute will be stored on the safe
|
|
stack. The safe stack remains unprotected against memory errors when accessing
|
|
these variables, so extra care must be taken to manually ensure that all such
|
|
accesses are safe. Furthermore, the addresses of such local variables should
|
|
never 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 current
|
|
thread.
|
|
|
|
``__builtin___get_unsafe_stack_bottom()``
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
This builtin function returns a pointer to the bottom of the unsafe stack of the
|
|
current thread.
|
|
|
|
``__builtin___get_unsafe_stack_top()``
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
This builtin function returns a pointer to the top of the unsafe stack of the
|
|
current thread.
|
|
|
|
``__builtin___get_unsafe_stack_start()``
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Deprecated: This builtin function is an alias for
|
|
``__builtin___get_unsafe_stack_bottom()``.
|
|
|
|
Design
|
|
======
|
|
|
|
Please refer to the `Code-Pointer Integrity <https://dslab.epfl.ch/proj/cpi/>`__
|
|
project page for more information about the design of the SafeStack and its
|
|
related technologies.
|
|
|
|
setjmp and exception handling
|
|
-----------------------------
|
|
|
|
The `OSDI'14 paper <https://dslab.epfl.ch/pubs/cpi.pdf>`_ mentions that
|
|
on Linux the instrumentation pass finds calls to setjmp or functions that
|
|
may throw an exception, and inserts required instrumentation at their call
|
|
sites. Specifically, the instrumentation pass saves the shadow stack pointer
|
|
on the safe stack before the call site, and restores it either after the
|
|
call to setjmp or after an exception has been caught. This is implemented
|
|
in the function ``SafeStack::createStackRestorePoints``.
|
|
|
|
Publications
|
|
------------
|
|
|
|
`Code-Pointer Integrity <https://dslab.epfl.ch/pubs/cpi.pdf>`__.
|
|
Volodymyr Kuznetsov, Laszlo Szekeres, Mathias Payer, George Candea, R. Sekar, Dawn Song.
|
|
USENIX Symposium on Operating Systems Design and Implementation
|
|
(`OSDI <https://www.usenix.org/conference/osdi14>`_), Broomfield, CO, October 2014
|