Skip to content

[libc++][hardening] Add an experimental function to log hardening errors #149452

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

Conversation

var-const
Copy link
Member

Unlike verbose_abort, this function merely logs the error but does not
terminate execution. It is intended to make it possible to implement the
observe semantic for Hardening.

@var-const var-const force-pushed the varconst/hardening-semantics-log_hardening_failure-2 branch from 48c191d to 74fc2ec Compare July 18, 2025 05:58
@var-const var-const force-pushed the varconst/hardening-semantics-log_hardening_failure-2 branch from 74fc2ec to 13e14f1 Compare July 22, 2025 09:48
@var-const var-const marked this pull request as ready for review July 22, 2025 09:49
@var-const var-const requested a review from a team as a code owner July 22, 2025 09:49
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jul 22, 2025
@var-const var-const requested a review from philnik777 July 22, 2025 09:49
@llvmbot
Copy link
Member

llvmbot commented Jul 22, 2025

@llvm/pr-subscribers-libcxx

Author: Konstantin Varlamov (var-const)

Changes

Unlike verbose_abort, this function merely logs the error but does not
terminate execution. It is intended to make it possible to implement the
observe semantic for Hardening.


Full diff: https://github.com/llvm/llvm-project/pull/149452.diff

9 Files Affected:

  • (modified) libcxx/include/CMakeLists.txt (+1)
  • (modified) libcxx/include/__config (+1)
  • (added) libcxx/include/__log_hardening_failure (+40)
  • (modified) libcxx/include/module.modulemap.in (+3)
  • (modified) libcxx/src/CMakeLists.txt (+1)
  • (added) libcxx/src/experimental/log_hardening_failure.cpp (+54)
  • (added) libcxx/test/libcxx/assertions/log_hardening_failure.pass.cpp (+27)
  • (modified) libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp (+4)
  • (modified) libcxx/utils/libcxx/test/params.py (+1)
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 25b567df2dd33..4a6a61b640a48 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -535,6 +535,7 @@ set(files
   __locale_dir/time.h
   __locale_dir/wbuffer_convert.h
   __locale_dir/wstring_convert.h
+  __log_hardening_failure
   __math/abs.h
   __math/copysign.h
   __math/error_functions.h
diff --git a/libcxx/include/__config b/libcxx/include/__config
index ee06abfba7a08..c867b4bcc0f1f 100644
--- a/libcxx/include/__config
+++ b/libcxx/include/__config
@@ -207,6 +207,7 @@ _LIBCPP_HARDENING_MODE_DEBUG
 #  define _LIBCPP_HAS_EXPERIMENTAL_PSTL _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
 #  define _LIBCPP_HAS_EXPERIMENTAL_TZDB _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
 #  define _LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
+#  define _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
 
 #  if defined(__MVS__)
 #    include <features.h> // for __NATIVE_ASCII_F
diff --git a/libcxx/include/__log_hardening_failure b/libcxx/include/__log_hardening_failure
new file mode 100644
index 0000000000000..76da1153d78b2
--- /dev/null
+++ b/libcxx/include/__log_hardening_failure
@@ -0,0 +1,40 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___LOG_HARDENING_FAILURE
+#define _LIBCPP___LOG_HARDENING_FAILURE
+
+#include <__config>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+// This function should never be called directly from the code -- it should only be called through the
+// `_LIBCPP_LOG_HARDENING_FAILURE` macro.
+_LIBCPP_EXPORTED_FROM_ABI void __log_hardening_failure(const char* __message) _NOEXCEPT;
+
+// _LIBCPP_LOG_HARDENING_FAILURE(message)
+//
+// This macro is used to log an error without terminating the program (as is the case for hardening failures if the
+// `observe` assertion semantic is used).
+
+#  if !defined(_LIBCPP_LOG_HARDENING_FAILURE)
+#    define _LIBCPP_LOG_HARDENING_FAILURE(__message) ::std::__log_hardening_failure(__message)
+#  endif // !defined(_LIBCPP_LOG_HARDENING_FAILURE)
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC
+
+#endif // _LIBCPP___LOG_HARDENING_FAILURE
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 78607f2c1301d..9ee964c0069f4 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2353,6 +2353,9 @@ module std [system] {
     header "__std_mbstate_t.h"
     export *
   }
+  module log_hardening_failure {
+    header "__log_hardening_failure"
+  }
   module verbose_abort {
     header "__verbose_abort"
   }
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 97fe57a5f24f8..f59fe0e08fccb 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -309,6 +309,7 @@ add_custom_target(cxx DEPENDS ${LIBCXX_BUILD_TARGETS})
 # Build the experimental static library
 set(LIBCXX_EXPERIMENTAL_SOURCES
   experimental/keep.cpp
+  experimental/log_hardening_failure.cpp
   )
 
 if (LIBCXX_PSTL_BACKEND STREQUAL "libdispatch")
diff --git a/libcxx/src/experimental/log_hardening_failure.cpp b/libcxx/src/experimental/log_hardening_failure.cpp
new file mode 100644
index 0000000000000..274175c1acaa8
--- /dev/null
+++ b/libcxx/src/experimental/log_hardening_failure.cpp
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include <__config>
+#include <__log_hardening_failure>
+#include <cstdio>
+
+#ifdef __BIONIC__
+#  include <syslog.h>
+extern "C" void android_set_abort_message(const char* msg);
+#endif // __BIONIC__
+
+#if defined(__APPLE__) && __has_include(<os/reason_private.h>)
+#  include <TargetConditionals.h>
+#  include <os/reason_private.h>
+#endif
+
+#if _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+void __log_hardening_failure(const char* message) noexcept {
+  // Always log the message to `stderr` in case the platform-specific system calls fail.
+  std::fputs(message, stderr);
+
+  // On Apple platforms, use the `os_fault_with_payload` OS function that simulates a crash.
+#  if defined(__APPLE__) && __has_include(<os/reason_private.h>) && !TARGET_OS_SIMULATOR
+  os_fault_with_payload(
+      /*reason_namespace=*/OS_REASON_SECURITY,
+      /*reason_code=*/0,
+      /*payload=*/nullptr,
+      /*payload_size=*/0,
+      /*reason_string=*/message,
+      /*reason_flags=*/0);
+
+#  elif defined(__BIONIC__)
+  // Show error in tombstone.
+  android_set_abort_message(message);
+
+  // Show error in logcat. The latter two arguments are ignored on Android.
+  openlog("libc++", 0, 0);
+  syslog(LOG_CRIT, "%s", message);
+  closelog();
+#  endif
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC
diff --git a/libcxx/test/libcxx/assertions/log_hardening_failure.pass.cpp b/libcxx/test/libcxx/assertions/log_hardening_failure.pass.cpp
new file mode 100644
index 0000000000000..d13b1a0a9c13a
--- /dev/null
+++ b/libcxx/test/libcxx/assertions/log_hardening_failure.pass.cpp
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Basic smoke test for `__log_hardening_failure`.
+//
+// UNSUPPORTED: libcpp-has-no-experimental-hardening-observe-semantic
+// XFAIL: availability-log_hardening_failure-missing
+// UNSUPPORTED: FROZEN-CXX03-HEADERS-FIXME
+
+#include <__log_hardening_failure>
+
+#include "test_macros.h"
+
+ASSERT_NOEXCEPT(std::__log_hardening_failure(""));
+
+int main(int, char**) {
+  std::__log_hardening_failure("Some message");
+  // It's difficult to properly test platform-specific logging behavior of the function; just make sure it exists and
+  // can be called at runtime.
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp b/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
index 3cf497da233fb..3d97446ffe826 100644
--- a/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
+++ b/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
@@ -29,3 +29,7 @@
 #if !_LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM
 #  error "-fexperimental-library should enable the syncstream header"
 #endif
+
+#if !_LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC
+#  error "-fexperimental-library should allow using the Hardening observe semantic"
+#endif
diff --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py
index adfb2a9f69508..93cf29bcdff0d 100644
--- a/libcxx/utils/libcxx/test/params.py
+++ b/libcxx/utils/libcxx/test/params.py
@@ -361,6 +361,7 @@ def getSuitableClangTidy(cfg):
             AddFeature("libcpp-has-no-incomplete-pstl"),
             AddFeature("libcpp-has-no-experimental-tzdb"),
             AddFeature("libcpp-has-no-experimental-syncstream"),
+            AddFeature("libcpp-has-no-experimental-hardening-observe-semantic"),
         ],
     ),
     # TODO: This can be improved once we use a version of GoogleBenchmark that supports the dry-run mode.

Unlike `verbose_abort`, this function merely logs the error but does not
terminate execution. It is intended to make it possible to implement the
`observe` semantic for Hardening.
@var-const var-const force-pushed the varconst/hardening-semantics-log_hardening_failure-2 branch from 13e14f1 to afc0146 Compare July 22, 2025 09:54
@var-const var-const changed the title [libc++][hardening] Introduce a dylib function to log hardening errors. [libc++][hardening] Add an experimental function to log hardening errors Jul 22, 2025
Copy link

github-actions bot commented Jul 23, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@tru tru moved this from Needs Triage to Needs Fix in LLVM Release Status Jul 24, 2025
@github-project-automation github-project-automation bot moved this from Needs Fix to Needs Merge in LLVM Release Status Jul 24, 2025
@ldionne
Copy link
Member

ldionne commented Jul 24, 2025

Merging now (bypassing check) since the only change was _LIBCPP_NOEXCEPT to noexcept in a C++03 guarded code block.

@ldionne ldionne merged commit d750c6d into llvm:main Jul 24, 2025
13 of 18 checks passed
@github-project-automation github-project-automation bot moved this from Needs Merge to Done in LLVM Release Status Jul 24, 2025
@ldionne
Copy link
Member

ldionne commented Jul 24, 2025

/cherry-pick d750c6d

@llvmbot
Copy link
Member

llvmbot commented Jul 24, 2025

/pull-request #150481

@zeroomega
Copy link
Contributor

Hi, after this change got landed, we are seeing a build failure on baremetal armv6m-none-eabi target, error message:

FAILED: libcxx/src/CMakeFiles/cxx_experimental.dir/experimental/log_hardening_failure.cpp.obj 
/b/s/w/ir/x/w/llvm_build/./bin/clang++ --target=armv6m-none-eabi -D_DEBUG -D_FILE_OFFSET_BITS=64 -D_GLIBCXX_ASSERTIONS -D_LARGEFILE_SOURCE -D_LIBCPP_BUILDING_HAS_NO_ABI_LIBRARY -D_LIBCPP_BUILDING_LIBRARY -D_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER -D_LIBCPP_LINK_PTHREAD_LIB -D_LIBCPP_LINK_RT_LIB -D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/b/s/w/ir/x/w/llvm_build/include/armv6m-unknown-none-eabi/c++/v1 -I/b/s/w/ir/x/w/llvm_build/include/c++/v1 -isystem /b/s/w/ir/x/w/llvm_build/include/armv6m-unknown-none-eabi --target=armv6m-none-eabi -Wno-atomic-alignment "-Dvfprintf(stream, format, vlist)=vprintf(format, vlist)" "-Dfprintf(stream, format, ...)=printf(format)" -D_LIBCPP_PRINT=1 -mthumb -fPIC -fno-semantic-interposition -fvisibility-inlines-hidden -Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion -Wmisleading-indentation -Wctad-maybe-unsupported -ffunction-sections -fdata-sections -ffile-prefix-map=/b/s/w/ir/x/w/llvm_build/runtimes/runtimes-armv6m-none-eabi-bins=../../../llvm-llvm-project -ffile-prefix-map=/b/s/w/ir/x/w/llvm-llvm-project/= -no-canonical-prefixes -Os -DNDEBUG -std=c++2b -UNDEBUG -faligned-allocation -nostdinc++ -fvisibility-inlines-hidden -fvisibility=hidden -fsized-deallocation -Wall -Wextra -Wnewline-eof -Wshadow -Wwrite-strings -Wno-unused-parameter -Wno-long-long -Werror=return-type -Wextra-semi -Wundef -Wunused-template -Wformat-nonliteral -Wzero-length-array -Wdeprecated-redundant-constexpr-static-def -Wno-nullability-completeness -Wno-user-defined-literals -Wno-covered-switch-default -Wno-suggest-override -Wno-error -fno-exceptions -fno-rtti -D_LIBCPP_ENABLE_EXPERIMENTAL -fdebug-prefix-map=/b/s/w/ir/x/w/llvm_build/include/c++/v1=/b/s/w/ir/x/w/llvm-llvm-project/libcxx/include -nostdlibinc -MD -MT libcxx/src/CMakeFiles/cxx_experimental.dir/experimental/log_hardening_failure.cpp.obj -MF libcxx/src/CMakeFiles/cxx_experimental.dir/experimental/log_hardening_failure.cpp.obj.d -o libcxx/src/CMakeFiles/cxx_experimental.dir/experimental/log_hardening_failure.cpp.obj -c /b/s/w/ir/x/w/llvm-llvm-project/libcxx/src/experimental/log_hardening_failure.cpp
/b/s/w/ir/x/w/llvm-llvm-project/libcxx/src/experimental/log_hardening_failure.cpp:21:8: error: reference to unresolved using declaration
   21 |   std::fputs(message, stderr);
      |        ^
/b/s/w/ir/x/w/llvm_build/include/c++/v1/cstdio:140:1: note: using declaration annotated with 'using_if_exists' here
  140 | using ::fputs _LIBCPP_USING_IF_EXISTS;
      | ^
/b/s/w/ir/x/w/llvm-llvm-project/libcxx/src/experimental/log_hardening_failure.cpp:21:3: error: excess elements in scalar initializer
   21 |   std::fputs(message, stderr);
      |   ^                 ~~~~~~~~
2 errors generated.

Build task: https://ci.chromium.org/ui/p/fuchsia/builders/toolchain.ci/clang-linux-x64/b8708441153070837249/overview
Could you take a look please? If it takes some time to fix, could you revert it?

@var-const
Copy link
Member Author

@zeroomega Does this failure happen on Fuchsia? IIUC, Fuchsia doesn't support many filesystem-related functions (and I don't think we officially support Fuchsia). The build error is complaining that fputs is unavailable -- what would you normally use on Fuchsia to log an error? I'm also surprised that a similar error doesn't show up in verbose_abort.cpp which uses std::vfprintf that I would expect to be similarly unavailable -- do you have a workaround on your side? (I don't see any Fuchsia-related workarounds in our code for that case)

An easy fix for the build error would be to simply avoid referencing fputs when we're compiling on Fuchsia but that would mean this logging function will be a no-op. I can do that as a quick workaround but perhaps there's a better way?

Cc @ldionne

var-const added a commit to var-const/llvm-project that referenced this pull request Jul 25, 2025
This is a quick fix for a build error on Fuchsia where many C standard
library filesystem-related functions are not available; it effectively
makes the hardening log function a no-op on Fuchsia.

Links:
* llvm#149452 (comment)
* https://ci.chromium.org/ui/p/fuchsia/builders/toolchain.ci/clang-linux-x64/b8708441153070837249/overview
@zeroomega
Copy link
Contributor

@zeroomega Does this failure happen on Fuchsia? IIUC, Fuchsia doesn't support many filesystem-related functions (and I don't think we officially support Fuchsia). The build error is complaining that fputs is unavailable -- what would you normally use on Fuchsia to log an error? I'm also surprised that a similar error doesn't show up in verbose_abort.cpp which uses std::vfprintf that I would expect to be similarly unavailable -- do you have a workaround on your side? (I don't see any Fuchsia-related workarounds in our code for that case)

An easy fix for the build error would be to simply avoid referencing fputs when we're compiling on Fuchsia but that would mean this logging function will be a no-op. I can do that as a quick workaround but perhaps there's a better way?

Cc @ldionne

It failed when building llvm baremetal target runtimes. Those are for microcontrollers like STM32 or Raspberry Pi Picos. Unrelated to Fuchsia. We encountered this issue because we build those runtimes when we build the LLVM toolchain.

I am not very familiar with libcxx but simple mitigation would be just avoiding building this file when the target triple is a baremetal target like armv6m-none-eabi, riscv32-unknown-elf.

@var-const
Copy link
Member Author

I am not very familiar with libcxx but simple mitigation would be just avoiding building this file when the target triple is a baremetal target like armv6m-none-eabi, riscv32-unknown-elf.

Libc++ cannot properly build in an environment that doesn't provide a reasonably complete C library, not without a lot of workarounds. I need to understand how this issue is currently mitigated in other places where it should show up, such as for verbose_abort.cpp. Can you point me to the relevant build files? I don't think omitting this file from the build would work because we'll likely end up with a linker error instead.

@zeroomega
Copy link
Contributor

I am not very familiar with libcxx but simple mitigation would be just avoiding building this file when the target triple is a baremetal target like armv6m-none-eabi, riscv32-unknown-elf.

Libc++ cannot properly build in an environment that doesn't provide a reasonably complete C library, not without a lot of workarounds. I need to understand how this issue is currently mitigated in other places where it should show up, such as for verbose_abort.cpp. Can you point me to the relevant build files? I don't think omitting this file from the build would work because we'll likely end up with a linker error instead.

I dig into how we build the baremetal targets and found out the mitigation in

set(RUNTIMES_${target}_CMAKE_${lang}_local_flags "--target=${target} -Wno-atomic-alignment \"-Dvfprintf(stream, format, vlist)=vprintf(format, vlist)\" \"-Dfprintf(stream, format, ...)=printf(format)\" -D_LIBCPP_PRINT=1")
. I don't have much context information on how that was decided. But I guess the issue is that we build those baremetal targets using llvm-libc. And due to obvious reasons that microcontroller baremetal targets don't have FILE support at all, any C functions that accept a FILE type parameters do not exist. The workaround basically replaces those to the non FILE functions, which redirect output to default hardware serial interface. I am testing a patch to replace fputs to puts with the same method to see if it clears the build error.

@zeroomega
Copy link
Contributor

I landed the workaround 38f6364 . It should address the failure on our side. Ideally, LLVM libc should provide those functions for the baremetal targets, even if they don't work. But that is a different discussion.

@var-const
Copy link
Member Author

@zeroomega Thank you for investigating and landing the fix! Very happy the breakage got fixed quickly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
Development

Successfully merging this pull request may close these issues.

5 participants