diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt --- a/libcxx/CMakeLists.txt +++ b/libcxx/CMakeLists.txt @@ -62,6 +62,17 @@ available on the platform. This includes things like most parts of and others like " ON) option(LIBCXX_INCLUDE_TESTS "Build the libc++ tests." ${LLVM_INCLUDE_TESTS}) +set(LIBCXX_SUPPORTED_HARDENING_MODES unchecked hardened debug) +set(LIBCXX_HARDENING_MODE "unchecked" CACHE STRING + "Specify the hardening mode to use. This mode will be used inside the + compiled library and will be the default when compiling user code. Note that + users can override this setting in their own code. This does not affect the + ABI. Supported values are ${LIBCXX_SUPPORTED_HARDENING_MODES}.") +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() 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 @@ -784,6 +795,19 @@ else() config_define(0 _LIBCPP_ENABLE_ASSERTIONS_DEFAULT) endif() +if (LIBCXX_HARDENING_MODE STREQUAL "hardened") + config_define(1 _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT) + config_define(0 _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT) +elseif (LIBCXX_HARDENING_MODE STREQUAL "debug") + config_define(0 _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT) + config_define(1 _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT) +elseif (LIBCXX_HARDENING_MODE STREQUAL "unchecked") + config_define(0 _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT) + config_define(0 _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT) +else() + message(FATAL_ERROR "LIBCXX_HARDENING_MODE is set to ${LIBCXX_HARDENING_MODE}, which is not a valid value. + Valid values are: ${LIBCXX_SUPPORTED_HARDENING_MODES}") +endif() if (LIBCXX_PSTL_CPU_BACKEND STREQUAL "serial") config_define(1 _LIBCPP_PSTL_CPU_BACKEND_SERIAL) diff --git a/libcxx/cmake/caches/Generic-debug-mode.cmake b/libcxx/cmake/caches/Generic-debug-mode.cmake new file mode 100644 --- /dev/null +++ b/libcxx/cmake/caches/Generic-debug-mode.cmake @@ -0,0 +1 @@ +set(LIBCXX_HARDENING_MODE "debug" CACHE STRING "") diff --git a/libcxx/cmake/caches/Generic-hardened-mode.cmake b/libcxx/cmake/caches/Generic-hardened-mode.cmake new file mode 100644 --- /dev/null +++ b/libcxx/cmake/caches/Generic-hardened-mode.cmake @@ -0,0 +1 @@ +set(LIBCXX_HARDENING_MODE "hardened" CACHE STRING "") diff --git a/libcxx/docs/HardenedMode.rst b/libcxx/docs/HardenedMode.rst new file mode 100644 --- /dev/null +++ b/libcxx/docs/HardenedMode.rst @@ -0,0 +1,37 @@ +============= +Hardened Mode +============= + +.. contents:: + :local: + +.. _using-hardened-mode: + +Using the hardened mode +==================== + +The hardened mode enables a set of security-critical assertions that prevent +undefined behavior caused by violating preconditions of the standard library. +These assertions can be done with relatively little overhead in constant time +and are intended to be used in production by security-conscious projects. + +In addition to the hardened mode, libc++ also provides the debug mode which +contains all the checks from the hardened mode and additionally more expensive +checks that may affect the complexity of algorithms. The debug mode is intended +to be used for testing, not in production. + +Vendors can enable the hardened mode by building the library with the +``LIBCXX_ENABLE_HARDENED_MODE`` option, and similarly enable the debug mode by +building with the ``LIBCXX_ENABLE_DEBUG_MODE`` option. + +The hardened mode requires ``LIBCXX_ENABLE_ASSERTIONS`` to work. If +``LIBCXX_ENABLE_ASSERTIONS`` was not set explicitly, enabling +``LIBCXX_ENABLE_HARDENED_MODE`` will implicitly enable +``LIBCXX_ENABLE_ASSERTIONS``. If ``LIBCXX_ENABLE_ASSERTIONS`` was explicitly +disabled, this will effectively disable the hardened mode. + +Enabling the hardened mode or the debug mode has no impact on the ABI. + +Iterator bounds checking +------------------------ +TODO(hardening) diff --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst --- a/libcxx/docs/ReleaseNotes.rst +++ b/libcxx/docs/ReleaseNotes.rst @@ -71,14 +71,25 @@ Anything that does not rely on having an actual filesystem available will now work, such as ``std::filesystem::path``, ``std::filesystem::perms`` and similar classes. +- The library now provides a hardened mode under which common cases of library undefined behavior will be turned into + a reliable program crash. Vendors can configure whether the hardened mode is enabled by default with the + ``LIBCXX_HARDENING_MODE`` variable at CMake configuration time. Users can control whether the hardened mode is + enabled on a per translation unit basis using the ``-D_LIBCPP_ENABLE_HARDENED_MODE=1`` macro. See + ``libcxx/docs/HardenedMode.rst`` for more details. + +- The library now provides a debug mode which is a superset of the hardened mode, additionally enabling more expensive + checks that are not suitable to be used in production. This replaces the legacy debug mode that was removed in this + release. Unlike the legacy debug mode, this doesn't affect the ABI and doesn't require locking. Vendors can configure + whether the debug mode is enabled by default with the ``LIBCXX_HARDENING_MODE`` variable at CMake configuration time. + Users can control whether the debug mode is enabled on a per translation unit basis using the + ``-D_LIBCPP_ENABLE_DEBUG_MODE=1`` macro. See ``libcxx/docs/HardenedMode.rst`` for more details. + Deprecations and Removals ------------------------- -- The legacy debug mode has been removed in this release. Defining the macro - `_LIBCPP_ENABLE_DEBUG_MODE` is now a no-op, and the `LIBCXX_ENABLE_DEBUG_MODE` - CMake variable has been removed. The legacy debug mode will be replaced by - finer-grained hardened modes. For additional context, refer to the `Discourse - post +- The legacy debug mode has been removed in this release. Enabling the macro `_LIBCPP_ENABLE_DEBUG_MODE` now enables the + new debug mode which is part of hardening (see the "Improvements and New Features" section above). The + `LIBCXX_ENABLE_DEBUG_MODE` CMake variable has been removed. For additional context, refer to the `Discourse post `_. - The ```` header has been removed in this release. The ```` header diff --git a/libcxx/docs/index.rst b/libcxx/docs/index.rst --- a/libcxx/docs/index.rst +++ b/libcxx/docs/index.rst @@ -184,6 +184,7 @@ DesignDocs/ExtendedCXX03Support DesignDocs/FeatureTestMacros DesignDocs/FileTimeType + DesignDocs/HardenedMode DesignDocs/HeaderRemovalPolicy DesignDocs/NoexceptPolicy DesignDocs/PSTLIntegration diff --git a/libcxx/include/__algorithm/comp_ref_type.h b/libcxx/include/__algorithm/comp_ref_type.h --- a/libcxx/include/__algorithm/comp_ref_type.h +++ b/libcxx/include/__algorithm/comp_ref_type.h @@ -66,7 +66,7 @@ // Pass the comparator by lvalue reference. Or in debug mode, using a // debugging wrapper that stores a reference. // TODO(varconst): update to be used in the new debug mode (or delete entirely). -#ifdef _LIBCPP_ENABLE_DEBUG_MODE +#if _LIBCPP_ENABLE_DEBUG_MODE template using __comp_ref_type = __debug_less<_Comp>; #else diff --git a/libcxx/include/__algorithm/three_way_comp_ref_type.h b/libcxx/include/__algorithm/three_way_comp_ref_type.h --- a/libcxx/include/__algorithm/three_way_comp_ref_type.h +++ b/libcxx/include/__algorithm/three_way_comp_ref_type.h @@ -59,7 +59,7 @@ // Pass the comparator by lvalue reference. Or in debug mode, using a // debugging wrapper that stores a reference. // TODO(varconst): update to be used in the new debug mode (or delete entirely). -# ifdef _LIBCPP_ENABLE_DEBUG_MODE +# if _LIBCPP_ENABLE_DEBUG_MODE template using __three_way_comp_ref_type = __debug_three_way_comp<_Comp>; # else diff --git a/libcxx/include/__config b/libcxx/include/__config --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -206,6 +206,71 @@ // } ABI +// HARDENING { + +// Enables the hardened mode which consists of all checks intended to be used in production. Hardened mode prioritizes +// security-critical checks that can be done with relatively little overhead in constant time. Mutually exclusive with +// `_LIBCPP_ENABLE_DEBUG_MODE`. +// +// #define _LIBCPP_ENABLE_HARDENED_MODE 1 + +// Enables the debug mode which contains all the checks from the hardened mode and additionally more expensive checks +// that may affect the complexity of algorithms. The debug mode is intended to be used for testing, not in production. +// Mutually exclusive with `_LIBCPP_ENABLE_HARDENED_MODE`. +// +// #define _LIBCPP_ENABLE_DEBUG_MODE 1 + +// Available checks: + +// TODO(hardening): add documentation for different checks here. + +# ifndef _LIBCPP_ENABLE_HARDENED_MODE +# define _LIBCPP_ENABLE_HARDENED_MODE _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT +# endif +# if _LIBCPP_ENABLE_HARDENED_MODE != 0 && _LIBCPP_ENABLE_HARDENED_MODE != 1 +# error "_LIBCPP_ENABLE_HARDENED_MODE must be set to 0 or 1." +# endif + +# ifndef _LIBCPP_ENABLE_DEBUG_MODE +# define _LIBCPP_ENABLE_DEBUG_MODE _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT +# endif +# if _LIBCPP_ENABLE_DEBUG_MODE != 0 && _LIBCPP_ENABLE_DEBUG_MODE != 1 +# error "_LIBCPP_ENABLE_DEBUG_MODE must be set to 0 or 1." +# endif + +# if _LIBCPP_ENABLE_HARDENED_MODE && _LIBCPP_ENABLE_DEBUG_MODE +# error "Only one of _LIBCPP_ENABLE_HARDENED_MODE and _LIBCPP_ENABLE_DEBUG_MODE can be enabled." +# endif + +// Hardened mode checks. +# if _LIBCPP_ENABLE_HARDENED_MODE + +// Automatically enable assertions in hardened mode (unless the user explicitly turned them off). +# ifndef _LIBCPP_ENABLE_ASSERTIONS +# define _LIBCPP_ENABLE_ASSERTIONS 1 +# endif + +// TODO(hardening): more checks to be added here... + +// Debug mode checks. +# elif _LIBCPP_ENABLE_DEBUG_MODE + +// Automatically enable assertions in debug mode (unless the user explicitly turned them off). +# ifndef _LIBCPP_ENABLE_ASSERTIONS +# define _LIBCPP_ENABLE_ASSERTIONS 1 +# endif + +// TODO(hardening): more checks to be added here... + +// Disable all checks if neither the hardened mode nor the debug mode is enabled. +# else + +// TODO: more checks to be added here... + +# endif // _LIBCPP_ENABLE_HARDENED_MODE + +// } HARDENING + # define _LIBCPP_TOSTRING2(x) #x # define _LIBCPP_TOSTRING(x) _LIBCPP_TOSTRING2(x) diff --git a/libcxx/include/__config_site.in b/libcxx/include/__config_site.in --- a/libcxx/include/__config_site.in +++ b/libcxx/include/__config_site.in @@ -34,6 +34,10 @@ #cmakedefine _LIBCPP_PSTL_CPU_BACKEND_SERIAL #cmakedefine _LIBCPP_PSTL_CPU_BACKEND_THREAD +// Hardening. +#cmakedefine01 _LIBCPP_ENABLE_HARDENED_MODE_DEFAULT +#cmakedefine01 _LIBCPP_ENABLE_DEBUG_MODE_DEFAULT + // __USE_MINGW_ANSI_STDIO gets redefined on MinGW #ifdef __clang__ # pragma clang diagnostic push diff --git a/libcxx/test/CMakeLists.txt b/libcxx/test/CMakeLists.txt --- a/libcxx/test/CMakeLists.txt +++ b/libcxx/test/CMakeLists.txt @@ -28,6 +28,8 @@ serialize_lit_param(enable_assertions True) endif() +serialize_lit_param(hardening_mode "\"${LIBCXX_HARDENING_MODE}\"") + if (CMAKE_CXX_COMPILER_TARGET) serialize_lit_param(target_triple "\"${CMAKE_CXX_COMPILER_TARGET}\"") else() diff --git a/libcxx/test/support/container_debug_tests.h b/libcxx/test/support/container_debug_tests.h --- a/libcxx/test/support/container_debug_tests.h +++ b/libcxx/test/support/container_debug_tests.h @@ -14,7 +14,7 @@ #error This header may only be used for libc++ tests #endif -#ifndef _LIBCPP_ENABLE_DEBUG_MODE +#if !_LIBCPP_ENABLE_DEBUG_MODE #error The library must be built with the debug mode enabled in order to use this header #endif diff --git a/libcxx/utils/ci/buildkite-pipeline.yml b/libcxx/utils/ci/buildkite-pipeline.yml --- a/libcxx/utils/ci/buildkite-pipeline.yml +++ b/libcxx/utils/ci/buildkite-pipeline.yml @@ -486,6 +486,40 @@ limit: 2 timeout_in_minutes: 120 + - label: "Hardened mode" + command: "libcxx/utils/ci/run-buildbot generic-hardened-mode" + artifact_paths: + - "**/test-results.xml" + - "**/*.abilist" + env: + CC: "clang-${LLVM_HEAD_VERSION}" + CXX: "clang++-${LLVM_HEAD_VERSION}" + agents: + queue: "libcxx-builders" + os: "linux" + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 + timeout_in_minutes: 120 + + - label: "Debug mode" + command: "libcxx/utils/ci/run-buildbot generic-debug-mode" + artifact_paths: + - "**/test-results.xml" + - "**/*.abilist" + env: + CC: "clang-${LLVM_HEAD_VERSION}" + CXX: "clang++-${LLVM_HEAD_VERSION}" + agents: + queue: "libcxx-builders" + os: "linux" + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 + timeout_in_minutes: 120 + - label: "With LLVM's libunwind" command: "libcxx/utils/ci/run-buildbot generic-with_llvm_unwinder" artifact_paths: diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot --- a/libcxx/utils/ci/run-buildbot +++ b/libcxx/utils/ci/run-buildbot @@ -387,6 +387,18 @@ check-runtimes check-abi-list ;; +generic-hardened-mode) + clean + generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-hardened-mode.cmake" + check-runtimes + check-abi-list +;; +generic-debug-mode) + clean + generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-debug-mode.cmake" + check-runtimes + check-abi-list +;; generic-with_llvm_unwinder) clean generate-cmake -DLIBCXXABI_USE_LLVM_UNWINDER=ON diff --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py --- a/libcxx/utils/libcxx/test/params.py +++ b/libcxx/utils/libcxx/test/params.py @@ -294,6 +294,23 @@ AddFeature("libcpp-has-assertions"), ], ), + Parameter( + name="hardening_mode", + choices=["unchecked", "hardened", "debug"], + type=str, + default="unchecked", + help="Whether to enable the hardened mode or the debug mode when compiling the test suite. This is only " + "meaningful when running the tests against libc++.", + actions=lambda hardening_mode: filter( + None, + [ + AddCompileFlag("-D_LIBCPP_ENABLE_HARDENED_MODE=1") if hardening_mode == "hardened" else None, + AddCompileFlag("-D_LIBCPP_ENABLE_DEBUG_MODE=1") if hardening_mode == "debug" else None, + AddFeature("libcpp-has-hardened-mode") if hardening_mode == "hardened" else None, + AddFeature("libcpp-has-debug-mode") if hardening_mode == "debug" else None, + ], + ), + ), Parameter( name="additional_features", type=list,