|
1 |
| -.. _hardening-modes: |
| 1 | +.. _hardening: |
2 | 2 |
|
3 | 3 | ===============
|
4 | 4 | Hardening Modes
|
@@ -29,8 +29,11 @@ modes are:
|
29 | 29 | rigour impacts performance more than fast mode: we recommend benchmarking to
|
30 | 30 | determine if that is acceptable for your program.
|
31 | 31 | - **Debug mode**, which enables all the available checks in the library,
|
32 |
| - including internal assertions, some of which might be very expensive. This |
33 |
| - mode is intended to be used for testing, not in production. |
| 32 | + including heuristic checks that might have significant performance overhead as |
| 33 | + well as internal library assertions. This mode should be used in |
| 34 | + non-production environments (such as test suites, CI, local development). We |
| 35 | + don’t commit to a particular level of performance in this mode and it’s *not* |
| 36 | + intended to be used in production. |
34 | 37 |
|
35 | 38 | .. note::
|
36 | 39 |
|
@@ -72,17 +75,302 @@ to control the level by passing **one** of the following options to the compiler
|
72 | 75 | Notes for vendors
|
73 | 76 | -----------------
|
74 | 77 |
|
75 |
| -Vendors can set the default hardening mode by providing ``LIBCXX_HARDENING_MODE`` |
76 |
| -as a configuration option, with the possible values of ``none``, ``fast``, |
77 |
| -``extensive`` and ``debug``. The default value is ``none`` which doesn't enable |
78 |
| -any hardening checks (this mode is sometimes called the ``unchecked`` mode). |
| 78 | +Vendors can set the default hardening mode by providing |
| 79 | +``LIBCXX_HARDENING_MODE`` as a configuration option, with the possible values of |
| 80 | +``none``, ``fast``, ``extensive`` and ``debug``. The default value is ``none`` |
| 81 | +which doesn't enable any hardening checks (this mode is sometimes called the |
| 82 | +``unchecked`` mode). |
79 | 83 |
|
80 | 84 | This option controls both the hardening mode that the precompiled library is
|
81 | 85 | built with and the default hardening mode that users will build with. If set to
|
82 | 86 | ``none``, the precompiled library will not contain any assertions, and user code
|
83 | 87 | will default to building without assertions.
|
84 | 88 |
|
85 |
| -Iterator bounds checking |
86 |
| ------------------------- |
| 89 | +Vendors can also override the termination handler by :ref:`providing a custom |
| 90 | +header <override-assertion-handler>`. |
87 | 91 |
|
88 |
| -TODO(hardening) |
| 92 | +Assertion categories |
| 93 | +==================== |
| 94 | + |
| 95 | +Inside the library, individual assertions are grouped into different |
| 96 | +_categories_. Each hardening mode enables a different set of assertion |
| 97 | +categories; categories provide an additional layer of abstraction that makes it |
| 98 | +easier to reason about the high-level semantics of a hardening mode. |
| 99 | + |
| 100 | +- ``valid-element-access`` -- checks that any attempts to access a container |
| 101 | + element, whether through the container object or through an iterator, are |
| 102 | + valid and do not attempt to go out of bounds or otherwise access |
| 103 | + a non-existent element. For iterator checks to work, bounded iterators must be |
| 104 | + enabled in the ABI. Types like ``optional`` and ``function`` are considered |
| 105 | + one-element containers for the purposes of this check. |
| 106 | + |
| 107 | +- ``valid-input-range`` -- checks that ranges (whether expressed as an iterator |
| 108 | + pair, an iterator and a sentinel, an iterator and a count, or |
| 109 | + a ``std::range``) given as input to library functions are valid: |
| 110 | + - the sentinel is reachable from the begin iterator; |
| 111 | + - TODO(hardening): both iterators refer to the same container. |
| 112 | + |
| 113 | + ("input" here refers to "an input given to an algorithm", not to an iterator |
| 114 | + category) |
| 115 | + |
| 116 | + Violating assertions in this category leads to an out-of-bounds access. |
| 117 | + |
| 118 | +- ``non-null`` -- checks that the pointer being dereferenced is not null. On |
| 119 | + most modern platforms zero address does not refer to an actual location in |
| 120 | + memory, so a null pointer dereference would not compromize the memory security |
| 121 | + of a program (however, it is still undefined behavior that can result in |
| 122 | + strange errors due to compiler optimizations). |
| 123 | + |
| 124 | +- ``non-overlapping-ranges`` -- for functions that take several ranges as |
| 125 | + arguments, checks that the given ranges do not overlap. |
| 126 | + |
| 127 | +- ``valid-deallocation`` -- checks that an attempt to deallocate memory is valid |
| 128 | + (e.g. the given object was allocated by the given allocator). Violating this |
| 129 | + category typically results in a memory leak. |
| 130 | + |
| 131 | +- ``valid-external-api-call`` -- checks that a call to an external API doesn't |
| 132 | + fail in an unexpected manner. This includes triggering documented cases of |
| 133 | + undefined behavior in an external library (like attempting to unlock an |
| 134 | + unlocked mutex in pthreads). Any API external to the library falls under this |
| 135 | + category (from system calls to compiler intrinsics). We generally don't expect |
| 136 | + these failures to compromize memory safety or otherwise create an immediate |
| 137 | + security issue. |
| 138 | + |
| 139 | +- ``compatible-allocator`` -- checks any operations that exchange nodes between |
| 140 | + containers to make sure the containers have compatible allocators. |
| 141 | + |
| 142 | +- ``argument-within-domain`` -- checks that the given argument is within the |
| 143 | + domain of valid arguments for the function. Violating this typically produces |
| 144 | + an incorrect result (e.g. the clamp algorithm returns the original value |
| 145 | + without clamping it due to incorrect functors) or puts an object into an |
| 146 | + invalid state (e.g. a string view where only a subset of elements is possible |
| 147 | + to access). This category is for assertions violating which doesn't cause any |
| 148 | + immediate issues in the library -- whatever the consequences are, they will |
| 149 | + happen in the user code. |
| 150 | + |
| 151 | +- ``pedantic`` -- checks prerequisites that are imposed by the Standard, but |
| 152 | + violating which happens to be benign in our implementation. |
| 153 | + |
| 154 | +- ``semantic-requirement`` -- checks that the given argument satisfies the |
| 155 | + semantic requirements imposed by the Standard. Typically, there is no simple |
| 156 | + way to completely prove that a semantic requirement is satisfied; thus, this |
| 157 | + would often be a heuristic check and it might be quite expensive. |
| 158 | + |
| 159 | +- ``internal`` -- checks that internal invariants of the library hold. These |
| 160 | + assertions don't depend on user input. |
| 161 | + |
| 162 | +- ``uncategorized`` -- for assertions that haven't been properly classified yet. |
| 163 | + This is an escape hatch used for some existing assertions in the library; all |
| 164 | + new code should have its assertions properly classified. |
| 165 | + |
| 166 | +Mapping between the hardening modes and the assertion categories |
| 167 | +================================================================ |
| 168 | + |
| 169 | +.. list-table:: |
| 170 | + :header-rows: 1 |
| 171 | + :widths: auto |
| 172 | + |
| 173 | + * - |
| 174 | + - ``fast`` |
| 175 | + - ``extensive`` |
| 176 | + - ``debug`` |
| 177 | + * - ``valid-element-access`` |
| 178 | + - ✅ |
| 179 | + - ✅ |
| 180 | + - ✅ |
| 181 | + * - ``valid-input-range`` |
| 182 | + - ✅ |
| 183 | + - ✅ |
| 184 | + - ✅ |
| 185 | + * - ``non-null`` |
| 186 | + - ❌ |
| 187 | + - ✅ |
| 188 | + - ✅ |
| 189 | + * - ``non-overlapping-ranges`` |
| 190 | + - ❌ |
| 191 | + - ✅ |
| 192 | + - ✅ |
| 193 | + * - ``valid-deallocation`` |
| 194 | + - ❌ |
| 195 | + - ✅ |
| 196 | + - ✅ |
| 197 | + * - ``valid-external-api-call`` |
| 198 | + - ❌ |
| 199 | + - ✅ |
| 200 | + - ✅ |
| 201 | + * - ``compatible-allocator`` |
| 202 | + - ❌ |
| 203 | + - ✅ |
| 204 | + - ✅ |
| 205 | + * - ``argument-within-domain`` |
| 206 | + - ❌ |
| 207 | + - ✅ |
| 208 | + - ✅ |
| 209 | + * - ``pedantic`` |
| 210 | + - ❌ |
| 211 | + - ✅ |
| 212 | + - ✅ |
| 213 | + * - ``semantic-requirement`` |
| 214 | + - ❌ |
| 215 | + - ❌ |
| 216 | + - ✅ |
| 217 | + * - ``internal`` |
| 218 | + - ❌ |
| 219 | + - ❌ |
| 220 | + - ✅ |
| 221 | + * - ``uncategorized`` |
| 222 | + - ❌ |
| 223 | + - ✅ |
| 224 | + - ✅ |
| 225 | + |
| 226 | +.. note:: |
| 227 | + |
| 228 | + At the moment, each subsequent hardening mode is a strict superset of the |
| 229 | + previous one (in other words, each subsequent mode only enables additional |
| 230 | + assertion categories without disabling any), but this won't necessarily be |
| 231 | + true for any hardening modes that might potentially be added in the future. |
| 232 | + |
| 233 | +Hardening assertion failure |
| 234 | +=========================== |
| 235 | + |
| 236 | +In production modes (``fast`` and ``extensive``), a hardening assertion failure |
| 237 | +immediately traps the program. This is the safest approach that also minimizes |
| 238 | +the code size penalty as the failure handler maps to a single instruction. The |
| 239 | +downside is that the failure provides no additional details other than the stack |
| 240 | +trace (which might also be affected by optimizations). |
| 241 | +TODO(hardening): describe ``__builtin_verbose_trap`` once we can use it. |
| 242 | + |
| 243 | +In the ``debug`` mode, an assertion failure terminates the program in an |
| 244 | +unspecified manner and also outputs the associated error message to the error |
| 245 | +output. This is less secure and increases the size of the binary (among other |
| 246 | +things, to store the error message strings) but makes the failure easier to |
| 247 | +debug. It also allows us to test the error messages in our test suite. |
| 248 | + |
| 249 | +.. _override-assertion-handler: |
| 250 | + |
| 251 | +Overriding the assertion failure handler |
| 252 | +---------------------------------------- |
| 253 | + |
| 254 | +Vendors can override the default termination handler mechanism by following |
| 255 | +these steps: |
| 256 | + |
| 257 | +- create a header file that provides a definition of a macro called |
| 258 | + ``_LIBCPP_ASSERTION_HANDLER``. The macro will be invoked when a hardening |
| 259 | + assertion fails, with a single parameter containing a null-terminated string |
| 260 | + with the error message. |
| 261 | +- when configuring the library, provide the path to custom header (relative to |
| 262 | + the root of the repository) via the CMake variable |
| 263 | + ``LIBCXX_ASSERTION_HANDLER_FILE``. |
| 264 | + |
| 265 | +ABI |
| 266 | +=== |
| 267 | + |
| 268 | +Setting a hardening mode does **not** affect the ABI. Each mode uses the subset |
| 269 | +of checks available in the current ABI configuration which is determined by the |
| 270 | +platform. |
| 271 | + |
| 272 | +It is important to stress that whether a particular check is enabled depends on |
| 273 | +the combination of the selected hardening mode and the hardening-related ABI |
| 274 | +options. Some checks require changing the ABI from the "default" to store |
| 275 | +additional information in the library classes -- e.g. checking whether an |
| 276 | +iterator is valid upon dereference generally requires storing data about bounds |
| 277 | +inside the iterator object. Using ``std::span`` as an example, setting the |
| 278 | +hardening mode to ``fast`` will always enable the ``valid-element-access`` |
| 279 | +checks when accessing elements via a ``span`` object, but whether dereferencing |
| 280 | +a ``span`` iterator does the equivalent check depends on the ABI configuration. |
| 281 | + |
| 282 | +ABI options |
| 283 | +----------- |
| 284 | + |
| 285 | +Vendors can use the following ABI options to enable additional hardening checks: |
| 286 | + |
| 287 | +- ``_LIBCPP_ABI_BOUNDED_ITERATORS`` -- changes the iterator type of select |
| 288 | + containers (see below) to a bounded iterator that keeps track of whether it's |
| 289 | + within the bounds of the original container and asserts it on every |
| 290 | + dereference. |
| 291 | + |
| 292 | + ABI impact: changes the iterator type of the relevant containers. |
| 293 | + |
| 294 | + Supported containers: |
| 295 | + - ``span``; |
| 296 | + - ``string_view``; |
| 297 | + - ``array``. |
| 298 | + |
| 299 | +ABI tags |
| 300 | +-------- |
| 301 | + |
| 302 | +We use ABI tags to allow translation units built with different hardening modes |
| 303 | +to interact with each other without causing ODR violations. Knowing how |
| 304 | +hardening modes are encoded into the ABI tags might be useful to examine |
| 305 | +a binary and determine whether it was built with hardening enabled. |
| 306 | + |
| 307 | +.. warning:: |
| 308 | + We don't commit to the ABI tags being stable between different releases of |
| 309 | + libc++. The following describes the state of the latest release and is for |
| 310 | + informational purposes only. |
| 311 | + |
| 312 | +The first character of an ABI tag encodes the hardening mode: |
| 313 | +- ``f`` -- [f]ast mode; |
| 314 | +- ``s`` -- extensive ("[s]afe") mode; |
| 315 | +- ``d`` -- [d]ebug mode; |
| 316 | +- ``n`` -- [n]one mode. |
| 317 | + |
| 318 | +Hardened containers |
| 319 | +=================== |
| 320 | + |
| 321 | +.. list-table:: |
| 322 | + :header-rows: 1 |
| 323 | + :widths: auto |
| 324 | + |
| 325 | + * - Name |
| 326 | + - Member functions |
| 327 | + - Iterators (ABI-dependent) |
| 328 | + * - ``span`` |
| 329 | + - ✅ |
| 330 | + - ✅ |
| 331 | + * - ``string_view`` |
| 332 | + - ✅ |
| 333 | + - ✅ |
| 334 | + * - ``array`` |
| 335 | + - ✅ |
| 336 | + - ❌ |
| 337 | + * - ``vector`` |
| 338 | + - ✅ |
| 339 | + - ❌ |
| 340 | + * - ``string`` |
| 341 | + - ✅ |
| 342 | + - ❌ |
| 343 | + * - ``list`` |
| 344 | + - ✅ |
| 345 | + - ❌ |
| 346 | + * - ``forward_list`` |
| 347 | + - ✅ |
| 348 | + - ❌ |
| 349 | + * - ``deque`` |
| 350 | + - ✅ |
| 351 | + - ❌ |
| 352 | + * - ``mdspan`` |
| 353 | + - ✅ |
| 354 | + - ❌ |
| 355 | + * - ``optional`` |
| 356 | + - ✅ |
| 357 | + - N/A |
| 358 | + |
| 359 | +TODO(hardening): make this table exhaustive. |
| 360 | + |
| 361 | +Testing |
| 362 | +======= |
| 363 | + |
| 364 | +Each hardening assertion should be tested using death tests (via the |
| 365 | +``TEST_LIBCPP_ASSERT_FAILURE`` macro). Use the ``libcpp-hardening-mode`` Lit |
| 366 | +feature to make sure the assertion is enabled in (and only in) the intended |
| 367 | +modes. Note that error messages are only tested (matched) if the ``debug`` |
| 368 | +hardening mode is used. |
| 369 | + |
| 370 | +Further reading |
| 371 | +=============== |
| 372 | + |
| 373 | +- ``_Hardening RFC <https://discourse.llvm.org/t/rfc-hardening-in-libc/73925>``: |
| 374 | + contains some of the design rationale. |
| 375 | + |
| 376 | +:ref:`hardening mode <using-hardening-modes>` |
0 commit comments