[libc-commits] [libc] [libc] introduce sanitization check and heap walk utilities (PR #210373)

via libc-commits libc-commits at lists.llvm.org
Fri Jul 17 09:46:57 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Schrodinger ZHU Yifan (SchrodingerZhu)

<details>
<summary>Changes</summary>

This PR originates from #<!-- -->205382. 

Heap sanitization helps us to proactively detect UAF and DF. According to the discussion in #<!-- -->205382, we separate this feature out from hardening concerns.

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


8 Files Affected:

- (modified) libc/cmake/modules/LLVMLibCCompileOptionRules.cmake (+4) 
- (modified) libc/config/config.json (+4) 
- (modified) libc/src/__support/freelist.cpp (+12) 
- (modified) libc/src/__support/freelist.h (+10) 
- (modified) libc/src/__support/freestore.h (+7) 
- (modified) libc/src/__support/freetrie.cpp (+20-1) 
- (modified) libc/src/__support/freetrie.h (+16) 
- (modified) libc/src/__support/libc_assert.h (+38) 


``````````diff
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/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 &&
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

``````````

</details>


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


More information about the libc-commits mailing list