1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
|
.. _hardening:
===============
Hardening Modes
===============
.. contents::
:local:
.. _using-hardening-modes:
Using hardening modes
=====================
libc++ provides several hardening modes, where each mode enables a set of
assertions that prevent undefined behavior caused by violating preconditions of
the standard library. Different hardening modes make different trade-offs
between the amount of checking and runtime performance. The available hardening
modes are:
- **Unchecked mode/none**, which disables all hardening checks.
- **Fast mode**, which contains a set of security-critical checks that can be
done with relatively little overhead in constant time and are intended to be
used in production. We recommend most projects adopt this.
- **Extensive mode**, which contains all the checks from fast mode and some
additional checks for undefined behavior that incur relatively little overhead
but aren't security-critical. Production builds requiring a broader set of
checks than fast mode should consider enabling extensive mode. The additional
rigour impacts performance more than fast mode: we recommend benchmarking to
determine if that is acceptable for your program.
- **Debug mode**, which enables all the available checks in the library,
including heuristic checks that might have significant performance overhead as
well as internal library assertions. This mode should be used in
non-production environments (such as test suites, CI, or local development).
We don’t commit to a particular level of performance in this mode and it’s
*not* intended to be used in production.
.. note::
Enabling hardening has no impact on the ABI.
Notes for users
---------------
As a libc++ user, consult with your vendor to determine the level of hardening
enabled by default.
Users wishing for a different hardening level to their vendor default are able
to control the level by passing **one** of the following options to the compiler:
- ``-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE``
- ``-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST``
- ``-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE``
- ``-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG``
.. warning::
The exact numeric values of these macros are unspecified and users should not
rely on them (e.g. expect the values to be sorted in any way).
.. warning::
If you would prefer to override the hardening level on a per-translation-unit
basis, you must do so **before** including any headers to avoid `ODR issues`_.
.. _`ODR issues`: https://en.cppreference.com/w/cpp/language/definition#:~:text=is%20ill%2Dformed.-,One%20Definition%20Rule,-Only%20one%20definition
.. note::
Since the static and shared library components of libc++ are built by the
vendor, setting this macro will have no impact on the hardening mode for the
pre-built components. Most libc++ code is header-based, so a user-provided
value for ``_LIBCPP_HARDENING_MODE`` will be mostly respected.
Notes for vendors
-----------------
Vendors can set the default hardening mode by providing
``LIBCXX_HARDENING_MODE`` as a configuration option, with the possible values of
``none``, ``fast``, ``extensive`` and ``debug``. The default value is ``none``
which doesn't enable any hardening checks (this mode is sometimes called the
``unchecked`` mode).
This option controls both the hardening mode that the precompiled library is
built with and the default hardening mode that users will build with. If set to
``none``, the precompiled library will not contain any assertions, and user code
will default to building without assertions.
Vendors can also override the way the program is terminated when an assertion
fails by :ref:`providing a custom header <override-assertion-handler>`.
Assertion categories
====================
Inside the library, individual assertions are grouped into different
*categories*. Each hardening mode enables a different set of assertion
categories; categories provide an additional layer of abstraction that makes it
easier to reason about the high-level semantics of a hardening mode.
.. note::
Users are not intended to interact with these categories directly -- the
categories are considered internal to the library and subject to change.
- ``valid-element-access`` -- checks that any attempts to access a container
element, whether through the container object or through an iterator, are
valid and do not attempt to go out of bounds or otherwise access
a non-existent element. This also includes operations that set up an imminent
invalid access (e.g. incrementing an end iterator). For iterator checks to
work, bounded iterators must be enabled in the ABI. Types like
``std::optional`` and ``std::function`` are considered containers (with at
most one element) for the purposes of this check.
- ``valid-input-range`` -- checks that ranges (whether expressed as an iterator
pair, an iterator and a sentinel, an iterator and a count, or
a ``std::range``) given as input to library functions are valid:
- the sentinel is reachable from the begin iterator;
- TODO(hardening): both iterators refer to the same container.
("input" here refers to "an input given to an algorithm", not to an iterator
category)
Violating assertions in this category leads to an out-of-bounds access.
- ``non-null`` -- checks that the pointer being dereferenced is not null. On
most modern platforms, the zero address does not refer to an actual location
in memory, so a null pointer dereference would not compromise the memory
security of a program (however, it is still undefined behavior that can result
in strange errors due to compiler optimizations).
- ``non-overlapping-ranges`` -- for functions that take several ranges as
arguments, checks that those ranges do not overlap.
- ``valid-deallocation`` -- checks that an attempt to deallocate memory is valid
(e.g. the given object was allocated by the given allocator). Violating this
category typically results in a memory leak.
- ``valid-external-api-call`` -- checks that a call to an external API doesn't
fail in an unexpected manner. This includes triggering documented cases of
undefined behavior in an external library (like attempting to unlock an
unlocked mutex in pthreads). Any API external to the library falls under this
category (from system calls to compiler intrinsics). We generally don't expect
these failures to compromise memory safety or otherwise create an immediate
security issue.
- ``compatible-allocator`` -- checks any operations that exchange nodes between
containers to make sure the containers have compatible allocators.
- ``argument-within-domain`` -- checks that the given argument is within the
domain of valid arguments for the function. Violating this typically produces
an incorrect result (e.g. ``std::clamp`` returns the original value without
clamping it due to incorrect functors) or puts an object into an invalid state
(e.g. a string view where only a subset of elements is accessible). This
category is for assertions violating which doesn't cause any immediate issues
in the library -- whatever the consequences are, they will happen in the user
code.
- ``pedantic`` -- checks preconditions that are imposed by the Standard, but
violating which happens to be benign in libc++.
- ``semantic-requirement`` -- checks that the given argument satisfies the
semantic requirements imposed by the Standard. Typically, there is no simple
way to completely prove that a semantic requirement is satisfied; thus, this
would often be a heuristic check and it might be quite expensive.
- ``internal`` -- checks that internal invariants of the library hold. These
assertions don't depend on user input.
- ``uncategorized`` -- for assertions that haven't been properly classified yet.
This category is an escape hatch used for some existing assertions in the
library; all new code should have its assertions properly classified.
Mapping between the hardening modes and the assertion categories
================================================================
.. list-table::
:header-rows: 1
:widths: auto
* - Category name
- ``fast``
- ``extensive``
- ``debug``
* - ``valid-element-access``
- ✅
- ✅
- ✅
* - ``valid-input-range``
- ✅
- ✅
- ✅
* - ``non-null``
- ❌
- ✅
- ✅
* - ``non-overlapping-ranges``
- ❌
- ✅
- ✅
* - ``valid-deallocation``
- ❌
- ✅
- ✅
* - ``valid-external-api-call``
- ❌
- ✅
- ✅
* - ``compatible-allocator``
- ❌
- ✅
- ✅
* - ``argument-within-domain``
- ❌
- ✅
- ✅
* - ``pedantic``
- ❌
- ✅
- ✅
* - ``semantic-requirement``
- ❌
- ❌
- ✅
* - ``internal``
- ❌
- ❌
- ✅
* - ``uncategorized``
- ❌
- ✅
- ✅
.. note::
At the moment, each subsequent hardening mode is a strict superset of the
previous one (in other words, each subsequent mode only enables additional
assertion categories without disabling any), but this won't necessarily be
true for any hardening modes that might be added in the future.
.. note::
The categories enabled by each mode are subject to change and users should not
rely on the precise assertions enabled by a mode at a given point in time.
However, the library does guarantee to keep the hardening modes stable and
to fulfill the semantics documented here.
Hardening assertion failure
===========================
In production modes (``fast`` and ``extensive``), a hardening assertion failure
immediately ``_traps <https://llvm.org/docs/LangRef.html#llvm-trap-intrinsic>``
the program. This is the safest approach that also minimizes the code size
penalty as the failure handler maps to a single instruction. The downside is
that the failure provides no additional details other than the stack trace
(which might also be affected by optimizations).
TODO(hardening): describe ``__builtin_verbose_trap`` once we can use it.
In the ``debug`` mode, an assertion failure terminates the program in an
unspecified manner and also outputs the associated error message to the error
output. This is less secure and increases the size of the binary (among other
things, it has to store the error message strings) but makes the failure easier
to debug. It also allows testing the error messages in our test suite.
.. _override-assertion-handler:
Overriding the assertion failure handler
----------------------------------------
Vendors can override the default assertion handler mechanism by following these
steps:
- create a header file that provides a definition of a macro called
``_LIBCPP_ASSERTION_HANDLER``. The macro will be invoked when a hardening
assertion fails, with a single parameter containing a null-terminated string
with the error message.
- when configuring the library, provide the path to custom header (relative to
the root of the repository) via the CMake variable
``LIBCXX_ASSERTION_HANDLER_FILE``.
Note that almost all libc++ headers include the assertion handler header which
means it should not include anything non-trivial from the standard library to
avoid creating circular dependencies.
There is no existing mechanism for users to override the assertion handler
because the ability to do the override other than at configure-time carries an
unavoidable code size penalty that would otherwise be imposed on all users,
whether they require such customization or not. Instead, we let vendors decide
what's right on their platform for their users -- a vendor who wishes to provide
this capability is free to do so, e.g. by declaring the assertion handler as an
overridable function.
ABI
===
Setting a hardening mode does **not** affect the ABI. Each mode uses the subset
of checks available in the current ABI configuration which is determined by the
platform.
It is important to stress that whether a particular check is enabled depends on
the combination of the selected hardening mode and the hardening-related ABI
options. Some checks require changing the ABI from the "default" to store
additional information in the library classes -- e.g. checking whether an
iterator is valid upon dereference generally requires storing data about bounds
inside the iterator object. Using ``std::span`` as an example, setting the
hardening mode to ``fast`` will always enable the ``valid-element-access``
checks when accessing elements via a ``std::span`` object, but whether
dereferencing a ``std::span`` iterator does the equivalent check depends on the
ABI configuration.
ABI options
-----------
Vendors can use some ABI options at CMake configuration time (when building libc++
itself) to enable additional hardening checks. This is done by passing these
macros as ``-DLIBCXX_ABI_DEFINES="_LIBCPP_ABI_FOO;_LIBCPP_ABI_BAR;etc"`` at
CMake configuration time. The available options are:
- ``_LIBCPP_ABI_BOUNDED_ITERATORS`` -- changes the iterator type of select
containers (see below) to a bounded iterator that keeps track of whether it's
within the bounds of the original container and asserts valid bounds on every
dereference.
ABI impact: changes the iterator type of the relevant containers.
Supported containers:
- ``span``;
- ``string_view``.
- ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING`` -- changes the iterator type of
``basic_string`` to a bounded iterator that keeps track of whether it's within
the bounds of the original container and asserts it on every dereference and
when performing iterator arithmetics.
ABI impact: changes the iterator type of ``basic_string`` and its
specializations, such as ``string`` and ``wstring``.
- ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR`` -- changes the iterator type of
``vector`` to a bounded iterator that keeps track of whether it's within the
bounds of the original container and asserts it on every dereference and when
performing iterator arithmetics. Note: this doesn't yet affect
``vector<bool>``.
ABI impact: changes the iterator type of ``vector`` (except ``vector<bool>``).
- ``_LIBCPP_ABI_BOUNDED_UNIQUE_PTR`` -- tracks the bounds of the array stored inside
a ``std::unique_ptr<T[]>``, allowing it to trap when accessed out-of-bounds. This
requires the ``std::unique_ptr`` to be created using an API like ``std::make_unique``
or ``std::make_unique_for_overwrite``, otherwise the bounds information is not available
to the library.
ABI impact: changes the layout of ``std::unique_ptr<T[]>``, and the representation
of a few library types that use ``std::unique_ptr`` internally, such as
the unordered containers.
- ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY`` -- changes the iterator type of ``std::array`` to a
bounded iterator that keeps track of whether it's within the bounds of the container and asserts it
on every dereference and when performing iterator arithmetic.
ABI impact: changes the iterator type of ``std::array``, its size and its layout.
ABI tags
--------
We use ABI tags to allow translation units built with different hardening modes
to interact with each other without causing ODR violations. Knowing how
hardening modes are encoded into the ABI tags might be useful to examine
a binary and determine whether it was built with hardening enabled.
.. warning::
We don't commit to the encoding scheme used by the ABI tags being stable
between different releases of libc++. The tags themselves are never stable, by
design -- new releases increase the version number. The following describes
the state of the latest release and is for informational purposes only.
The first character of an ABI tag encodes the hardening mode:
- ``f`` -- [f]ast mode;
- ``s`` -- extensive ("[s]afe") mode;
- ``d`` -- [d]ebug mode;
- ``n`` -- [n]one mode.
Hardened containers status
==========================
.. list-table::
:header-rows: 1
:widths: auto
* - Name
- Member functions
- Iterators (ABI-dependent)
* - ``span``
- ✅
- ✅
* - ``string_view``
- ✅
- ✅
* - ``array``
- ✅
- ❌
* - ``vector``
- ✅
- ✅ (see note)
* - ``string``
- ✅
- ✅ (see note)
* - ``list``
- ✅
- ❌
* - ``forward_list``
- ✅
- ❌
* - ``deque``
- ✅
- ❌
* - ``map``
- ❌
- ❌
* - ``set``
- ❌
- ❌
* - ``multimap``
- ❌
- ❌
* - ``multiset``
- ❌
- ❌
* - ``unordered_map``
- Partial
- Partial
* - ``unordered_set``
- Partial
- Partial
* - ``unordered_multimap``
- Partial
- Partial
* - ``unordered_multiset``
- Partial
- Partial
* - ``mdspan``
- ✅
- ❌
* - ``optional``
- ✅
- N/A
* - ``function``
- ❌
- N/A
* - ``variant``
- N/A
- N/A
* - ``any``
- N/A
- N/A
* - ``expected``
- ✅
- N/A
* - ``valarray``
- Partial
- N/A
* - ``bitset``
- ✅
- N/A
Note: for ``vector`` and ``string``, the iterator does not check for
invalidation (accesses made via an invalidated iterator still lead to undefined
behavior)
Note: ``vector<bool>`` iterator is not currently hardened.
Testing
=======
Please see :ref:`Testing documentation <testing-hardening-assertions>`.
Further reading
===============
- `Hardening RFC <https://discourse.llvm.org/t/rfc-hardening-in-libc/73925>`_:
contains some of the design rationale.
|