Friday, November 6, 2026

Inside hardened_malloc: how GrapheneOS rebuilds the heap for exploit resistance

Inside hardened_malloc: how GrapheneOS rebuilds the heap for exploit resistance

hardened_malloc is a security-focused memory allocator from GrapheneOS that replaces the standard malloc implementation to harden 64-bit Linux and Android systems against heap exploitation.

ToolGrapheneOS/hardened_malloc — hardened malloc allocator focused on mitigating heap corruption, with Bionic, musl and glibc support
CategoryMemory allocator / exploit mitigation hardening library (C)
Primary UseReplacing the standard malloc via libc integration or LD_PRELOAD-style loading of libhardened_malloc.so to defend against heap corruption exploitation on 64-bit systems
Safe UseHardening your own systems, distros and Android builds; defensive research and lab testing of allocator-level mitigations in authorized environments
Telemetry NotePurely defensive library — it leaves no network traces; defenders observe it via ld.so.preload contents, loaded libhardened_malloc.so mappings, and a greatly elevated vm.max_map_count from guard pages

hardened_malloc is the security-focused general-purpose allocator from the GrapheneOS project, written in C and designed to provide the standard malloc API plus extensions while substantially hardening against heap corruption vulnerabilities. Unlike mainstream allocators that treat exploitation resistance as an afterthought, this project puts hardening at the center of the design, and the README is unusually candid about the tradeoffs: performance and memory usage are deliberately sacrificed for security in the default build. It currently supports Bionic on Android, musl, and glibc, with the strongest integration on Android where custom hooks into the libc layer enable additional hardening.

The lineage matters for understanding the architecture. This is a successor to an earlier effort that extended OpenBSD malloc with extra security features, and the design remains heavily based on OpenBSD's allocator — though the code was written fresh, reusing only the hash table implementation. The key departures are a sole focus on hardening rather than bug finding, finer-grained size classes with slab sizes beyond 4k to reduce internal fragmentation, no reliance on fine-grained kernel mmap randomization, and a 64-bit-only target so the allocator can aggressively exploit the large address space. For 32-bit systems the maintainers point back at a port of OpenBSD malloc with small extensions instead.

The size-class partitioning is where the memory-safety engineering shows. Per-size-class memory regions get their own random bases, an approach the README credits as loosely inspired by PartitionAlloc's size and type-based partitioning. Combined with guard slabs and large allocation guard regions, this means corruption in one size class cannot easily be leveraged against metadata or objects in another. The design also carries forward the earlier OpenBSD extensions: padding for canaries distinct from OpenBSD's current scheme, write-after-free detection tied to clearing on free, randomized quarantine queues and arrays, and proper double-free detection for quarantined allocations.

From a scalability standpoint, the allocator offers a configurable number of entirely independent arenas, with internal locking within arenas further divided per size class. The README frames the performance goal as decent long-term behavior under real workloads rather than allocator micro-benchmarks — a refreshing stance in a space where benchmarks routinely reward caching at the cost of isolation. Notably, there is no thread caching by default; the security posture takes priority over the per-thread fast paths that mainstream allocators depend on for headline numbers.

Deployment on traditional Linux is straightforward and well documented. The library builds as libhardened_malloc.so, which can be integrated directly into a libc build or, more practically for testing, added as a full path line to /etc/ld.so.preload. The README advises installing to /usr/lib/ for maximum compatibility, since AppArmor profiles and systemd ExecPaths= restrictions can silently break preloads from /usr/local/lib/ — it even shows the exact ld.so error format to watch for. Using the LD_PRELOAD environment variable is explicitly discouraged for production and will not work at all for setuid binaries where AT_SECURE is set.

A practical gotcha that will bite anyone evaluating this in a lab: the guard slabs and guard regions generate a very large number of memory mappings, so vm.max_map_count must be raised substantially — the README suggests 1048576 via a sysctl.d file or init.rc on Android. Alternatively, CONFIG_GUARD_SLABS_INTERVAL can be increased to reduce mapping count, though the project notes the feature has low overhead and recommends against weakening it. On arm64, kernels must use 4k pages and 4-level page tables covering the full 48-bit address space for the default configuration to work.

Configuration is compile-time only, driven through make arguments, and built around a template system with two presets: config/default.mk and config/light.mk, selected via make VARIANT=light. The default preset enables essentially all optional security features — only the niche CONFIG_SEAL_METADATA is off — and is described as aggressive in trading performance and memory for security. The light preset disables slab quarantines, the write-after-free check and slot randomization, and raises the guard slab interval from 1 to 8, while keeping zero-on-free and slab canaries. Even light is positioned as far more secure than mainstream allocators, and packagers are encouraged to ship both variants as libhardened_malloc.so and libhardened_malloc-light.so.

For memory-constrained deployments, the README suggests make CONFIG_N_ARENA=1 CONFIG_EXTENDED_SIZE_CLASSES=false, which also shrinks empty slab caches and quarantines since those are sized from the largest size class. On the question of weakening protections further, the maintainers draw a clear line: disabling slab canaries removes the strict upgrade over traditional allocators with headers and basic consistency checks, and is not recommended in most cases since canaries are what mitigate small overflows and C string overflows.

Testing is covered from two angles. Individual applications can be exercised through the bundled preload.sh wrapper, which launches dynamically linked executables against the allocator, and an automated suite runs with make test. One compatibility note worth flagging: OpenSSH 8.1 or higher is required because the seccomp-bpf filter must permit mprotect with PROT_READ|PROT_WRITE rather than killing the process — a detail that tells you the allocator actively re-protects memory regions during operation.

On Android, GrapheneOS ships hardened_malloc as the standard allocator inside its Bionic libc, and other Android-based operating systems can reuse the referenced Bionic integration commit. There is even a documented path to keep jemalloc as a runtime choice, though the README warns this weakens security for threat models where persistent state is untrusted, tying into the project's attestation work. Dependency policy is strict and honest: Debian stable pins the floor (glibc 2.41, Linux 6.12, Clang 19.1.7 or GCC 14.2.0), Android support tracks the active android17-release branch, and older versions are explicitly unsupported.

For defenders and hardening engineers, the interest is twofold. First, it is a real-world, production-proven reference for what allocator-level exploit mitigation looks like when done without compromise — canaries, quarantine, zero-on-free, randomization and per-size-class isolation all in one coherent design, MIT-licensed at GrapheneOS/hardened_malloc with roughly two thousand stars. Second, it is directly deployable: rolling it into a hardened distro, an Android build, or a targeted namespace for risky services raises the bar meaningfully against heap exploitation of memory-unsafe code. For authorized security teams assessing the residual risk of legacy C and C++ services, it is one of the few drop-in mitigations that operates below the application layer without touching a line of application code.

Official project repository for GrapheneOS/hardened_malloc.
Download Tool

Educational analysis for authorized security professionals. Use only in controlled, authorized environments.

Share articleFacebookXLinkedIn

Continue exploring

Browse all articles →

0 comentários:

Post a Comment

Note: Only a member of this blog may post a comment.