[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