[libc-commits] [libc] [libc] add option for hardened freelist (PR #205382)

Daniel Thornburgh via libc-commits libc-commits at lists.llvm.org
Tue Jul 14 16:47:59 PDT 2026


================
@@ -15,9 +15,110 @@
 #define LLVM_LIBC_SRC___SUPPORT_FREELIST_H
 
 #include "block.h"
+#include "hdr/stdint_proxy.h"
+#include "src/__support/CPP/bit.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/config.h"
+
+#ifndef LIBC_COPT_HARDEN_FREELIST
+#define LIBC_COPT_HARDEN_FREELIST false
+#endif
+
+#if LIBC_COPT_HARDEN_FREELIST
+#define LIBC_HARDENING_ASSERT(cond)                                            \
+  do {                                                                         \
+    if (LIBC_UNLIKELY(!(cond))) {                                              \
+      __builtin_trap();                                                        \
+    }                                                                          \
+  } while (0)
+#else
+#define LIBC_HARDENING_ASSERT(cond) LIBC_ASSERT(cond)
+#endif
 
 namespace LIBC_NAMESPACE_DECL {
 
+/// Secrets used to encrypt and verify forward and backward pointers in free
+/// lists when hardening is enabled.
+///
+/// When hardening is enabled (LIBC_COPT_HARDEN_FREELIST), we use XOR and
+/// bit-rotation encoding to protect the forward (`next`) and backward (`prev`)
+/// pointers in free list nodes. This encoding mechanism protects heap integrity
+/// against memory corruption and software bugs.
+///
+/// Threat Model and Security Guarantees:
+/// To effectively protect against intentional attackers who might attempt to
+/// infer the secret keys from memory, this hardening mechanism relies on the
+/// following assumptions:
+///
+/// - Header Separation: We assume that the allocator metadata (the heap header
+///   and secret keys) is isolated from dynamic data memory. This ensures that a
+///   linear buffer overflow read cannot easily dump the secret keys from
+///   adjacent memory.
+///
+/// - Allocation Randomness: We assume that allocation and deallocation patterns
+///   have sufficient randomness or diversity such that an attacker cannot
+///   reliably predict the exact memory layout of free list nodes. If an
+///   attacker could reliably guess node addresses and read forward/backward
+///   pointers via local overflow reads, they could XOR known addresses against
+///   encoded pointers to derive the secret keys.
+struct FreeListSecrets {
+  static constexpr int NODE_PTR_ROTATE_DISTANCE = 17;
+
+#if LIBC_COPT_HARDEN_FREELIST
+  uintptr_t k0;
+  uintptr_t k1;
+  uintptr_t k2;
+
+  LIBC_INLINE constexpr FreeListSecrets(uintptr_t k0, uintptr_t k1,
+                                        uintptr_t k2)
+      : k0(k0), k1(k1), k2(k2) {}
+  LIBC_INLINE constexpr FreeListSecrets() : k0(0), k1(0), k2(0) {}
+#else
+  LIBC_INLINE constexpr FreeListSecrets() = default;
+#endif
+
+  template <typename T> LIBC_INLINE T *decrypt_next(T *next_val) const {
+#if LIBC_COPT_HARDEN_FREELIST
+    return reinterpret_cast<T *>(reinterpret_cast<uintptr_t>(next_val) ^ k0);
+#else
+    return next_val;
+#endif
+  }
+
+  template <typename T>
+  LIBC_INLINE T *decrypt_prev([[maybe_unused]] const void *node,
+                              T *prev_val) const {
+#if LIBC_COPT_HARDEN_FREELIST
+    uintptr_t val = reinterpret_cast<uintptr_t>(prev_val) ^ k2 ^
+                    reinterpret_cast<uintptr_t>(node);
+    val = cpp::rotl(val, NODE_PTR_ROTATE_DISTANCE);
+    return reinterpret_cast<T *>(val ^ k1);
+#else
+    return prev_val;
+#endif
+  }
+
+  template <typename T> LIBC_INLINE T *encrypt_next(T *next_val) const {
----------------
mysterymath wrote:

nit: I think keeping encrypt and decrypt in pairs would make the semantics more obvious; in particular, that next is a simple XOR encryption with a one time pad.

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


More information about the libc-commits mailing list