Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 1 | ======================== |
| 2 | Scudo Hardened Allocator |
| 3 | ======================== |
| 4 | |
| 5 | .. contents:: |
| 6 | :local: |
| 7 | :depth: 1 |
| 8 | |
| 9 | Introduction |
| 10 | ============ |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 11 | |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 12 | The Scudo Hardened Allocator is a user-mode allocator based on LLVM Sanitizer's |
| 13 | CombinedAllocator, which aims at providing additional mitigations against heap |
| 14 | based vulnerabilities, while maintaining good performance. |
| 15 | |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 16 | Currently, the allocator supports (was tested on) the following architectures: |
| 17 | |
| 18 | - i386 (& i686) (32-bit); |
| 19 | - x86_64 (64-bit); |
| 20 | - armhf (32-bit); |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 21 | - AArch64 (64-bit); |
| 22 | - MIPS (32-bit & 64-bit). |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 23 | |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 24 | The name "Scudo" has been retained from the initial implementation (Escudo |
| 25 | meaning Shield in Spanish and Portuguese). |
| 26 | |
| 27 | Design |
| 28 | ====== |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 29 | |
Kostya Kortchinsky | 014bd60 | 2018-01-04 18:31:22 +0000 | [diff] [blame] | 30 | Allocator |
| 31 | --------- |
| 32 | Scudo can be considered a Frontend to the Sanitizers' common allocator (later |
| 33 | referenced as the Backend). It is split between a Primary allocator, fast and |
| 34 | efficient, that services smaller allocation sizes, and a Secondary allocator |
| 35 | that services larger allocation sizes and is backed by the operating system |
| 36 | memory mapping primitives. |
| 37 | |
| 38 | Scudo was designed with security in mind, but aims at striking a good balance |
| 39 | between security and performance. It is highly tunable and configurable. |
| 40 | |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 41 | Chunk Header |
| 42 | ------------ |
| 43 | Every chunk of heap memory will be preceded by a chunk header. This has two |
| 44 | purposes, the first one being to store various information about the chunk, |
| 45 | the second one being to detect potential heap overflows. In order to achieve |
Kostya Kortchinsky | 014bd60 | 2018-01-04 18:31:22 +0000 | [diff] [blame] | 46 | this, the header will be checksummed, involving the pointer to the chunk itself |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 47 | and a global secret. Any corruption of the header will be detected when said |
| 48 | header is accessed, and the process terminated. |
| 49 | |
| 50 | The following information is stored in the header: |
| 51 | |
| 52 | - the 16-bit checksum; |
Kostya Kortchinsky | 014bd60 | 2018-01-04 18:31:22 +0000 | [diff] [blame] | 53 | - the class ID for that chunk, which is the "bucket" where the chunk resides |
| 54 | for Primary backed allocations, or 0 for Secondary backed allocations; |
| 55 | - the size (Primary) or unused bytes amount (Secondary) for that chunk, which is |
| 56 | necessary for computing the size of the chunk; |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 57 | - the state of the chunk (available, allocated or quarantined); |
| 58 | - the allocation type (malloc, new, new[] or memalign), to detect potential |
| 59 | mismatches in the allocation APIs used; |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 60 | - the offset of the chunk, which is the distance in bytes from the beginning of |
Kostya Kortchinsky | 014bd60 | 2018-01-04 18:31:22 +0000 | [diff] [blame] | 61 | the returned chunk to the beginning of the Backend allocation; |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 62 | |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 63 | This header fits within 8 bytes, on all platforms supported. |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 64 | |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 65 | The checksum is computed as a CRC32 (made faster with hardware support) |
| 66 | of the global secret, the chunk pointer itself, and the 8 bytes of header with |
Kostya Kortchinsky | 014bd60 | 2018-01-04 18:31:22 +0000 | [diff] [blame] | 67 | the checksum field zeroed out. It is not intended to be cryptographically |
| 68 | strong. |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 69 | |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 70 | The header is atomically loaded and stored to prevent races. This is important |
| 71 | as two consecutive chunks could belong to different threads. We also want to |
| 72 | avoid any type of double fetches of information located in the header, and use |
| 73 | local copies of the header for this purpose. |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 74 | |
| 75 | Delayed Freelist |
| 76 | ----------------- |
Kostya Kortchinsky | 014bd60 | 2018-01-04 18:31:22 +0000 | [diff] [blame] | 77 | A delayed freelist allows us to not return a chunk directly to the Backend, but |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 78 | to keep it aside for a while. Once a criterion is met, the delayed freelist is |
Kostya Kortchinsky | 014bd60 | 2018-01-04 18:31:22 +0000 | [diff] [blame] | 79 | emptied, and the quarantined chunks are returned to the Backend. This helps |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 80 | mitigate use-after-free vulnerabilities by reducing the determinism of the |
| 81 | allocation and deallocation patterns. |
| 82 | |
| 83 | This feature is using the Sanitizer's Quarantine as its base, and the amount of |
| 84 | memory that it can hold is configurable by the user (see the Options section |
| 85 | below). |
| 86 | |
| 87 | Randomness |
| 88 | ---------- |
| 89 | It is important for the allocator to not make use of fixed addresses. We use |
| 90 | the dynamic base option for the SizeClassAllocator, allowing us to benefit |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 91 | from the randomness of the system memory mapping functions. |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 92 | |
| 93 | Usage |
| 94 | ===== |
| 95 | |
| 96 | Library |
| 97 | ------- |
| 98 | The allocator static library can be built from the LLVM build tree thanks to |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 99 | the ``scudo`` CMake rule. The associated tests can be exercised thanks to the |
| 100 | ``check-scudo`` CMake rule. |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 101 | |
| 102 | Linking the static library to your project can require the use of the |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 103 | ``whole-archive`` linker flag (or equivalent), depending on your linker. |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 104 | Additional flags might also be necessary. |
| 105 | |
| 106 | Your linked binary should now make use of the Scudo allocation and deallocation |
| 107 | functions. |
| 108 | |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 109 | You may also build Scudo like this: |
| 110 | |
Kostya Kortchinsky | db26686 | 2017-08-29 19:54:19 +0000 | [diff] [blame] | 111 | .. code:: |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 112 | |
| 113 | cd $LLVM/projects/compiler-rt/lib |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 114 | clang++ -fPIC -std=c++11 -msse4.2 -O2 -I. scudo/*.cpp \ |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 115 | $(\ls sanitizer_common/*.{cc,S} | grep -v "sanitizer_termination\|sanitizer_common_nolibc\|sancov_\|sanitizer_unwind\|sanitizer_symbol") \ |
| 116 | -shared -o libscudo.so -pthread |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 117 | |
| 118 | and then use it with existing binaries as follows: |
| 119 | |
Kostya Kortchinsky | db26686 | 2017-08-29 19:54:19 +0000 | [diff] [blame] | 120 | .. code:: |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 121 | |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 122 | LD_PRELOAD=`pwd`/libscudo.so ./a.out |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 123 | |
Kostya Kortchinsky | 014bd60 | 2018-01-04 18:31:22 +0000 | [diff] [blame] | 124 | Clang |
| 125 | ----- |
| 126 | With a recent version of Clang (post rL317337), the allocator can be linked with |
| 127 | a binary at compilation using the ``-fsanitize=scudo`` command-line argument, if |
| 128 | the target platform is supported. Currently, the only other Sanitizer Scudo is |
| 129 | compatible with is UBSan (eg: ``-fsanitize=scudo,undefined``). Compiling with |
| 130 | Scudo will also enforce PIE for the output binary. |
| 131 | |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 132 | Options |
| 133 | ------- |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 134 | Several aspects of the allocator can be configured on a per process basis |
| 135 | through the following ways: |
| 136 | |
| 137 | - at compile time, by defining ``SCUDO_DEFAULT_OPTIONS`` to the options string |
| 138 | you want set by default; |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 139 | |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 140 | - by defining a ``__scudo_default_options`` function in one's program that |
| 141 | returns the options string to be parsed. Said function must have the following |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 142 | prototype: ``extern "C" const char* __scudo_default_options(void)``, with a |
| 143 | default visibility. This will override the compile time define; |
Kostya Serebryany | 2ca02f6 | 2016-08-02 22:25:38 +0000 | [diff] [blame] | 144 | |
| 145 | - through the environment variable SCUDO_OPTIONS, containing the options string |
| 146 | to be parsed. Options defined this way will override any definition made |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 147 | through ``__scudo_default_options``. |
Kostya Serebryany | 2ca02f6 | 2016-08-02 22:25:38 +0000 | [diff] [blame] | 148 | |
| 149 | The options string follows a syntax similar to ASan, where distinct options |
| 150 | can be assigned in the same string, separated by colons. |
| 151 | |
| 152 | For example, using the environment variable: |
| 153 | |
Kostya Kortchinsky | db26686 | 2017-08-29 19:54:19 +0000 | [diff] [blame] | 154 | .. code:: |
Kostya Serebryany | 2ca02f6 | 2016-08-02 22:25:38 +0000 | [diff] [blame] | 155 | |
Kostya Kortchinsky | a135575 | 2017-08-29 19:42:50 +0000 | [diff] [blame] | 156 | SCUDO_OPTIONS="DeleteSizeMismatch=1:QuarantineSizeKb=64" ./a.out |
Kostya Serebryany | 2ca02f6 | 2016-08-02 22:25:38 +0000 | [diff] [blame] | 157 | |
| 158 | Or using the function: |
| 159 | |
Kostya Kortchinsky | a135575 | 2017-08-29 19:42:50 +0000 | [diff] [blame] | 160 | .. code:: cpp |
Kostya Serebryany | 2ca02f6 | 2016-08-02 22:25:38 +0000 | [diff] [blame] | 161 | |
| 162 | extern "C" const char *__scudo_default_options() { |
Kostya Kortchinsky | a135575 | 2017-08-29 19:42:50 +0000 | [diff] [blame] | 163 | return "DeleteSizeMismatch=1:QuarantineSizeKb=64"; |
Kostya Serebryany | 2ca02f6 | 2016-08-02 22:25:38 +0000 | [diff] [blame] | 164 | } |
| 165 | |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 166 | |
| 167 | The following options are available: |
| 168 | |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 169 | +-----------------------------+----------------+----------------+------------------------------------------------+ |
| 170 | | Option | 64-bit default | 32-bit default | Description | |
| 171 | +-----------------------------+----------------+----------------+------------------------------------------------+ |
Kostya Kortchinsky | a135575 | 2017-08-29 19:42:50 +0000 | [diff] [blame] | 172 | | QuarantineSizeKb | 256 | 64 | The size (in Kb) of quarantine used to delay | |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 173 | | | | | the actual deallocation of chunks. Lower value | |
| 174 | | | | | may reduce memory usage but decrease the | |
| 175 | | | | | effectiveness of the mitigation; a negative | |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 176 | | | | | value will fallback to the defaults. Setting | |
| 177 | | | | | *both* this and ThreadLocalQuarantineSizeKb to | |
| 178 | | | | | zero will disable the quarantine entirely. | |
Kostya Kortchinsky | a135575 | 2017-08-29 19:42:50 +0000 | [diff] [blame] | 179 | +-----------------------------+----------------+----------------+------------------------------------------------+ |
| 180 | | QuarantineChunksUpToSize | 2048 | 512 | Size (in bytes) up to which chunks can be | |
| 181 | | | | | quarantined. | |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 182 | +-----------------------------+----------------+----------------+------------------------------------------------+ |
| 183 | | ThreadLocalQuarantineSizeKb | 1024 | 256 | The size (in Kb) of per-thread cache use to | |
| 184 | | | | | offload the global quarantine. Lower value may | |
| 185 | | | | | reduce memory usage but might increase | |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 186 | | | | | contention on the global quarantine. Setting | |
| 187 | | | | | *both* this and QuarantineSizeKb to zero will | |
| 188 | | | | | disable the quarantine entirely. | |
Kostya Kortchinsky | 4795c08 | 2017-02-09 16:07:52 +0000 | [diff] [blame] | 189 | +-----------------------------+----------------+----------------+------------------------------------------------+ |
| 190 | | DeallocationTypeMismatch | true | true | Whether or not we report errors on | |
| 191 | | | | | malloc/delete, new/free, new/delete[], etc. | |
| 192 | +-----------------------------+----------------+----------------+------------------------------------------------+ |
| 193 | | DeleteSizeMismatch | true | true | Whether or not we report errors on mismatch | |
| 194 | | | | | between sizes of new and delete. | |
| 195 | +-----------------------------+----------------+----------------+------------------------------------------------+ |
| 196 | | ZeroContents | false | false | Whether or not we zero chunk contents on | |
| 197 | | | | | allocation and deallocation. | |
| 198 | +-----------------------------+----------------+----------------+------------------------------------------------+ |
Kostya Serebryany | 8d0ee94 | 2016-06-07 01:20:26 +0000 | [diff] [blame] | 199 | |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 200 | Allocator related common Sanitizer options can also be passed through Scudo |
Kostya Kortchinsky | 5a95f87 | 2018-05-18 17:02:35 +0000 | [diff] [blame] | 201 | options, such as ``allocator_may_return_null`` or ``abort_on_error``. A detailed |
| 202 | list including those can be found here: |
Kostya Serebryany | 1e3f45d | 2016-08-09 23:57:04 +0000 | [diff] [blame] | 203 | https://github.com/google/sanitizers/wiki/SanitizerCommonFlags. |