Hardening Modes — libc++ documentation (original) (raw)

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:

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:

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.

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 providingLIBCXX_HARDENING_MODE as a configuration option, with the possible values ofnone, fast, extensive and debug. The default value is nonewhich doesn’t enable any hardening checks (this mode is sometimes called theunchecked 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 tonone, 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 providing a custom header.

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.

Mapping between the hardening modes and the assertion categories

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.

Overriding the assertion failure handler

Vendors can override the default assertion handler mechanism by following these steps:

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-accesschecks 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 the following ABI options to enable additional hardening checks:

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:

Hardened containers status

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 Testing documentation.

Further reading