Skip to content

Commit

Permalink
[libc++][hardening][NFC] Add macros to enable hardened mode.
Browse files Browse the repository at this point in the history
This patch only adds new configuration knobs -- the actual assertions
will be added in follow-up patches.

Differential Revision: https://reviews.llvm.org/D153902
  • Loading branch information
var-const committed Jul 12, 2023
1 parent d0b5165 commit d1367ca
Show file tree
Hide file tree
Showing 34 changed files with 529 additions and 14 deletions.
20 changes: 20 additions & 0 deletions libcxx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ option(LIBCXX_ENABLE_FILESYSTEM
available on the platform. This includes things like most parts of <filesystem> and
others like <fstream>" 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 default 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
Expand Down Expand Up @@ -784,6 +794,16 @@ if (LIBCXX_ENABLE_ASSERTIONS)
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)
endif()

if (LIBCXX_PSTL_CPU_BACKEND STREQUAL "serial")
config_define(1 _LIBCPP_PSTL_CPU_BACKEND_SERIAL)
Expand Down
1 change: 1 addition & 0 deletions libcxx/cmake/caches/Generic-debug-mode.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
set(LIBCXX_HARDENING_MODE "debug" CACHE STRING "")
1 change: 1 addition & 0 deletions libcxx/cmake/caches/Generic-hardened-mode.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
set(LIBCXX_HARDENING_MODE "hardened" CACHE STRING "")
41 changes: 41 additions & 0 deletions libcxx/docs/HardenedMode.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
=============
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.

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 set the default hardened mode by using the ``LIBCXX_HARDENING_MODE``
CMake variable. Setting ``LIBCXX_HARDENING_MODE`` to ``hardened`` enables the
hardened mode, and similarly setting the variable to ``debug`` enables the debug
mode. The default value is ``unchecked`` which doesn't enable the hardened mode.
Users can control whether the hardened mode or the debug mode is enabled
on a per translation unit basis by setting the ``_LIBCPP_ENABLE_HARDENED_MODE``
or ``_LIBCPP_ENABLE_DEBUG_MODE`` macro to ``1``.

The hardened mode requires ``LIBCXX_ENABLE_ASSERTIONS`` to work. If
``LIBCXX_ENABLE_ASSERTIONS`` was not set explicitly, enabling the hardened mode
(or the debug 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)
21 changes: 16 additions & 5 deletions libcxx/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,25 @@ Improvements and New Features
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 termination. 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. Setting the macro ``_LIBCPP_ENABLE_DEBUG_MODE`` to ``1`` 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
<https://discourse.llvm.org/t/rfc-removing-the-legacy-debug-mode-from-libc/71026>`_.

- The ``<experimental/coroutine>`` header has been removed in this release. The ``<coroutine>`` header
Expand Down
1 change: 1 addition & 0 deletions libcxx/docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Getting Started with libc++
TestingLibcxx
Contributing
Modules
HardenedMode
ReleaseProcedure
Status/Cxx14
Status/Cxx17
Expand Down
3 changes: 1 addition & 2 deletions libcxx/include/__algorithm/comp_ref_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ struct __debug_less

// 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 <class _Comp>
using __comp_ref_type = __debug_less<_Comp>;
#else
Expand Down
3 changes: 1 addition & 2 deletions libcxx/include/__algorithm/three_way_comp_ref_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ struct __debug_three_way_comp {

// 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 <class _Comp>
using __three_way_comp_ref_type = __debug_three_way_comp<_Comp>;
# else
Expand Down
65 changes: 65 additions & 0 deletions libcxx/include/__config
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 4 additions & 0 deletions libcxx/include/__config_site.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions libcxx/include/__tree
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,9 @@ __tree_remove(_NodePtr __root, _NodePtr __z) _NOEXCEPT
{
_LIBCPP_ASSERT_UNCATEGORIZED(__root != nullptr, "Root node should not be null");
_LIBCPP_ASSERT_UNCATEGORIZED(__z != nullptr, "The node to remove should not be null");
// TODO: Use in the new debug mode:
// _LIBCPP_DEBUG_ASSERT(std::__tree_invariant(__root), "The tree invariants should hold");
#if _LIBCPP_ENABLE_DEBUG_MODE
_LIBCPP_ASSERT_UNCATEGORIZED(std::__tree_invariant(__root), "The tree invariants should hold");
#endif
// __z will be removed from the tree. Client still needs to destruct/deallocate it
// __y is either __z, or if __z has two children, __tree_next(__z).
// __y will have at most one child.
Expand Down
2 changes: 2 additions & 0 deletions libcxx/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ if (LIBCXX_ENABLE_ASSERTIONS)
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ int main(int, char**)
std::make_heap(v.begin(), v.end());
assert(stats.copied == 0);
assert(stats.moved == 153'486);
#if !_LIBCPP_ENABLE_DEBUG_MODE
assert(stats.compared == 188'285);
#endif

assert(std::is_heap(v.begin(), v.end()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
// XFAIL: availability-verbose_abort-missing
// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_ASSERTIONS=1 -D_LIBCPP_DEBUG_STRICT_WEAK_ORDERING_CHECK
// When the debug mode is enabled, this test fails because we actually catch on the fly that the comparator is not
// a strict-weak ordering before we catch that we'd dereference out-of-bounds inside std::sort, which leads to different
// errors than the ones tested below.
// XFAIL: libcpp-has-debug-mode

// This test uses a specific combination of an invalid comparator and sequence of values to
// ensure that our sorting functions do not go out-of-bounds and satisfy strict weak ordering in that case.
Expand Down
28 changes: 28 additions & 0 deletions libcxx/test/libcxx/assertions/modes/debug.pass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// This test ensures that assertions trigger without the user having to do anything when the debug mode has been enabled
// by default.

// UNSUPPORTED: !libcpp-has-debug-mode
// `check_assertion.h` is only available starting from C++11.
// UNSUPPORTED: c++03
// `check_assertion.h` requires Unix headers.
// REQUIRES: has-unix-headers

#include <cassert>
#include "check_assertion.h"

int main(int, char**) {
_LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire");
TEST_LIBCPP_ASSERT_FAILURE([] {
_LIBCPP_ASSERT_UNCATEGORIZED(false, "Should fire");
}(), "Should fire");

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// This test ensures that we can disable the debug mode on a per-TU basis regardless of how the library was built.

// TODO(hardening): currently, explicitly enabling assertions enables all uncategorized assertions and overrides
// disabling the debug mode.
// UNSUPPORTED: libcpp-has-hardened-mode, libcpp-has-assertions
// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_DEBUG_MODE=0

#include <cassert>

int main(int, char**) {
_LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire");
_LIBCPP_ASSERT_UNCATEGORIZED(false, "Also should not fire");

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// This test ensures that we can enable the debug mode on a per-TU basis regardless of how the library was built.

// Hardened mode would additionally trigger the error that hardened and debug modes are mutually exclusive.
// UNSUPPORTED: libcpp-has-hardened-mode
// `check_assertion.h` is only available starting from C++11.
// UNSUPPORTED: c++03
// `check_assertion.h` requires Unix headers.
// REQUIRES: has-unix-headers
// The ability to set a custom abort message is required to compare the assertion message.
// XFAIL: availability-verbose_abort-missing
// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_DEBUG_MODE=1

#include <cassert>
#include "check_assertion.h"

int main(int, char**) {
_LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire");
TEST_LIBCPP_ASSERT_FAILURE([] {
_LIBCPP_ASSERT_UNCATEGORIZED(false, "Should fire");
}(), "Should fire");

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// This test verifies that setting the debug mode to a value other than `0` or `1` triggers a compile-time error.

// Hardened mode would additionally trigger the error that hardened and debug modes are mutually exclusive.
// UNSUPPORTED: libcpp-has-hardened-mode
// Modules build produces a different error ("Could not build module 'std'").
// UNSUPPORTED: modules-build
// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_DEBUG_MODE=2

#include <cassert>

// expected-error@*:* {{_LIBCPP_ENABLE_DEBUG_MODE must be set to 0 or 1.}}
21 changes: 21 additions & 0 deletions libcxx/test/libcxx/assertions/modes/debug_no_assertions.pass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// Test that we can override whether assertions are enabled regardless of the hardening mode in use.

// UNSUPPORTED: !libcpp-has-debug-mode
// ADDITIONAL_COMPILE_FLAGS: -Wno-macro-redefined -D_LIBCPP_ENABLE_ASSERTIONS=0

#include <cassert>

int main(int, char**) {
_LIBCPP_ASSERT_UNCATEGORIZED(true, "Should not fire");
_LIBCPP_ASSERT_UNCATEGORIZED(false, "Also should not fire");

return 0;
}
Loading

0 comments on commit d1367ca

Please sign in to comment.