brintos

brintos / llvm-project-archived public Read only

0
0
Text · 13.1 KiB · 937956f Raw
287 lines · plain
1========2GWP-ASan3========4 5.. contents::6   :local:7   :depth: 28 9Introduction10============11 12GWP-ASan is a sampled allocator framework that assists in finding use-after-free13and heap-buffer-overflow bugs in production environments. It informally is a14recursive acronym, "**G**\WP-ASan **W**\ill **P**\rovide **A**\llocation15**SAN**\ity".16 17GWP-ASan is based on the classic18`Electric Fence Malloc Debugger <https://linux.die.net/man/3/efence>`_, with a19key adaptation. Notably, we only choose a very small percentage of allocations20to sample, and apply guard pages to these sampled allocations only. The sampling21is small enough to allow us to have very low performance overhead.22 23There is a small, tunable memory overhead that is fixed for the lifetime of the24process. This is approximately ~40KiB per process using the default settings,25depending on the average size of your allocations.26 27GWP-ASan vs. ASan28=================29 30Unlike `AddressSanitizer <https://clang.llvm.org/docs/AddressSanitizer.html>`_,31GWP-ASan does not induce a significant performance overhead. ASan often requires32the use of dedicated canaries to be viable in production environments, and as33such is often impractical. Moreover, ASan's runtime is not developed with34security considerations in mind, making compiled binaries more vulnerable to35exploits.36 37However, GWP-ASan is only capable of finding a subset of the memory issues38detected by ASan. Furthermore, GWP-ASan's bug detection capabilities are39only probabilistic. As such, we recommend using ASan over GWP-ASan in testing,40as well as anywhere else that guaranteed error detection is more valuable than41the 2x execution slowdown/binary size bloat. For the majority of production42environments, this impact is too high and security is indispensable, so GWP-ASan43proves extremely useful.44 45 46Design47======48 49**Please note:** The implementation of GWP-ASan is largely in-flux, and these50details are subject to change. There are currently other implementations of51GWP-ASan, such as the implementation featured in52`Chromium <https://cs.chromium.org/chromium/src/components/gwp_asan/>`_. The53long-term support goal is to ensure feature-parity where reasonable, and to54support compiler-rt as the reference implementation.55 56Allocator Support57-----------------58 59GWP-ASan is not a replacement for a traditional allocator. Instead, it works by60inserting stubs into a supporting allocator to redirect allocations to GWP-ASan61when they're chosen to be sampled. These stubs are generally implemented in the62implementation of ``malloc()``, ``free()`` and ``realloc()``. The stubs are63extremely small, which makes using GWP-ASan in most allocators fairly trivial.64The stubs follow the same general pattern (example ``malloc()`` pseudocode65below):66 67.. code:: cpp68 69  #ifdef INSTALL_GWP_ASAN_STUBS70    gwp_asan::GuardedPoolAllocator GWPASanAllocator;71  #endif72 73  void* YourAllocator::malloc(..) {74  #ifdef INSTALL_GWP_ASAN_STUBS75    if (GWPASanAllocator.shouldSample(..))76      return GWPASanAllocator.allocate(..);77  #endif78 79    // ... the rest of your allocator code here.80  }81 82Then, all the supporting allocator needs to do is compile with83``-DINSTALL_GWP_ASAN_STUBS`` and link against the GWP-ASan library! For84performance reasons, we strongly recommend static linkage of the GWP-ASan85library.86 87Guarded Allocation Pool88-----------------------89 90The core of GWP-ASan is the guarded allocation pool. Each sampled allocation is91backed using its own *guarded* slot, which may consist of one or more accessible92pages. Each guarded slot is surrounded by two *guard* pages, which are mapped as93inaccessible. The collection of all guarded slots makes up the *guarded94allocation pool*.95 96Buffer Underflow/Overflow Detection97-----------------------------------98 99We gain buffer-overflow and buffer-underflow detection through these guard100pages. When a memory access overruns the allocated buffer, it will touch the101inaccessible guard page, causing memory exception. This exception is caught and102handled by the internal crash handler. Because each allocation is recorded with103metadata about where (and by what thread) it was allocated and deallocated, we104can provide information that will help identify the root cause of the bug.105 106Allocations are randomly selected to be either left- or right-aligned to provide107equal detection of both underflows and overflows.108 109Use after Free Detection110------------------------111 112The guarded allocation pool also provides use-after-free detection. Whenever a113sampled allocation is deallocated, we map its guarded slot as inaccessible. Any114memory accesses after deallocation will thus trigger the crash handler, and we115can provide useful information about the source of the error.116 117Please note that the use-after-free detection for a sampled allocation is118transient. To keep memory overhead fixed while still detecting bugs, deallocated119slots are randomly reused to guard future allocations.120 121Usage122=====123 124GWP-ASan already ships by default in the125`Scudo Hardened Allocator <https://llvm.org/docs/ScudoHardenedAllocator.html>`_,126so building with ``-fsanitize=scudo`` is the quickest and easiest way to try out127GWP-ASan.128 129Options130-------131 132GWP-ASan's configuration is managed by the supporting allocator. We provide a133generic configuration management library that is used by Scudo. It allows134several aspects of GWP-ASan to be configured through the following methods:135 136- When the GWP-ASan library is compiled, by setting137  ``-DGWP_ASAN_DEFAULT_OPTIONS`` to the options string you want set by default.138  If you're building GWP-ASan as part of a compiler-rt/LLVM build, add it during139  cmake configure time (e.g. ``cmake ... -DGWP_ASAN_DEFAULT_OPTIONS="..."``). If140  you're building GWP-ASan outside of compiler-rt, simply ensure that you141  specify ``-DGWP_ASAN_DEFAULT_OPTIONS="..."`` when building142  ``optional/options_parser.cpp``).143 144- By defining a ``__gwp_asan_default_options`` function in one's program that145  returns the options string to be parsed. Said function must have the following146  prototype: ``extern "C" const char* __gwp_asan_default_options(void)``, with a147  default visibility. This will override the compile time define;148 149- Depending on allocator support (Scudo has support for this mechanism): Through150  an environment variable, containing the options string to be parsed. In Scudo,151  this is through `SCUDO_OPTIONS=GWP_ASAN_${OPTION_NAME}=${VALUE}` (e.g.152  `SCUDO_OPTIONS=GWP_ASAN_SampleRate=100`). Options defined this way will153  override any definition made through ``__gwp_asan_default_options``.154 155The options string follows a syntax similar to ASan, where distinct options156can be assigned in the same string, separated by colons.157 158For example, using the environment variable:159 160.. code:: console161 162  GWP_ASAN_OPTIONS="MaxSimultaneousAllocations=16:SampleRate=5000" ./a.out163 164Or using the function:165 166.. code:: cpp167 168  extern "C" const char *__gwp_asan_default_options() {169    return "MaxSimultaneousAllocations=16:SampleRate=5000";170  }171 172The following options are available:173 174+----------------------------+---------+--------------------------------------------------------------------------------+175| Option                     | Default | Description                                                                    |176+----------------------------+---------+--------------------------------------------------------------------------------+177| Enabled                    | true    | Is GWP-ASan enabled?                                                           |178+----------------------------+---------+--------------------------------------------------------------------------------+179| PerfectlyRightAlign        | false   | When allocations are right-aligned, should we perfectly align them up to the   |180|                            |         | page boundary? By default (false), we round up allocation size to the nearest  |181|                            |         | power of two (2, 4, 8, 16) up to a maximum of 16-byte alignment for            |182|                            |         | performance reasons. Setting this to true can find single byte                 |183|                            |         | buffer-overflows at the cost of performance, and may be incompatible with      |184|                            |         | some architectures.                                                            |185+----------------------------+---------+--------------------------------------------------------------------------------+186| MaxSimultaneousAllocations | 16      | Number of simultaneously-guarded allocations available in the pool.            |187+----------------------------+---------+--------------------------------------------------------------------------------+188| SampleRate                 | 5000    | The probability (1 / SampleRate) that a page is selected for GWP-ASan          |189|                            |         | sampling. Sample rates up to (2^31 - 1) are supported.                         |190+----------------------------+---------+--------------------------------------------------------------------------------+191| InstallSignalHandlers      | true    | Install GWP-ASan signal handlers for SIGSEGV during dynamic loading. This      |192|                            |         | allows better error reports by providing stack traces for allocation and       |193|                            |         | deallocation when reporting a memory error. GWP-ASan's signal handler will     |194|                            |         | forward the signal to any previously-installed handler, and user programs      |195|                            |         | that install further signal handlers should make sure they do the same. Note,  |196|                            |         | if the previously installed SIGSEGV handler is SIG_IGN, we terminate the       |197|                            |         | process after dumping the error report.                                        |198+----------------------------+---------+--------------------------------------------------------------------------------+199 200Example201-------202 203The below code has a use-after-free bug, where the ``string_view`` is created as204a reference to the temporary result of the ``string+`` operator. The205use-after-free occurs when ``sv`` is dereferenced on line 8.206 207.. code:: cpp208 209  1: #include <iostream>210  2: #include <string>211  3: #include <string_view>212  4:213  5: int main() {214  6:   std::string s = "Hellooooooooooooooo ";215  7:   std::string_view sv = s + "World\n";216  8:   std::cout << sv;217  9: }218 219Compiling this code with Scudo+GWP-ASan will probabilistically catch this bug220and provide us a detailed error report:221 222.. code:: console223 224  $ clang++ -fsanitize=scudo -g buggy_code.cpp225  $ for i in `seq 1 500`; do226      SCUDO_OPTIONS="GWP_ASAN_SampleRate=100" ./a.out > /dev/null;227    done228  |229  | *** GWP-ASan detected a memory error ***230  | Use after free at 0x7feccab26000 (0 bytes into a 41-byte allocation at 0x7feccab26000) by thread 31027 here:231  |   ...232  |   #9 ./a.out(_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St17basic_string_viewIS3_S4_E+0x45) [0x55585c0afa55]233  |   #10 ./a.out(main+0x9f) [0x55585c0af7cf]234  |   #11 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7fecc966952b]235  |   #12 ./a.out(_start+0x2a) [0x55585c0867ba]236  |237  | 0x7feccab26000 was deallocated by thread 31027 here:238  |   ...239  |   #7 ./a.out(main+0x83) [0x55585c0af7b3]240  |   #8 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7fecc966952b]241  |   #9 ./a.out(_start+0x2a) [0x55585c0867ba]242  |243  | 0x7feccab26000 was allocated by thread 31027 here:244  |   ...245  |   #12 ./a.out(main+0x57) [0x55585c0af787]246  |   #13 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7fecc966952b]247  |   #14 ./a.out(_start+0x2a) [0x55585c0867ba]248  |249  | *** End GWP-ASan report ***250  | Segmentation fault251 252To symbolize these stack traces, some care has to be taken. Scudo currently uses253GNU's ``backtrace_symbols()`` from ``<execinfo.h>`` to unwind. The unwinder254provides human-readable stack traces in ``function+offset`` form, rather than255the normal ``binary+offset`` form. In order to use addr2line or similar tools to256recover the exact line number, we must convert the ``function+offset`` to257``binary+offset``. A helper script is available at258``compiler-rt/lib/gwp_asan/scripts/symbolize.sh``. Using this script will259attempt to symbolize each possible line, falling back to the previous output if260anything fails. This results in the following output:261 262.. code:: console263 264 265  $ cat my_gwp_asan_error.txt | symbolize.sh266  |267  | *** GWP-ASan detected a memory error ***268  | Use after free at 0x7feccab26000 (0 bytes into a 41-byte allocation at 0x7feccab26000) by thread 31027 here:269  | ...270  | #9 /usr/lib/gcc/x86_64-linux-gnu/8.0.1/../../../../include/c++/8.0.1/string_view:547271  | #10 /tmp/buggy_code.cpp:8272  |273  | 0x7feccab26000 was deallocated by thread 31027 here:274  | ...275  | #7 /tmp/buggy_code.cpp:8276  | #8 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7fecc966952b]277  | #9 ./a.out(_start+0x2a) [0x55585c0867ba]278  |279  | 0x7feccab26000 was allocated by thread 31027 here:280  | ...281  | #12 /tmp/buggy_code.cpp:7282  | #13 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7fecc966952b]283  | #14 ./a.out(_start+0x2a) [0x55585c0867ba]284  |285  | *** End GWP-ASan report ***286  | Segmentation fault287