Skip to content

Commit 075384e

Browse files
committed
[libc++][hardening] Finish documenting hardening.
1 parent dd82fd4 commit 075384e

File tree

2 files changed

+299
-11
lines changed

2 files changed

+299
-11
lines changed

libcxx/docs/Hardening.rst

Lines changed: 298 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.. _hardening-modes:
1+
.. _hardening:
22

33
===============
44
Hardening Modes
@@ -29,8 +29,11 @@ modes are:
2929
rigour impacts performance more than fast mode: we recommend benchmarking to
3030
determine if that is acceptable for your program.
3131
- **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.
3437

3538
.. note::
3639

@@ -72,17 +75,302 @@ to control the level by passing **one** of the following options to the compiler
7275
Notes for vendors
7376
-----------------
7477

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).
7983

8084
This option controls both the hardening mode that the precompiled library is
8185
built with and the default hardening mode that users will build with. If set to
8286
``none``, the precompiled library will not contain any assertions, and user code
8387
will default to building without assertions.
8488

85-
Iterator bounds checking
86-
------------------------
89+
Vendors can also override the termination handler by :ref:`providing a custom
90+
header <override-assertion-handler>`.
8791

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>`

libcxx/docs/ReleaseNotes/18.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ and C++26 features.
4040

4141
New hardened modes for the library have been added, replacing the legacy debug mode that was
4242
removed in the LLVM 17 release. Unlike the legacy debug mode, some of these hardening modes are
43-
also intended to be used in production. See :ref:`hardening-modes` for more details.
43+
also intended to be used in production. See :ref:`hardening` for more details.
4444

4545
Work on the ranges support has progressed. See
4646
:ref:`ranges-status` for the current status.

0 commit comments

Comments
 (0)