[libc-commits] [libc] [libc] introduce hardening mode and add heap integrity check as example (PR #210373)

Schrodinger ZHU Yifan via libc-commits libc-commits at lists.llvm.org
Wed Jul 22 11:58:49 PDT 2026


https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/210373

>From c01f26dc58c984070e608f1e183dec27fff93f5c Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Fri, 17 Jul 2026 09:02:26 -0700
Subject: [PATCH 1/8] [libc] introduce sanitization check macros

TAG=agy
CONV=a13562f0-1759-4e08-b474-130301f9e743
---
 .../modules/LLVMLibCCompileOptionRules.cmake  |  4 ++
 libc/config/config.json                       |  4 ++
 libc/src/__support/libc_assert.h              | 38 +++++++++++++++++++
 3 files changed, 46 insertions(+)

diff --git a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
index b013152effa4c..9aefbd6ac5ac9 100644
--- a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
+++ b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
@@ -193,6 +193,10 @@ function(_get_compile_options_from_config output_var)
     list(APPEND config_options "-DLIBC_COPT_USE_C_ASSERT")
   endif()
 
+  if(LIBC_COPT_ENABLE_SANITIZATION)
+    libc_add_definition(config_options "LIBC_COPT_ENABLE_SANITIZATION")
+  endif()
+
   set(${output_var} ${config_options} PARENT_SCOPE)
 endfunction(_get_compile_options_from_config)
 
diff --git a/libc/config/config.json b/libc/config/config.json
index d576d7d14e4af..9c03a3cf09110 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -189,6 +189,10 @@
     "LIBC_COPT_USE_C_ASSERT": {
       "value": false,
       "doc": "Use the system assert macro for LIBC_ASSERT."
+    },
+    "LIBC_COPT_ENABLE_SANITIZATION": {
+      "value": false,
+      "doc": "Enable sanitization checks in the library."
     }
   }
 }
diff --git a/libc/src/__support/libc_assert.h b/libc/src/__support/libc_assert.h
index 26dd0fc2f9562..0f6ead63d017f 100644
--- a/libc/src/__support/libc_assert.h
+++ b/libc/src/__support/libc_assert.h
@@ -20,6 +20,22 @@
 #define LIBC_ASSERT(COND) assert(COND)
 #endif // LIBC_ASSERT
 
+#ifndef LIBC_COPT_ENABLE_SANITIZATION
+#define LIBC_COPT_ENABLE_SANITIZATION false
+#endif
+
+#if LIBC_COPT_ENABLE_SANITIZATION
+#define LIBC_SANITIZATION_CHECK(COND)                                          \
+  do {                                                                         \
+    LIBC_ASSERT((COND) && "Runtime sanitization failed.");                     \
+    __builtin_trap();                                                          \
+  } while (false)
+#else
+#define LIBC_SANITIZATION_CHECK(COND)                                          \
+  do {                                                                         \
+  } while (false)
+#endif
+
 #else // Not LIBC_COPT_USE_C_ASSERT
 
 #include "src/__support/OSUtil/exit.h"
@@ -78,6 +94,28 @@ LIBC_INLINE void report_assertion_failure(const char *assertion,
   } while (false)
 #endif // NDEBUG
 
+#ifndef LIBC_COPT_ENABLE_SANITIZATION
+#define LIBC_COPT_ENABLE_SANITIZATION false
+#endif
+
+#if LIBC_COPT_ENABLE_SANITIZATION
+#define LIBC_SANITIZATION_CHECK(COND)                                          \
+  do {                                                                         \
+    if (LIBC_UNLIKELY(!(COND))) {                                              \
+      LIBC_NAMESPACE::write_to_stderr(__FILE__ ":" LLVM_LIBC_STRINGIFY(        \
+          __LINE__) ": Runtime sanitization failed: '" #COND                   \
+                    "' in function: '");                                       \
+      LIBC_NAMESPACE::write_to_stderr(__PRETTY_FUNCTION__);                    \
+      LIBC_NAMESPACE::write_to_stderr("'\n");                                  \
+      __builtin_trap();                                                        \
+    }                                                                          \
+  } while (false)
+#else
+#define LIBC_SANITIZATION_CHECK(COND)                                          \
+  do {                                                                         \
+  } while (false)
+#endif
+
 #endif // LIBC_COPT_USE_C_ASSERT
 
 #endif // LLVM_LIBC_SRC___SUPPORT_LIBC_ASSERT_H

>From 9929f8af024c13b61e747e5ad3f2418fc93e7550 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Fri, 17 Jul 2026 09:33:12 -0700
Subject: [PATCH 2/8] [libc] sanitize the freelist

---
 libc/src/__support/freelist.cpp | 12 ++++++++++++
 libc/src/__support/freelist.h   | 10 ++++++++++
 libc/src/__support/freestore.h  |  7 +++++++
 libc/src/__support/freetrie.cpp | 21 ++++++++++++++++++++-
 libc/src/__support/freetrie.h   | 16 ++++++++++++++++
 5 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/libc/src/__support/freelist.cpp b/libc/src/__support/freelist.cpp
index 2fdcaadbdb554..133d2da1104f9 100644
--- a/libc/src/__support/freelist.cpp
+++ b/libc/src/__support/freelist.cpp
@@ -20,6 +20,7 @@ void FreeList::push(Node *node) {
     LIBC_ASSERT(BlockRef::from_usable_space(node).outer_size() ==
                     begin_->block().outer_size() &&
                 "freelist entries must have the same size");
+    begin_->sanitize();
     // Since the list is circular, insert the node immediately before begin_.
     node->prev = begin_->prev;
     node->next = begin_;
@@ -32,6 +33,7 @@ void FreeList::push(Node *node) {
 
 void FreeList::remove(Node *node) {
   LIBC_ASSERT(begin_ && "cannot remove from empty list");
+  node->sanitize();
   Node *next = node->next;
   if (node == next) {
     LIBC_ASSERT(node == begin_ &&
@@ -46,4 +48,14 @@ void FreeList::remove(Node *node) {
   }
 }
 
+void FreeList::sanitize() const {
+  if (!begin_)
+    return;
+  Node *curr = begin_;
+  do {
+    curr->sanitize();
+    curr = curr->next;
+  } while (curr != begin_);
+}
+
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/freelist.h b/libc/src/__support/freelist.h
index 48e70c7c29df6..96710bd292850 100644
--- a/libc/src/__support/freelist.h
+++ b/libc/src/__support/freelist.h
@@ -15,6 +15,7 @@
 #define LLVM_LIBC_SRC___SUPPORT_FREELIST_H
 
 #include "block.h"
+#include "src/__support/libc_assert.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -41,6 +42,12 @@ class FreeList {
     /// @returns The inner size of blocks in the list containing this node.
     LIBC_INLINE size_t size() const { return block().inner_size(); }
 
+  protected:
+    LIBC_INLINE void sanitize() const {
+      LIBC_SANITIZATION_CHECK(next->prev == this);
+      LIBC_SANITIZATION_CHECK(prev->next == this);
+    }
+
   private:
     // Circularly linked pointers to adjacent nodes.
     Node *prev;
@@ -85,6 +92,9 @@ class FreeList {
   /// Remove an arbitrary node from the list.
   void remove(Node *node);
 
+  /// Verify integrity of all nodes in the list.
+  void sanitize() const;
+
 private:
   Node *begin_;
 };
diff --git a/libc/src/__support/freestore.h b/libc/src/__support/freestore.h
index adc0e061ace93..9a9b084741377 100644
--- a/libc/src/__support/freestore.h
+++ b/libc/src/__support/freestore.h
@@ -46,6 +46,13 @@ class FreeStore {
   /// allocated. Returns nullptr if there is no such block.
   BlockRef remove_best_fit(size_t size);
 
+  /// Sanitization check for the entire store.
+  LIBC_INLINE void sanitize() const {
+    large_trie.sanitize();
+    for (const FreeList &list : small_lists)
+      list.sanitize();
+  }
+
 private:
   static constexpr size_t MIN_OUTER_SIZE = align_up(
       BlockRef::HEADER_SIZE + sizeof(FreeList::Node), BlockRef::MIN_ALIGN);
diff --git a/libc/src/__support/freetrie.cpp b/libc/src/__support/freetrie.cpp
index e76efe717f215..9738c4635e581 100644
--- a/libc/src/__support/freetrie.cpp
+++ b/libc/src/__support/freetrie.cpp
@@ -7,11 +7,13 @@
 //===----------------------------------------------------------------------===//
 
 #include "freetrie.h"
+#include "src/__support/libc_assert.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 void FreeTrie::remove(Node *node) {
   LIBC_ASSERT(!empty() && "cannot remove from empty trie");
+  node->sanitize();
   FreeList list = node;
   list.pop();
   Node *new_node = static_cast<Node *>(list.begin());
@@ -20,8 +22,11 @@ void FreeTrie::remove(Node *node) {
     // This is legal because there is no relationship between the size of the
     // root and its children.
     Node *leaf = node;
-    while (leaf->lower || leaf->upper)
+    while (leaf->lower || leaf->upper) {
+      leaf->sanitize();
       leaf = leaf->lower ? leaf->lower : leaf->upper;
+    }
+    leaf->sanitize();
     if (leaf == node) {
       // If the root is a leaf, then removing it empties the subtrie.
       replace_node(node, nullptr);
@@ -44,6 +49,7 @@ void FreeTrie::remove(Node *node) {
 
 void FreeTrie::replace_node(Node *node, Node *new_node) {
   LIBC_ASSERT(is_head(node) && "only head nodes contain trie links");
+  node->sanitize();
 
   if (node->parent) {
     Node *&parent_child =
@@ -61,4 +67,17 @@ void FreeTrie::replace_node(Node *node, Node *new_node) {
     node->upper->parent = new_node;
 }
 
+void FreeTrie::sanitize() const {
+  auto sanitize_trie_node = [&](auto &self, const Node *node) -> void {
+    if (!node)
+      return;
+    node->sanitize();
+    FreeList list = const_cast<Node *>(node);
+    list.sanitize();
+    self(self, node->lower);
+    self(self, node->upper);
+  };
+  sanitize_trie_node(sanitize_trie_node, root);
+}
+
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/freetrie.h b/libc/src/__support/freetrie.h
index 9e35463462b38..32f6a306f0c7e 100644
--- a/libc/src/__support/freetrie.h
+++ b/libc/src/__support/freetrie.h
@@ -15,6 +15,7 @@
 #define LLVM_LIBC_SRC___SUPPORT_FREETRIE_H
 
 #include "freelist.h"
+#include "src/__support/libc_assert.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -60,6 +61,16 @@ class FreeTrie {
     Node *parent;
 
     friend class FreeTrie;
+
+    LIBC_INLINE void sanitize() const {
+      FreeList::Node::sanitize();
+      if (lower)
+        LIBC_SANITIZATION_CHECK(lower->parent == this);
+      if (upper)
+        LIBC_SANITIZATION_CHECK(upper->parent == this);
+      if (parent)
+        LIBC_SANITIZATION_CHECK(parent->lower == this || parent->upper == this);
+    }
   };
 
   /// Power-of-two range of sizes covered by a subtrie.
@@ -110,6 +121,9 @@ class FreeTrie {
   /// nullptr.
   Node *find_best_fit(size_t size);
 
+  /// Verify integrity of all nodes in the trie.
+  void sanitize() const;
+
 private:
   /// @returns Whether a node is the head of its containing freelist.
   bool is_head(Node *node) const { return node->parent || node == root; }
@@ -135,6 +149,7 @@ LIBC_INLINE void FreeTrie::push(BlockRef block) {
   while (*cur && (*cur)->size() != size) {
     LIBC_ASSERT(cur_range.contains(size) && "requested size out of trie range");
     parent = *cur;
+    (*cur)->sanitize();
     if (size <= cur_range.lower().max()) {
       cur = &(*cur)->lower;
       cur_range = cur_range.lower();
@@ -167,6 +182,7 @@ LIBC_INLINE FreeTrie::Node *FreeTrie::find_best_fit(size_t size) {
   FreeTrie::SizeRange deferred_upper_range{0, 0};
 
   while (true) {
+    cur->sanitize();
     LIBC_ASSERT(cur_range.contains(cur->size()) &&
                 "trie node size out of range");
     LIBC_ASSERT(cur_range.max() >= size &&

>From 2b3e18f3666fd00f0badbd283f1eef96b1b3fdef Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Fri, 17 Jul 2026 09:54:01 -0700
Subject: [PATCH 3/8] [libc] add sanitization tests for heap

TAG=agy
CONV=28eb1693-190f-4b50-8010-394735339711
---
 libc/src/__support/freelist_heap.h             |  1 +
 libc/test/src/__support/freelist_heap_test.cpp | 16 ++++++++++++++++
 2 files changed, 17 insertions(+)

diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index 73a80754050dd..1b9e5f08ffff3 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -51,6 +51,7 @@ class FreeListHeap {
   void *realloc(void *ptr, size_t size);
   void *calloc(size_t num, size_t size);
   size_t allocation_size(const void *ptr) const;
+  LIBC_INLINE void sanitize() const { free_store.sanitize(); }
 
   cpp::span<cpp::byte> region() const { return {begin, end}; }
 
diff --git a/libc/test/src/__support/freelist_heap_test.cpp b/libc/test/src/__support/freelist_heap_test.cpp
index 1ee6bf0ce4ab4..4667eedcda599 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -366,3 +366,19 @@ TEST_FOR_EACH_ALLOCATOR(AllocationSize, 2048) {
   allocator.free(ptr);
   EXPECT_EQ(allocator.allocation_size(ptr), size_t(0));
 }
+
+TEST_FOR_EACH_ALLOCATOR(CanSanitize, 2048) {
+  allocator.sanitize();
+
+  void *ptr1 = allocator.allocate(512);
+  allocator.sanitize();
+
+  void *ptr2 = allocator.allocate(512);
+  allocator.sanitize();
+
+  allocator.free(ptr1);
+  allocator.sanitize();
+
+  allocator.free(ptr2);
+  allocator.sanitize();
+}

>From a4263e86e7e7ecd71ba5c4ecba0d3e3d05f85689 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Wed, 22 Jul 2026 11:03:17 -0700
Subject: [PATCH 4/8] rework

---
 .../modules/LLVMLibCCompileOptionRules.cmake  |   4 +-
 libc/config/config.json                       |   6 +-
 libc/src/__support/CMakeLists.txt             |   5 +
 libc/src/__support/common.h                   |   1 +
 libc/src/__support/libc_assert.h              | 135 +++++++++---------
 libc/src/__support/macros/CMakeLists.txt      |   7 +
 libc/src/__support/macros/hardening.h         |  31 ++++
 7 files changed, 119 insertions(+), 70 deletions(-)
 create mode 100644 libc/src/__support/macros/hardening.h

diff --git a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
index 9aefbd6ac5ac9..b0a3ca156b1d5 100644
--- a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
+++ b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
@@ -193,8 +193,8 @@ function(_get_compile_options_from_config output_var)
     list(APPEND config_options "-DLIBC_COPT_USE_C_ASSERT")
   endif()
 
-  if(LIBC_COPT_ENABLE_SANITIZATION)
-    libc_add_definition(config_options "LIBC_COPT_ENABLE_SANITIZATION")
+  if(LIBC_COPT_HARDENING_MODE)
+    libc_add_definition(config_options "LIBC_COPT_HARDENING_MODE=${LIBC_COPT_HARDENING_MODE}")
   endif()
 
   set(${output_var} ${config_options} PARENT_SCOPE)
diff --git a/libc/config/config.json b/libc/config/config.json
index 9c03a3cf09110..ed365076d6dd5 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -190,9 +190,9 @@
       "value": false,
       "doc": "Use the system assert macro for LIBC_ASSERT."
     },
-    "LIBC_COPT_ENABLE_SANITIZATION": {
-      "value": false,
-      "doc": "Enable sanitization checks in the library."
+    "LIBC_COPT_HARDENING_MODE": {
+      "value": "LIBC_HARDENING_MODE_NONE",
+      "doc": "Hardening mode for the library."
     }
   }
 }
diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index f4cb283976bc3..2bb9f04416046 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -101,9 +101,11 @@ add_header_library(
     macros/properties/compiler.h
     macros/attributes.h
     macros/config.h
+    macros/hardening.h
   DEPENDS
     libc.hdr.stdint_proxy
     libc.src.__support.CPP.bit
+    libc.src.__support.macros.hardening
 )
 
 add_header_library(
@@ -389,6 +391,8 @@ if(LIBC_COPT_USE_C_ASSERT OR NOT LLVM_LIBC_FULL_BUILD)
     libc_assert
     HDRS
       libc_assert.h
+    DEPENDS
+      libc.src.__support.macros.hardening
   )
 else()
   add_header_library(
@@ -399,6 +403,7 @@ else()
       .integer_to_string
       libc.src.__support.OSUtil.osutil
       libc.src.__support.macros.optimization
+      libc.src.__support.macros.hardening
   )
 endif()
 
diff --git a/libc/src/__support/common.h b/libc/src/__support/common.h
index d90fe7b8ae98d..e6a285177c8d3 100644
--- a/libc/src/__support/common.h
+++ b/libc/src/__support/common.h
@@ -15,6 +15,7 @@
 
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/hardening.h"
 #include "src/__support/macros/properties/architectures.h"
 #include "src/__support/macros/properties/compiler.h"
 
diff --git a/libc/src/__support/libc_assert.h b/libc/src/__support/libc_assert.h
index 0f6ead63d017f..5fa99b36c8ad3 100644
--- a/libc/src/__support/libc_assert.h
+++ b/libc/src/__support/libc_assert.h
@@ -9,43 +9,72 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_LIBC_ASSERT_H
 #define LLVM_LIBC_SRC___SUPPORT_LIBC_ASSERT_H
 
-#if defined(LIBC_COPT_USE_C_ASSERT) || !defined(LIBC_FULL_BUILD)
-
-// The build is configured to just use the public <assert.h> API
-// for libc's internal assertions.
-
-#ifndef LIBC_ASSERT
-#include <assert.h>
-
-#define LIBC_ASSERT(COND) assert(COND)
-#endif // LIBC_ASSERT
+#include "src/__support/macros/attributes.h" // For LIBC_INLINE
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/hardening.h"
+#include "src/__support/macros/macro-utils.h"
+#include "src/__support/macros/optimization.h" // For LIBC_UNLIKEL
+#include "src/__support/macros/properties/os.h"
 
-#ifndef LIBC_COPT_ENABLE_SANITIZATION
-#define LIBC_COPT_ENABLE_SANITIZATION false
+#ifdef LIBC_FULL_BUILD
+#include "src/__support/OSUtil/io.h"
+#include "src/__support/integer_to_string.h"
+#include "src/stdlib/abort_utils.h"
 #endif
 
-#if LIBC_COPT_ENABLE_SANITIZATION
-#define LIBC_SANITIZATION_CHECK(COND)                                          \
+//===----------------------------------------------------------------------===//
+// _LIBC_ASSERT(COND, MSG) (always-on assert regardless of NDEBUG)
+//===----------------------------------------------------------------------===//
+#ifndef LIBC_FULL_BUILD
+#if LIBC_TARGET_OS_IS_LINUX
+// __assert_fail is in LSB, hence we should always be able to use it here.
+extern "C" [[gnu::noreturn]] void __assert_fail(const char *assertion,
+                                                const char *filename,
+                                                uint32_t line,
+                                                const char *funcname);
+#define _LIBC_ASSERT(COND, MSG)                                                \
+  do {                                                                         \
+    if (LIBC_UNLIKELY(!(COND)))                                                \
+      __assert_fail(MSG, __FILE__, __LINE__, __PRETTY_FUNCTION__);             \
+  } while (false)
+#else
+// Fallback path will just trap: we cannot reliably do anything else.
+#define _LIBC_ASSERT(COND, MSG)                                                \
   do {                                                                         \
-    LIBC_ASSERT((COND) && "Runtime sanitization failed.");                     \
-    __builtin_trap();                                                          \
+    if (LIBC_UNLIKELY(!(COND)))                                                \
+      __builtin_trap();                                                        \
   } while (false)
+#endif // LIBC_TARGET_OS_IS_LINUX
 #else
-#define LIBC_SANITIZATION_CHECK(COND)                                          \
+// Call abort on assertion as it is required by standards like LSB. Calling exit
+// also confuses the debugger as exiting will not trigger debugger's
+// catch-unwind behavior by default.
+#define _LIBC_ASSERT(COND, MSG)                                                \
   do {                                                                         \
+    if (LIBC_UNLIKELY(!(COND))) {                                              \
+      LIBC_NAMESPACE::write_to_stderr(__FILE__ ":" LLVM_LIBC_STRINGIFY(        \
+          __LINE__) ": Assertion failed: '" MSG "' in function: '");           \
+      LIBC_NAMESPACE::write_to_stderr(__PRETTY_FUNCTION__);                    \
+      LIBC_NAMESPACE::write_to_stderr("'\n");                                  \
+      LIBC_NAMESPACE::abort_utils::abort();                                    \
+    }                                                                          \
   } while (false)
-#endif
+#endif // LIBC_FULL_BUILD
 
-#else // Not LIBC_COPT_USE_C_ASSERT
+//===----------------------------------------------------------------------===//
+// LIBC_ASSERT(COND) (NDEBUG guarded assertion)
+//===----------------------------------------------------------------------===//
 
-#include "src/__support/OSUtil/exit.h"
-#include "src/__support/OSUtil/io.h"
-#include "src/__support/integer_to_string.h"
-#include "src/__support/macros/attributes.h" // For LIBC_INLINE
-#include "src/__support/macros/config.h"
-#include "src/__support/macros/macro-utils.h"
-#include "src/__support/macros/optimization.h" // For LIBC_UNLIKELY
+#if defined(LIBC_COPT_USE_C_ASSERT) || !defined(LIBC_FULL_BUILD)
 
+// The build is configured to just use the public <assert.h> API
+// for libc's internal assertions.
+#ifndef LIBC_ASSERT
+#include <assert.h>
+#define LIBC_ASSERT(COND) assert(COND)
+#endif // LIBC_ASSERT
+
+#else // Not LIBC_COPT_USE_C_ASSERT
 namespace LIBC_NAMESPACE_DECL {
 
 // This is intended to be removed in a future patch to use a similar design to
@@ -70,52 +99,28 @@ LIBC_INLINE void report_assertion_failure(const char *assertion,
 #error "Unexpected: LIBC_ASSERT macro already defined"
 #endif
 
-// The public "assert" macro calls abort on failure. Should it be same here?
-// The libc internal assert can fire from anywhere inside the libc. So, to
-// avoid potential chicken-and-egg problems, it is simple to do an exit
-// on assertion failure instead of calling abort. We also don't want to use
-// __builtin_trap as it could potentially be implemented using illegal
-// instructions which can be very misleading when debugging.
 #ifdef NDEBUG
 #define LIBC_ASSERT(COND)                                                      \
   do {                                                                         \
   } while (false)
 #else
-
-#define LIBC_ASSERT(COND)                                                      \
-  do {                                                                         \
-    if (LIBC_UNLIKELY(!(COND))) {                                              \
-      LIBC_NAMESPACE::write_to_stderr(__FILE__ ":" LLVM_LIBC_STRINGIFY(        \
-          __LINE__) ": Assertion failed: '" #COND "' in function: '");         \
-      LIBC_NAMESPACE::write_to_stderr(__PRETTY_FUNCTION__);                    \
-      LIBC_NAMESPACE::write_to_stderr("'\n");                                  \
-      LIBC_NAMESPACE::internal::exit(0xFF);                                    \
-    }                                                                          \
-  } while (false)
+// Forward to _LIBC_ASSERT with the condition stringified.
+#define LIBC_ASSERT(COND) _LIBC_ASSERT(COND, #COND)
 #endif // NDEBUG
 
-#ifndef LIBC_COPT_ENABLE_SANITIZATION
-#define LIBC_COPT_ENABLE_SANITIZATION false
-#endif
-
-#if LIBC_COPT_ENABLE_SANITIZATION
-#define LIBC_SANITIZATION_CHECK(COND)                                          \
-  do {                                                                         \
-    if (LIBC_UNLIKELY(!(COND))) {                                              \
-      LIBC_NAMESPACE::write_to_stderr(__FILE__ ":" LLVM_LIBC_STRINGIFY(        \
-          __LINE__) ": Runtime sanitization failed: '" #COND                   \
-                    "' in function: '");                                       \
-      LIBC_NAMESPACE::write_to_stderr(__PRETTY_FUNCTION__);                    \
-      LIBC_NAMESPACE::write_to_stderr("'\n");                                  \
-      __builtin_trap();                                                        \
-    }                                                                          \
-  } while (false)
-#else
-#define LIBC_SANITIZATION_CHECK(COND)                                          \
-  do {                                                                         \
-  } while (false)
-#endif
-
 #endif // LIBC_COPT_USE_C_ASSERT
 
+//===----------------------------------------------------------------------===//
+// Hardening runtime check
+//===----------------------------------------------------------------------===//
+
+#if LIBC_COPT_HARDENING_MODE == LIBC_HARDENING_MODE_NONE
+#define LIBC_HEAP_INTEGRITY_CHECK(COND, MSG) ((void)0)
+#elif LIBC_COPT_HARDENING_MODE == LIBC_HARDENING_MODE_FAST
+#define LIBC_HEAP_INTEGRITY_CHECK(COND, MSG) ((void)0)
+#elif LIBC_COPT_HARDENING_MODE == LIBC_HARDENING_MODE_EXTENSIVE
+#define LIBC_HEAP_INTEGRITY_CHECK(COND, MSG) _LIBC_ASSERT(COND, MSG)
+#elif LIBC_COPT_HARDENING_MODE == LIBC_HARDENING_MODE_DEBUG
+#define LIBC_HEAP_INTEGRITY_CHECK(COND, MSG) _LIBC_ASSERT(COND, MSG)
+#endif
 #endif // LLVM_LIBC_SRC___SUPPORT_LIBC_ASSERT_H
diff --git a/libc/src/__support/macros/CMakeLists.txt b/libc/src/__support/macros/CMakeLists.txt
index d0fb102911f35..c9afc2829dbcb 100644
--- a/libc/src/__support/macros/CMakeLists.txt
+++ b/libc/src/__support/macros/CMakeLists.txt
@@ -54,3 +54,10 @@ add_header_library(
     .macro_utils
     libc.src.__support.CPP.string_view
 )
+
+add_header_library(
+  hardening
+  HDRS
+    hardening.h
+)
+
diff --git a/libc/src/__support/macros/hardening.h b/libc/src/__support/macros/hardening.h
new file mode 100644
index 0000000000000..3b0acf65fb291
--- /dev/null
+++ b/libc/src/__support/macros/hardening.h
@@ -0,0 +1,31 @@
+//===-- Hardening mode macros -----------------------------------*- 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 LLVM_LIBC_SRC___SUPPORT_MACROS_HARDENING_H
+#define LLVM_LIBC_SRC___SUPPORT_MACROS_HARDENING_H
+
+#define LIBC_HARDENING_MODE_NONE 0x0000'000A
+#define LIBC_HARDENING_MODE_FAST 0x0000'00B0
+#define LIBC_HARDENING_MODE_EXTENSIVE 0x0000'0C00
+#define LIBC_HARDENING_MODE_DEBUG 0x0000'D000
+
+#ifndef LIBC_COPT_HARDENING_MODE
+#define LIBC_COPT_HARDENING_MODE LIBC_HARDENING_MODE_NONE
+#endif
+
+#if (LIBC_COPT_HARDENING_MODE != LIBC_HARDENING_MODE_NONE &&                   \
+     LIBC_COPT_HARDENING_MODE != LIBC_HARDENING_MODE_FAST &&                   \
+     LIBC_COPT_HARDENING_MODE != LIBC_HARDENING_MODE_EXTENSIVE &&              \
+     LIBC_COPT_HARDENING_MODE != LIBC_HARDENING_MODE_DEBUG)
+#error                                                                         \
+    "LIBC_COPT_HARDENING_MODE must be defined with one of the following values: \
+LIBC_HARDENING_MODE_NONE, LIBC_HARDENING_MODE_FAST, \
+LIBC_HARDENING_MODE_EXTENSIVE, LIBC_HARDENING_MODE_DEBUG"
+#endif
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MACROS_HARDENING_H

>From ea6b32b1ea76ec096b34119eef56b2c9caf8388c Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Wed, 22 Jul 2026 11:20:58 -0700
Subject: [PATCH 5/8] fix

---
 libc/src/__support/freelist.h    |  6 ++++--
 libc/src/__support/freetrie.h    |  9 ++++++---
 libc/src/__support/libc_assert.h | 21 ++++++++++-----------
 3 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/libc/src/__support/freelist.h b/libc/src/__support/freelist.h
index 96710bd292850..560bbcc9d1242 100644
--- a/libc/src/__support/freelist.h
+++ b/libc/src/__support/freelist.h
@@ -44,8 +44,10 @@ class FreeList {
 
   protected:
     LIBC_INLINE void sanitize() const {
-      LIBC_SANITIZATION_CHECK(next->prev == this);
-      LIBC_SANITIZATION_CHECK(prev->next == this);
+      LIBC_HEAP_INTEGRITY_CHECK(next->prev == this,
+                                "FreeList node corruption detected");
+      LIBC_HEAP_INTEGRITY_CHECK(prev->next == this,
+                                "FreeList node corruption detected");
     }
 
   private:
diff --git a/libc/src/__support/freetrie.h b/libc/src/__support/freetrie.h
index 32f6a306f0c7e..a2adfacfa44e4 100644
--- a/libc/src/__support/freetrie.h
+++ b/libc/src/__support/freetrie.h
@@ -65,11 +65,14 @@ class FreeTrie {
     LIBC_INLINE void sanitize() const {
       FreeList::Node::sanitize();
       if (lower)
-        LIBC_SANITIZATION_CHECK(lower->parent == this);
+        LIBC_HEAP_INTEGRITY_CHECK(lower->parent == this,
+                                  "FreeTrie lower child corruption detected");
       if (upper)
-        LIBC_SANITIZATION_CHECK(upper->parent == this);
+        LIBC_HEAP_INTEGRITY_CHECK(upper->parent == this,
+                                  "FreeTrie upper child corruption detected");
       if (parent)
-        LIBC_SANITIZATION_CHECK(parent->lower == this || parent->upper == this);
+        LIBC_HEAP_INTEGRITY_CHECK(parent->lower == this || parent->upper == this,
+                                  "FreeTrie parent pointer corruption detected");
     }
   };
 
diff --git a/libc/src/__support/libc_assert.h b/libc/src/__support/libc_assert.h
index 5fa99b36c8ad3..f564779b31913 100644
--- a/libc/src/__support/libc_assert.h
+++ b/libc/src/__support/libc_assert.h
@@ -17,21 +17,18 @@
 #include "src/__support/macros/properties/os.h"
 
 #ifdef LIBC_FULL_BUILD
+#include "src/__support/OSUtil/exit.h"
 #include "src/__support/OSUtil/io.h"
 #include "src/__support/integer_to_string.h"
-#include "src/stdlib/abort_utils.h"
 #endif
 
 //===----------------------------------------------------------------------===//
 // _LIBC_ASSERT(COND, MSG) (always-on assert regardless of NDEBUG)
 //===----------------------------------------------------------------------===//
 #ifndef LIBC_FULL_BUILD
-#if LIBC_TARGET_OS_IS_LINUX
+#ifdef LIBC_TARGET_OS_IS_LINUX
 // __assert_fail is in LSB, hence we should always be able to use it here.
-extern "C" [[gnu::noreturn]] void __assert_fail(const char *assertion,
-                                                const char *filename,
-                                                uint32_t line,
-                                                const char *funcname);
+#include <assert.h>
 #define _LIBC_ASSERT(COND, MSG)                                                \
   do {                                                                         \
     if (LIBC_UNLIKELY(!(COND)))                                                \
@@ -46,9 +43,10 @@ extern "C" [[gnu::noreturn]] void __assert_fail(const char *assertion,
   } while (false)
 #endif // LIBC_TARGET_OS_IS_LINUX
 #else
-// Call abort on assertion as it is required by standards like LSB. Calling exit
-// also confuses the debugger as exiting will not trigger debugger's
-// catch-unwind behavior by default.
+// FIXME: Calling abort on assertion is actually required by standards like LSB.
+// Calling exit also confuses the debugger as exiting will not trigger
+// debugger's stop-on-signal behavior by default. Currently, adding abort will
+// result in cyclic dependency.
 #define _LIBC_ASSERT(COND, MSG)                                                \
   do {                                                                         \
     if (LIBC_UNLIKELY(!(COND))) {                                              \
@@ -56,7 +54,7 @@ extern "C" [[gnu::noreturn]] void __assert_fail(const char *assertion,
           __LINE__) ": Assertion failed: '" MSG "' in function: '");           \
       LIBC_NAMESPACE::write_to_stderr(__PRETTY_FUNCTION__);                    \
       LIBC_NAMESPACE::write_to_stderr("'\n");                                  \
-      LIBC_NAMESPACE::abort_utils::abort();                                    \
+      LIBC_NAMESPACE::internal::exit(0xFF);                                    \
     }                                                                          \
   } while (false)
 #endif // LIBC_FULL_BUILD
@@ -64,7 +62,6 @@ extern "C" [[gnu::noreturn]] void __assert_fail(const char *assertion,
 //===----------------------------------------------------------------------===//
 // LIBC_ASSERT(COND) (NDEBUG guarded assertion)
 //===----------------------------------------------------------------------===//
-
 #if defined(LIBC_COPT_USE_C_ASSERT) || !defined(LIBC_FULL_BUILD)
 
 // The build is configured to just use the public <assert.h> API
@@ -122,5 +119,7 @@ LIBC_INLINE void report_assertion_failure(const char *assertion,
 #define LIBC_HEAP_INTEGRITY_CHECK(COND, MSG) _LIBC_ASSERT(COND, MSG)
 #elif LIBC_COPT_HARDENING_MODE == LIBC_HARDENING_MODE_DEBUG
 #define LIBC_HEAP_INTEGRITY_CHECK(COND, MSG) _LIBC_ASSERT(COND, MSG)
+#else
+#error "Unsupported hardening mode"
 #endif
 #endif // LLVM_LIBC_SRC___SUPPORT_LIBC_ASSERT_H

>From 9f3844c2e1fffe634640b999d0fb72a69f454d44 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Wed, 22 Jul 2026 11:29:59 -0700
Subject: [PATCH 6/8] remove wrong check

---
 libc/src/__support/freetrie.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libc/src/__support/freetrie.cpp b/libc/src/__support/freetrie.cpp
index 9738c4635e581..d5069dfc39675 100644
--- a/libc/src/__support/freetrie.cpp
+++ b/libc/src/__support/freetrie.cpp
@@ -49,7 +49,6 @@ void FreeTrie::remove(Node *node) {
 
 void FreeTrie::replace_node(Node *node, Node *new_node) {
   LIBC_ASSERT(is_head(node) && "only head nodes contain trie links");
-  node->sanitize();
 
   if (node->parent) {
     Node *&parent_child =

>From 4eafb26619111fcba954f26ea53831c595814336 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Wed, 22 Jul 2026 11:42:04 -0700
Subject: [PATCH 7/8] rename

---
 libc/src/__support/freelist.cpp                |  8 ++++----
 libc/src/__support/freelist.h                  |  4 ++--
 libc/src/__support/freelist_heap.h             |  2 +-
 libc/src/__support/freestore.h                 |  8 ++++----
 libc/src/__support/freetrie.cpp                | 16 ++++++++--------
 libc/src/__support/freetrie.h                  | 10 +++++-----
 libc/test/src/__support/freelist_heap_test.cpp | 12 ++++++------
 7 files changed, 30 insertions(+), 30 deletions(-)

diff --git a/libc/src/__support/freelist.cpp b/libc/src/__support/freelist.cpp
index 133d2da1104f9..f20866cc4ecbe 100644
--- a/libc/src/__support/freelist.cpp
+++ b/libc/src/__support/freelist.cpp
@@ -20,7 +20,7 @@ void FreeList::push(Node *node) {
     LIBC_ASSERT(BlockRef::from_usable_space(node).outer_size() ==
                     begin_->block().outer_size() &&
                 "freelist entries must have the same size");
-    begin_->sanitize();
+    begin_->integrity_check();
     // Since the list is circular, insert the node immediately before begin_.
     node->prev = begin_->prev;
     node->next = begin_;
@@ -33,7 +33,7 @@ void FreeList::push(Node *node) {
 
 void FreeList::remove(Node *node) {
   LIBC_ASSERT(begin_ && "cannot remove from empty list");
-  node->sanitize();
+  node->integrity_check();
   Node *next = node->next;
   if (node == next) {
     LIBC_ASSERT(node == begin_ &&
@@ -48,12 +48,12 @@ void FreeList::remove(Node *node) {
   }
 }
 
-void FreeList::sanitize() const {
+void FreeList::integrity_check() const {
   if (!begin_)
     return;
   Node *curr = begin_;
   do {
-    curr->sanitize();
+    curr->integrity_check();
     curr = curr->next;
   } while (curr != begin_);
 }
diff --git a/libc/src/__support/freelist.h b/libc/src/__support/freelist.h
index 560bbcc9d1242..1775e8d2ab496 100644
--- a/libc/src/__support/freelist.h
+++ b/libc/src/__support/freelist.h
@@ -43,7 +43,7 @@ class FreeList {
     LIBC_INLINE size_t size() const { return block().inner_size(); }
 
   protected:
-    LIBC_INLINE void sanitize() const {
+    LIBC_INLINE void integrity_check() const {
       LIBC_HEAP_INTEGRITY_CHECK(next->prev == this,
                                 "FreeList node corruption detected");
       LIBC_HEAP_INTEGRITY_CHECK(prev->next == this,
@@ -95,7 +95,7 @@ class FreeList {
   void remove(Node *node);
 
   /// Verify integrity of all nodes in the list.
-  void sanitize() const;
+  void integrity_check() const;
 
 private:
   Node *begin_;
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index 1b9e5f08ffff3..d2ec9339d72ed 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -51,7 +51,7 @@ class FreeListHeap {
   void *realloc(void *ptr, size_t size);
   void *calloc(size_t num, size_t size);
   size_t allocation_size(const void *ptr) const;
-  LIBC_INLINE void sanitize() const { free_store.sanitize(); }
+  LIBC_INLINE void integrity_check() const { free_store.integrity_check(); }
 
   cpp::span<cpp::byte> region() const { return {begin, end}; }
 
diff --git a/libc/src/__support/freestore.h b/libc/src/__support/freestore.h
index 9a9b084741377..ea48465a7e91d 100644
--- a/libc/src/__support/freestore.h
+++ b/libc/src/__support/freestore.h
@@ -46,11 +46,11 @@ class FreeStore {
   /// allocated. Returns nullptr if there is no such block.
   BlockRef remove_best_fit(size_t size);
 
-  /// Sanitization check for the entire store.
-  LIBC_INLINE void sanitize() const {
-    large_trie.sanitize();
+  /// Integrity check for the entire store.
+  LIBC_INLINE void integrity_check() const {
+    large_trie.integrity_check();
     for (const FreeList &list : small_lists)
-      list.sanitize();
+      list.integrity_check();
   }
 
 private:
diff --git a/libc/src/__support/freetrie.cpp b/libc/src/__support/freetrie.cpp
index d5069dfc39675..72d35aed44baa 100644
--- a/libc/src/__support/freetrie.cpp
+++ b/libc/src/__support/freetrie.cpp
@@ -13,7 +13,7 @@ namespace LIBC_NAMESPACE_DECL {
 
 void FreeTrie::remove(Node *node) {
   LIBC_ASSERT(!empty() && "cannot remove from empty trie");
-  node->sanitize();
+  node->integrity_check();
   FreeList list = node;
   list.pop();
   Node *new_node = static_cast<Node *>(list.begin());
@@ -23,10 +23,10 @@ void FreeTrie::remove(Node *node) {
     // root and its children.
     Node *leaf = node;
     while (leaf->lower || leaf->upper) {
-      leaf->sanitize();
+      leaf->integrity_check();
       leaf = leaf->lower ? leaf->lower : leaf->upper;
     }
-    leaf->sanitize();
+    leaf->integrity_check();
     if (leaf == node) {
       // If the root is a leaf, then removing it empties the subtrie.
       replace_node(node, nullptr);
@@ -66,17 +66,17 @@ void FreeTrie::replace_node(Node *node, Node *new_node) {
     node->upper->parent = new_node;
 }
 
-void FreeTrie::sanitize() const {
-  auto sanitize_trie_node = [&](auto &self, const Node *node) -> void {
+void FreeTrie::integrity_check() const {
+  auto integrity_check_trie_node = [&](auto &self, const Node *node) -> void {
     if (!node)
       return;
-    node->sanitize();
+    node->integrity_check();
     FreeList list = const_cast<Node *>(node);
-    list.sanitize();
+    list.integrity_check();
     self(self, node->lower);
     self(self, node->upper);
   };
-  sanitize_trie_node(sanitize_trie_node, root);
+  integrity_check_trie_node(integrity_check_trie_node, root);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/freetrie.h b/libc/src/__support/freetrie.h
index a2adfacfa44e4..627b36412cd45 100644
--- a/libc/src/__support/freetrie.h
+++ b/libc/src/__support/freetrie.h
@@ -62,8 +62,8 @@ class FreeTrie {
 
     friend class FreeTrie;
 
-    LIBC_INLINE void sanitize() const {
-      FreeList::Node::sanitize();
+    LIBC_INLINE void integrity_check() const {
+      FreeList::Node::integrity_check();
       if (lower)
         LIBC_HEAP_INTEGRITY_CHECK(lower->parent == this,
                                   "FreeTrie lower child corruption detected");
@@ -125,7 +125,7 @@ class FreeTrie {
   Node *find_best_fit(size_t size);
 
   /// Verify integrity of all nodes in the trie.
-  void sanitize() const;
+  void integrity_check() const;
 
 private:
   /// @returns Whether a node is the head of its containing freelist.
@@ -152,7 +152,7 @@ LIBC_INLINE void FreeTrie::push(BlockRef block) {
   while (*cur && (*cur)->size() != size) {
     LIBC_ASSERT(cur_range.contains(size) && "requested size out of trie range");
     parent = *cur;
-    (*cur)->sanitize();
+    (*cur)->integrity_check();
     if (size <= cur_range.lower().max()) {
       cur = &(*cur)->lower;
       cur_range = cur_range.lower();
@@ -185,7 +185,7 @@ LIBC_INLINE FreeTrie::Node *FreeTrie::find_best_fit(size_t size) {
   FreeTrie::SizeRange deferred_upper_range{0, 0};
 
   while (true) {
-    cur->sanitize();
+    cur->integrity_check();
     LIBC_ASSERT(cur_range.contains(cur->size()) &&
                 "trie node size out of range");
     LIBC_ASSERT(cur_range.max() >= size &&
diff --git a/libc/test/src/__support/freelist_heap_test.cpp b/libc/test/src/__support/freelist_heap_test.cpp
index 4667eedcda599..e7858c742f4a6 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -367,18 +367,18 @@ TEST_FOR_EACH_ALLOCATOR(AllocationSize, 2048) {
   EXPECT_EQ(allocator.allocation_size(ptr), size_t(0));
 }
 
-TEST_FOR_EACH_ALLOCATOR(CanSanitize, 2048) {
-  allocator.sanitize();
+TEST_FOR_EACH_ALLOCATOR(IntegrityCheck, 2048) {
+  allocator.integrity_check();
 
   void *ptr1 = allocator.allocate(512);
-  allocator.sanitize();
+  allocator.integrity_check();
 
   void *ptr2 = allocator.allocate(512);
-  allocator.sanitize();
+  allocator.integrity_check();
 
   allocator.free(ptr1);
-  allocator.sanitize();
+  allocator.integrity_check();
 
   allocator.free(ptr2);
-  allocator.sanitize();
+  allocator.integrity_check();
 }

>From fc0fb294e1df6dd44a8b8cc9b4c76361bf343b0c Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Wed, 22 Jul 2026 11:56:51 -0700
Subject: [PATCH 8/8] add death test

---
 libc/src/__support/macros/hardening.h         | 12 ++---
 libc/test/src/__support/CMakeLists.txt        | 12 +++++
 .../__support/freelist_heap_death_test.cpp    | 48 +++++++++++++++++++
 3 files changed, 64 insertions(+), 8 deletions(-)
 create mode 100644 libc/test/src/__support/freelist_heap_death_test.cpp

diff --git a/libc/src/__support/macros/hardening.h b/libc/src/__support/macros/hardening.h
index 3b0acf65fb291..8ea4b3070bfc5 100644
--- a/libc/src/__support/macros/hardening.h
+++ b/libc/src/__support/macros/hardening.h
@@ -9,14 +9,10 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_MACROS_HARDENING_H
 #define LLVM_LIBC_SRC___SUPPORT_MACROS_HARDENING_H
 
-#define LIBC_HARDENING_MODE_NONE 0x0000'000A
-#define LIBC_HARDENING_MODE_FAST 0x0000'00B0
-#define LIBC_HARDENING_MODE_EXTENSIVE 0x0000'0C00
-#define LIBC_HARDENING_MODE_DEBUG 0x0000'D000
-
-#ifndef LIBC_COPT_HARDENING_MODE
-#define LIBC_COPT_HARDENING_MODE LIBC_HARDENING_MODE_NONE
-#endif
+#define LIBC_HARDENING_MODE_NONE 0x0000'000F
+#define LIBC_HARDENING_MODE_FAST 0x0000'00F0
+#define LIBC_HARDENING_MODE_EXTENSIVE 0x0000'F00
+#define LIBC_HARDENING_MODE_DEBUG 0x0000'F000
 
 #if (LIBC_COPT_HARDENING_MODE != LIBC_HARDENING_MODE_NONE &&                   \
      LIBC_COPT_HARDENING_MODE != LIBC_HARDENING_MODE_FAST &&                   \
diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index 8233529266326..3a8de60288a01 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -71,6 +71,18 @@ if(LLVM_LIBC_FULL_BUILD AND NOT LIBC_TARGET_OS_IS_GPU)
       libc.src.string.memcpy
       libc.src.string.memory_utils.inline_memset
   )
+
+  add_libc_test(
+    freelist_heap_death_test
+    SUITE
+      libc-support-tests
+    SRCS
+      freelist_heap_death_test.cpp
+    DEPENDS
+      libc.src.__support.CPP.span
+      libc.src.__support.freelist_heap
+    UNIT_TEST_ONLY
+  )
 endif()
 
 add_libc_test(
diff --git a/libc/test/src/__support/freelist_heap_death_test.cpp b/libc/test/src/__support/freelist_heap_death_test.cpp
new file mode 100644
index 0000000000000..cdfe6033ffe23
--- /dev/null
+++ b/libc/test/src/__support/freelist_heap_death_test.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Death tests for freelist_heap.
+///
+//===----------------------------------------------------------------------===//
+#include "src/__support/freelist_heap.h"
+#include "test/UnitTest/Test.h"
+
+// Just a stub, not really used
+asm(".globl _end, __llvm_libc_heap_limit\n_end:\n__llvm_libc_heap_limit:\n");
+
+using LIBC_NAMESPACE::FreeListHeap;
+using LIBC_NAMESPACE::cpp::byte;
+
+#if defined(ENABLE_SUBPROCESS_TESTS) && LIBC_COPT_HARDENING_MODE > LIBC_HARDENING_MODE_FAST
+TEST(LlvmLibcFreeListHeapDeathTest, DoubleFreeDeath) {
+  byte buf[2048] = {byte(0)};
+  FreeListHeap allocator(buf);
+
+  void *ptr = allocator.allocate(512);
+  ASSERT_NE(ptr, static_cast<void *>(nullptr));
+  allocator.free(ptr);
+  EXPECT_EXITS(
+      [&] {
+        allocator.free(ptr);
+        allocator.integrity_check();
+      },
+      255);
+}
+
+TEST(LlvmLibcFreeListHeapDeathTest, UseAfterFreeDeath) {
+  byte buf[2048] = {byte(0)};
+  FreeListHeap allocator(buf);
+
+  void *ptr = allocator.allocate(512);
+  ASSERT_NE(ptr, static_cast<void *>(nullptr));
+  allocator.free(ptr);
+  *reinterpret_cast<uintptr_t *>(ptr) = 0xDEADBEEF;
+  EXPECT_EXITS([&] { allocator.integrity_check(); }, 255);
+}
+#endif // ENABLE_SUBPROCESS_TESTS



More information about the libc-commits mailing list