
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.
This is a security-focused general purpose memory allocator providing the malloc API along with various extensions. It provides substantial hardening against heap corruption vulnerabilities. The security-focused design also leads to much less metadata overhead and memory waste from fragmentation than a more traditional allocator design. It aims to provide decent overall performance with a focus on long-term performance and memory usage rather than allocator micro-benchmarks. It offers scalability via a configurable number of entirely independent arenas, with the internal locking within arenas further divided up per size class.
This project currently supports Bionic (Android), musl and glibc. It may support other non-Linux operating systems in the future. For Android, there's custom integration and other hardening features which is also planned for musl in the future. The glibc support will be limited to replacing the malloc implementation because musl is a much more robust and cleaner base to build on and can cover the same use cases.
This allocator is intended as a successor to a previous implementation based on extending OpenBSD malloc with various additional security features. It's still heavily based on the OpenBSD malloc design, albeit not on the existing code other than reusing the hash table implementation. The main differences in the design are that it's solely focused on hardening rather than finding bugs, uses finer-grained size classes along with slab sizes going beyond 4k to reduce internal fragmentation, doesn't rely on the kernel having fine-grained mmap randomization and only targets 64-bit to make aggressive use of the large address space. There are lots of smaller differences in the implementation approach. It incorporates the previous extensions made to OpenBSD malloc including adding padding to allocations for canaries (distinct from the current OpenBSD malloc canaries), write-after-free detection tied to the existing clearing on free, queues alongside the existing randomized arrays for quarantining allocations and proper double-free detection for quarantined allocations. The per-size-class memory regions with their own random bases were loosely inspired by the size and type-based partitioning in PartitionAlloc. The planned changes to OpenBSD malloc ended up being too extensive and invasive so this project was started as a fresh implementation better able to accomplish the goals. For 32-bit, a port of OpenBSD malloc with small extensions can be used instead as this allocator fundamentally doesn't support that environment.
Debian stable (currently Debian 13) determines the most ancient set of supported dependencies:
For Android, the Linux GKI 6.1, 6.6 and 6.12 branches are supported.
However, using more recent releases is highly recommended. Older versions of the dependencies may be compatible at the moment but are not tested and will explicitly not be supported.
For external malloc replacement with musl, musl 1.1.20 is required. However, there will be custom integration offering better performance in the future along with other hardening for the C standard library implementation.
For Android, only the current generation, actively developed maintenance branch of the Android
Open Source Project will be supported, which currently means android17-release.
The preload.sh script can be used for testing with dynamically linked
executables using glibc or musl:
./preload.sh krita --new-image RGBA,U8,500,500
It can be necessary to substantially increase the vm.max_map_count sysctl to
accommodate the large number of mappings caused by guard slabs and large
allocation guard regions. The number of mappings can also be drastically
reduced via a significant increase to CONFIG_GUARD_SLABS_INTERVAL but the
feature has a low performance and memory usage cost so that isn't recommended.
It can offer slightly better performance when integrated into the C standard library and there are other opportunities for similar hardening within C standard library and dynamic linker implementations. For example, a library region can be implemented to offer similar isolation for dynamic libraries as this allocator offers across different size classes. The intention is that this will be offered as part of hardened variants of the Bionic and musl C standard libraries.
A collection of simple, automated tests are provided and can be run with the make command as follows:
make test
OpenSSH 8.1 or higher is required to allow the mprotect PROT_READ|PROT_WRITE
system calls in the seccomp-bpf filter rather than killing the process.
On GrapheneOS, hardened_malloc is integrated into the standard C library as the standard malloc implementation. Other Android-based operating systems can reuse the integration code to provide it. If desired, jemalloc can be left as a runtime configuration option by only conditionally using hardened_malloc to give users the choice between performance and security. However, this reduces security for threat models where persistent state is untrusted, i.e. verified boot and attestation (see the attestation sister project).
Make sure to raise vm.max_map_count substantially too to accommodate the very
large number of guard pages created by hardened_malloc. This can be done in
init.rc (system/core/rootdir/init.rc) near the other virtual memory
configuration:
write /proc/sys/vm/max_map_count 1048576
This is unnecessary if you set CONFIG_GUARD_SLABS_INTERVAL to a very large
value in the build configuration.
On traditional Linux-based operating systems, hardened_malloc can either be
integrated into the libc implementation as a replacement for the standard
malloc implementation or loaded as a dynamic library. Rather than rebuilding
each executable to be linked against it, it can be added as a preloaded
library to /etc/ld.so.preload. For example, with libhardened_malloc.so
installed to /usr/local/lib/libhardened_malloc.so, add that full path as a
line to the /etc/ld.so.preload configuration file:
/usr/local/lib/libhardened_malloc.so
The format of this configuration file is a whitespace-separated list, so it's good practice to put each library on a separate line.
For maximum compatibility libhardened_malloc.so can be installed into
/usr/lib/ to avoid preload failures caused by AppArmor profiles or systemd
ExecPaths= restrictions. Check for logs of the following format:
ERROR: ld.so: object '/usr/local/lib/libhardened_malloc.so' from /etc/ld.so.preload cannot be preloaded (failed to map segment from shared object): ignored.