Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++][hardening] Rework how the assertion handler can be overridden. #77883

Merged
merged 19 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions libcxx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ if (NOT "${LIBCXX_HARDENING_MODE}" IN_LIST LIBCXX_SUPPORTED_HARDENING_MODES)
message(FATAL_ERROR
"Unsupported hardening mode: '${LIBCXX_HARDENING_MODE}'. Supported values are ${LIBCXX_SUPPORTED_HARDENING_MODES}.")
endif()
set(LIBCXX_ASSERTION_HANDLER_FILE
"${CMAKE_CURRENT_SOURCE_DIR}/vendor/llvm/default_assertion_handler.in"
CACHE STRING
"Specify the path to a header that contains a custom implementation of the
assertion handler that gets invoked when a hardening assertion fails. If
provided, this header will be included by the library, replacing the
default assertion handler.")
option(LIBCXX_ENABLE_RANDOM_DEVICE
"Whether to include support for std::random_device in the library. Disabling
this can be useful when building the library for platforms that don't have
Expand Down
46 changes: 46 additions & 0 deletions libcxx/docs/BuildingLibcxx.rst
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,15 @@ libc++ Feature Options
Use the specified GCC toolchain and standard library when building the native
stdlib benchmark tests.

.. option:: LIBCXX_ASSERTION_HANDLER_FILE:PATH

**Default**:: ``"${CMAKE_CURRENT_SOURCE_DIR}/vendor/llvm/default_assertion_handler.in"``

Specify the path to a header that contains a custom implementation of the
assertion handler that gets invoked when a hardening assertion fails. If
provided, this header will be included by the library, replacing the
default assertion handler.


libc++ ABI Feature Options
--------------------------
Expand Down Expand Up @@ -473,6 +482,43 @@ LLVM-specific options
others.


.. _assertion-handler:

Overriding the default assertion handler
==========================================

When the library wants to terminate due to an unforeseen condition (such as
a hardening assertion failure), the program is aborted through a special verbose
termination function. The library provides a default function that prints an
error message and calls ``std::abort()``. Note that this function is provided by
the static or shared library, so it is only available when deploying to
a platform where the compiled library is sufficiently recent. On older
platforms, the program will terminate in an unspecified unsuccessful manner, but
the quality of diagnostics won't be great.

However, vendors can also override that mechanism at CMake configuration time.
When a hardening assertion fails, the library invokes the
``_LIBCPP_ASSERTION_HANDLER`` macro. A vendor may provide a header that contains
a custom definition of this macro and specify the path to the header via the
``LIBCXX_ASSERTION_HANDLER_FILE`` CMake variable. If provided, this header will
be included by the library and replace the default implementation. The header
must not include any standard library headers (directly or transitively) because
doing so will almost always create a circular dependency. The
``_LIBCPP_ASSERTION_HANDLER(message)`` macro takes a single parameter that
contains an error message explaining the hardening failure and some details
about the source location that triggered it.

When a hardening assertion fails, it means that the program is about to invoke
library undefined behavior. For this reason, the custom assertion handler is
generally expected to terminate the program. If a custom assertion handler
decides to avoid doing so (e.g. it chooses to log and continue instead), it does
so at its own risk -- this approach should only be used in non-production builds
and with an understanding of potential consequences. Furthermore, the custom
assertion handler should not throw any exceptions as it may be invoked from
standard library functions that are marked ``noexcept`` (so throwing will result
in ``std::terminate`` being called).


Using Alternate ABI libraries
=============================

Expand Down
4 changes: 4 additions & 0 deletions libcxx/docs/ReleaseNotes/18.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ Deprecations and Removals
macro is provided to restore the previous behavior, and it will be supported in the LLVM 18 release only.
In LLVM 19 and beyond, ``_LIBCPP_ENABLE_NARROWING_CONVERSIONS_IN_VARIANT`` will not be honored anymore.

- The only supported way to customize the assertion handler that gets invoked when a hardening assertion fails
is now by setting the ``LIBCXX_ASSERTION_HANDLER_FILE`` CMake variable and providing a custom header. See
the documentation on overriding the default assertion handler for details.

- The ``_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED`` macro is not honored anymore in LLVM 18.
Please see the updated documentation about the hardening modes in libc++ and in particular the
``_LIBCPP_VERBOSE_ABORT`` macro for details.
Expand Down
64 changes: 0 additions & 64 deletions libcxx/docs/UsingLibcxx.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,70 +146,6 @@ IWYU, you should run the tool like so:
If you would prefer to not use that flag, then you can replace ``/path/to/include-what-you-use/share/libcxx.imp``
file with the libc++-provided ``libcxx.imp`` file.

.. _termination-handler:

Overriding the default termination handler
==========================================

When the library wants to terminate due to an unforeseen condition (such as a hardening assertion
failure), the program is aborted through a special verbose termination function. The library provides
a default function that prints an error message and calls ``std::abort()``. Note that this function is
provided by the static or shared library, so it is only available when deploying to a platform where
the compiled library is sufficiently recent. On older platforms, the program will terminate in an
unspecified unsuccessful manner, but the quality of diagnostics won't be great.

However, users can also override that mechanism at two different levels. First, the mechanism can be
overridden at compile time by defining the ``_LIBCPP_VERBOSE_ABORT(format, args...)`` variadic macro.
When that macro is defined, it will be called with a format string as the first argument, followed by
a series of arguments to format using printf-style formatting. Compile-time customization may be
useful to get precise control over code generation, however it is also inconvenient to use in
some cases. Indeed, compile-time customization of the verbose termination function requires that all
translation units be compiled with a consistent definition for ``_LIBCPP_VERBOSE_ABORT`` to avoid ODR
violations, which can add complexity in the build system of users.

Otherwise, if compile-time customization is not necessary, link-time customization of the handler is also
possible, similarly to how replacing ``operator new`` works. This mechanism trades off fine-grained control
over the call site where the termination is initiated in exchange for better ergonomics. Link-time
customization is done by simply defining the following function in exactly one translation unit of your
program:

.. code-block:: cpp

void __libcpp_verbose_abort(char const* format, ...)

This mechanism is similar to how one can replace the default definition of ``operator new``
and ``operator delete``. For example:

.. code-block:: cpp

// In HelloWorldHandler.cpp
#include <version> // must include any libc++ header before defining the function (C compatibility headers excluded)

void std::__libcpp_verbose_abort(char const* format, ...) {
std::va_list list;
va_start(list, format);
std::vfprintf(stderr, format, list);
va_end(list);

std::abort();
}

// In HelloWorld.cpp
#include <vector>

int main() {
std::vector<int> v;
int& x = v[0]; // Your termination function will be called here if hardening is enabled.
}

Also note that the verbose termination function should never return. Since assertions in libc++
catch undefined behavior, your code will proceed with undefined behavior if your function is called
and does return.

Furthermore, exceptions should not be thrown from the function. Indeed, many functions in the
library are ``noexcept``, and any exception thrown from the termination function will result
in ``std::terminate`` being called.

Libc++ Configuration Macros
===========================

Expand Down
10 changes: 9 additions & 1 deletion libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1020,9 +1020,11 @@ endforeach()

configure_file("__config_site.in" "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site" @ONLY)
configure_file("module.modulemap.in" "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap" @ONLY)
configure_file("${LIBCXX_ASSERTION_HANDLER_FILE}" "${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler" COPYONLY)

set(_all_includes "${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}/__config_site"
"${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap")
"${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap"
"${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler")
foreach(f ${files})
set(src "${CMAKE_CURRENT_SOURCE_DIR}/${f}")
set(dst "${LIBCXX_GENERATED_INCLUDE_DIR}/${f}")
Expand Down Expand Up @@ -1059,6 +1061,12 @@ if (LIBCXX_INSTALL_HEADERS)
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
COMPONENT cxx-headers)

# Install the generated __assertion_handler file to the generic include dir.
install(FILES "${LIBCXX_GENERATED_INCLUDE_DIR}/__assertion_handler"
DESTINATION "${LIBCXX_INSTALL_INCLUDE_DIR}"
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
COMPONENT cxx-headers)

# Install the generated modulemap file to the generic include dir.
install(FILES "${LIBCXX_GENERATED_INCLUDE_DIR}/module.modulemap"
DESTINATION "${LIBCXX_INSTALL_INCLUDE_DIR}"
Expand Down
6 changes: 3 additions & 3 deletions libcxx/include/__assert
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
#ifndef _LIBCPP___ASSERT
#define _LIBCPP___ASSERT

#include <__assertion_handler> // Note: this include is generated by CMake and is potentially vendor-provided.
#include <__config>
#include <__verbose_abort>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
Expand All @@ -20,8 +20,8 @@
#define _LIBCPP_ASSERT(expression, message) \
(__builtin_expect(static_cast<bool>(expression), 1) \
? (void)0 \
: _LIBCPP_VERBOSE_ABORT( \
"%s:%d: assertion %s failed: %s\n", __builtin_FILE(), __builtin_LINE(), #expression, message))
: _LIBCPP_ASSERTION_HANDLER(__FILE__ ":" _LIBCPP_TOSTRING(__LINE__) ": assertion " _LIBCPP_TOSTRING( \
expression) " failed: " message "\n"))

// TODO: __builtin_assume can currently inhibit optimizations. Until this has been fixed and we can add
// assumptions without a clear optimization intent, disable that to avoid worsening the code generation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
// This test ensures that enabling assertions with the legacy `_LIBCPP_ENABLE_ASSERTIONS` now enables the extensive
// hardening mode.

// `check_assertion.h` is only available starting from C++11 and requires Unix headers.
// UNSUPPORTED: c++03, !has-unix-headers
// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support.
// UNSUPPORTED: c++03, !has-unix-headers, no-localization
// The ability to set a custom abort message is required to compare the assertion message.
// XFAIL: availability-verbose_abort-missing
// Note that GCC doesn't support `-Wno-macro-redefined`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@

// This test ensures that we can override any hardening mode with the debug mode on a per-TU basis.

// `check_assertion.h` is only available starting from C++11.
// UNSUPPORTED: c++03
// `check_assertion.h` requires Unix headers.
// REQUIRES: has-unix-headers
// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support.
// UNSUPPORTED: c++03, !has-unix-headers, no-localization
// The ability to set a custom abort message is required to compare the assertion message.
// XFAIL: availability-verbose_abort-missing
// ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HARDENING_MODE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@

// This test ensures that we can override any hardening mode with the extensive hardening mode on a per-TU basis.

// `check_assertion.h` is only available starting from C++11.
// UNSUPPORTED: c++03
// `check_assertion.h` requires Unix headers.
// REQUIRES: has-unix-headers
// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support.
// UNSUPPORTED: c++03, !has-unix-headers, no-localization
// The ability to set a custom abort message is required to compare the assertion message.
// XFAIL: availability-verbose_abort-missing
// ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HARDENING_MODE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@

// This test ensures that we can override any hardening mode with the fast mode on a per-TU basis.

// `check_assertion.h` is only available starting from C++11.
// UNSUPPORTED: c++03
// `check_assertion.h` requires Unix headers.
// REQUIRES: has-unix-headers
// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support.
// UNSUPPORTED: c++03, !has-unix-headers, no-localization
// The ability to set a custom abort message is required to compare the assertion message.
// XFAIL: availability-verbose_abort-missing
// ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HARDENING_MODE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@

// This test ensures that we can override any hardening mode with the unchecked mode on a per-TU basis.

// `check_assertion.h` is only available starting from C++11.
// UNSUPPORTED: c++03
// `check_assertion.h` requires Unix headers.
// REQUIRES: has-unix-headers
// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support.
// UNSUPPORTED: c++03, !has-unix-headers, no-localization
// ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HARDENING_MODE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE

#include <cassert>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
// Regression test to error in deque::__annotate_from_to in deque,
// with origin in deque::__add_back_capacity.

// REQUIRES: has-unix-headers
// UNSUPPORTED: c++03
// `check_assertion.h` is only available starting from C++11 and requires Unix headers and regex support.
// UNSUPPORTED: c++03, !has-unix-headers, no-localization

#include <deque>
#include <cstdio>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: no-exceptions
// REQUIRES: has-unix-headers
// `check_assertion.h` requires Unix headers and regex support.
// UNSUPPORTED: !has-unix-headers, no-localization

// UNSUPPORTED: libcpp-has-no-incomplete-pstl

Expand Down
Loading