Library / SDK
GrapheneOS/hardened_malloc avatar
GrapheneOS/hardened_malloc

hardened_malloc: the GrapheneOS security allocator for Android and Linux

Hardened allocator designed for modern systems. It has integration into Android's Bionic libc and can be used externally with musl and glibc as a dynamic library for use on other Linux-based platforms. It will gain more portability/integration over time.

2,024 stars166 forksCMIT

At a glance

What is it?
hardened_malloc is a security-focused C allocator from the GrapheneOS project that replaces the system malloc on Android and Linux with hardening against heap corruption vulnerabilities. It trades some allocation throughput for quarantine queues, per-class random bases and canary detection, and targets 64-bit systems only.
Who is it for?
Adopt hardened_malloc on any 64-bit Android or Linux system where resistance to heap corruption matters more than peak allocation throughput. On Android, integrating through the Bionic path gives the deepest hardening; confirm the current supported AOSP branch before starting, since only the actively developed generation is in scope.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A malloc replacement for hardening, not for micro-benchmark wins

The allocator that ships inside GrapheneOS serves a different goal than jemalloc or tcmalloc. Those allocators are optimized for throughput on multi-threaded workloads, and their benchmarks reflect that. GrapheneOS/hardened_malloc is built around the opposite priority: the introduction calls it "a security-focused general purpose memory allocator providing the malloc API along with various extensions" that gives "substantial hardening against heap corruption vulnerabilities." The word used for performance is "decent," and the design targets "long-term performance and memory usage rather than allocator micro-benchmarks."

That framing matters for adoption decisions. This allocator is not a drop-in speed improvement. It is a hardening layer that trades some allocation throughput for meaningful resistance to a class of memory safety vulnerabilities.

It currently supports three libc targets: Bionic (Android), musl and glibc. It runs only on 64-bit systems. That constraint is not a version gap waiting to close. The introduction says that for 32-bit systems, "a port of OpenBSD malloc with small extensions can be used instead as this allocator fundamentally doesn't support that environment." The 64-bit requirement follows from the design: aggressive use of address space is what makes several of the security mechanisms viable.

Guard slabs, quarantine queues, canaries and per-class random bases

The allocator's hardening comes from several mechanisms named in the introduction. Each allocation can carry padding for canaries, enabling write-after-free detection that is tied to the existing clearing-on-free behavior. These canaries are distinct from those in current OpenBSD malloc.

Freed allocations pass through a quarantine that uses both randomized arrays and queues: two data structures holding recently freed memory before it can be reallocated. For allocations held in that quarantine, double-free detection is explicit. A use-after-free that modifies an allocation will be caught at some point during the quarantine phase; a double-free will be detected immediately for quarantined blocks.

Per-size-class memory regions each have their own randomly chosen base address. The introduction says this "was loosely inspired by the size and type-based partitioning in PartitionAlloc." The result: exploiting a heap vulnerability requires knowing not just the general heap layout but the specific region base for the targeted size class, each independently randomized.

Size classes are finer-grained than in OpenBSD malloc, and slab sizes extend beyond 4 KB. This reduces internal fragmentation compared to the 4K-aligned approach in the OpenBSD design the project descended from.

Testing with preload.sh before full OS integration

For engineers who want to test hardened_malloc without integrating it into a libc or rebuilding a distribution, the repository includes preload.sh. The script tests with dynamically linked executables using glibc or musl. The README gives this example:

bash
./preload.sh krita --new-image RGBA,U8,500,500

That line prepends the allocator to a process via LD_PRELOAD before running the named application, letting you observe its behavior with a real workload before committing to OS-level integration. Any dynamically linked binary can replace krita in that command.

One operational requirement applies: the guard slabs and large allocation guard regions create a large number of virtual memory mappings. The README calls for substantially increasing vm.max_map_count to accommodate them. The interval can be reduced with CONFIG_GUARD_SLABS_INTERVAL, but the README says this feature carries "a low performance and memory usage cost so that isn't recommended."

For automated regression testing, the repository provides a test suite:

bash
make test

The Makefile compiles the library with -O3, LTO, and several hardening-focused compiler flags: -fstack-clash-protection, -fstack-protector-strong, -fno-plt and -fvisibility=hidden. Those flags apply to the allocator itself, not just the applications it serves.

Android integration through Bionic: init.rc, guard pages and the AOSP branch constraint

On GrapheneOS, the allocator is built directly into Bionic as the standard malloc implementation. Other Android-based operating systems can adopt it by reusing the integration code published in a separate platform_bionic commit. Integrating into the C standard library rather than preloading gives access to additional hardening features that are not available through the preload path.

For Android integration, vm.max_map_count must be raised to handle the number of guard pages. The README gives the init.rc placement, near the other virtual memory configuration in system/core/rootdir/init.rc:

bash
write /proc/sys/vm/max_map_count 1048576

This step is unnecessary only if CONFIG_GUARD_SLABS_INTERVAL is set to a very large value.

A strict constraint governs AOSP support: the repository supports only the current actively developed maintenance branch of the Android Open Source Project. At this time that means android17-release. Older AOSP branches are not supported. The introduction is specific: only the current generation is in scope.

If jemalloc is left as a runtime option for users alongside hardened_malloc, the introduction says this "reduces security for threat models where persistent state is untrusted," which includes systems using verified boot and attestation. On those systems, removing the jemalloc fallback is the recommended path.

glibc gets a malloc swap; musl gets deeper integration and further hardening

The introduction creates an explicit architectural hierarchy between glibc and musl. Glibc support will stop at malloc replacement: the reasoning given is that musl is a cleaner base for the planned C standard library work and can cover the same use cases as glibc. For musl, the roadmap names custom integration for better performance, plus other hardening for the C standard library itself. For glibc, the scope stops at malloc replacement, and no further hardening is planned.

That is a stated architectural decision, not a temporary gap. The introduction's view that musl can "cover the same use cases" as glibc signals the project's position: for a security-focused system, musl is the preferred base.

For engineers running glibc-based Linux systems, the practical consequence is that the preload.sh path works today, the allocator replaces malloc, and the guard mechanisms are active. What is not coming for glibc is the deeper C library integration that musl will receive. For those already committed to glibc, the allocator still provides meaningful value; it just stops at the allocator boundary.

Where hardened_malloc is the wrong tool: 32-bit, strict version floors and OpenSSH

The 32-bit exclusion is fundamental. The introduction says this allocator "fundamentally doesn't support" 32-bit environments. For 32-bit targets, a port of OpenBSD malloc with small extensions is the suggested path. This is not a planned feature; the 64-bit-only design is what makes the address space-dependent security mechanisms viable.

The dependency floor is strict. The repository targets Debian 13 as the oldest supported configuration: glibc 2.41, Linux 6.12, Clang 19.1.7 or GCC 14.2.0. The README is specific: older versions may be compatible at the moment but are not tested and "will explicitly not be supported." For musl, the minimum is musl 1.1.20. For Android, only GKI branches 6.1, 6.6 and 6.12 are supported.

A separate compatibility constraint comes from OpenSSH: version 8.1 or higher is required to allow the mprotect PROT_READ|PROT_WRITE system calls in the seccomp-bpf filter. Earlier OpenSSH versions kill the process when those calls are made, so this is a hard prerequisite for environments where SSH access goes through seccomp filtering.

Anything 32-bit, running on Debian 12 or older, or pairing with OpenSSH below 8.1 is outside supported scope.

Thread caching by design, MIT terms, and the OpenBSD malloc lineage

The table of contents includes a section titled "Thread caching (or lack thereof)." That parenthetical is a design statement. Thread-local caches are how jemalloc and tcmalloc achieve a significant portion of their performance on high-allocation-rate workloads. This allocator removes that mechanism entirely. Scalability comes instead from a configurable number of fully independent arenas with per-size-class locking within each arena.

The allocator's heritage traces to OpenBSD malloc. The design is based on OpenBSD malloc's approach but not its code, except for the hash table implementation which is reused directly. The main architectural departures are the sole focus on hardening rather than bug-finding, finer-grained size classes with slab sizes beyond 4k, no reliance on kernel mmap randomization, and the 64-bit-only target. The padding canaries, write-after-free detection, quarantine queues and double-free detection for quarantined allocations are all carried over from the previous OpenBSD-based work.

The code is published under the MIT terms. Version 14 was published on 2026-02-22, with version 13 on 2024-10-14 and version 12 on 2024-08-06. The gap between versions 12 and 13 was roughly ten weeks; between 13 and 14 it was roughly four months. Upgrading means rebuilding against the new source, and the strict version floor on dependencies means a system running older kernel or compiler versions will need to update those first.

Editorial conclusion

Adopt hardened_malloc on any 64-bit Android or Linux system where resistance to heap corruption matters more than peak allocation throughput. On Android, integrating through the Bionic path gives the deepest hardening; confirm the current supported AOSP branch before starting, since only the actively developed generation is in scope. On glibc-based Linux, the preload path via ./preload.sh is the practical starting point today, since deeper libc integration is planned for musl only. Do not use it on 32-bit systems: the introduction calls it a fundamental incompatibility, not a gap to be closed. Before integrating into a production Android build, set vm.max_map_count to 1048576 in init.rc.

Frequently asked questions

What does hardened_malloc protect against that a standard allocator does not?

It adds canaries for write-after-free detection, quarantine queues that delay reuse of freed allocations, double-free detection for quarantined blocks, and per-size-class memory regions with independent random base addresses. These mechanisms target heap corruption vulnerabilities specifically.

Does hardened_malloc work on 32-bit systems?

No. The introduction says the allocator "fundamentally doesn't support" 32-bit environments because the design requires a 64-bit address space. The README names a port of OpenBSD malloc with small extensions as the path for 32-bit targets instead.

What is the minimum Linux kernel version required for hardened_malloc?

The project targets Debian 13 as the oldest supported configuration, which requires Linux 6.12. Older versions may be compatible but are not tested and are explicitly not supported. For Android, GKI branches 6.1, 6.6 and 6.12 are supported.

Can hardened_malloc be used with glibc on Linux?

Yes, as a malloc replacement via preload.sh. However, the introduction says glibc support will be limited to replacing the malloc implementation, while custom integration and further libc hardening are planned only for musl.

How does hardened_malloc handle thread caching?

It has none. The table of contents labels the relevant section "Thread caching (or lack thereof)," indicating this is an intentional design choice. Scalability comes from configurable independent arenas with per-size-class locking rather than thread-local caches.

Official sources

  1. GrapheneOS/hardened_malloc on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/grapheneos-hardened-malloc.svg)](https://hysenlabs.com/projects/grapheneos-hardened-malloc)