mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-16 20:26:35 +00:00
481 lines
17 KiB
ReStructuredText
481 lines
17 KiB
ReStructuredText
.. _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://clang.llvm.org/docs/LanguageExtensions.html#builtin-verbose-trap>``
|
||
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).
|
||
|
||
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.
|