MCPcopy Create free account
hub / github.com/GrapheneOS/hardened_malloc

github.com/GrapheneOS/hardened_malloc @TQ3A.230605.012.2023061402

Chat with this repo
repository ↗ · DeepWiki ↗ · release TQ3A.230605.012.2023061402 ↗ · + Follow
256 symbols 725 edges 60 files ⚖ MIT 9 documented · 4% updated 19d ago14 · 2026-02-22★ 1,97831 open issues

Browse by type

Functions 220 Types & classes 36
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Hardened malloc

Introduction

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.

Dependencies

Debian stable (currently Debian 12) determines the most ancient set of supported dependencies:

  • glibc 2.36
  • Linux 6.1
  • Clang 14.0.6 or GCC 12.2.0

For Android, the Linux GKI 5.10, 5.15 and 6.1 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 android13-qpr2-release.

Testing

Individual Applications

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.

Automated Test Framework

A collection of simple, automated tests are provided and can be run with the make command as follows:

make test

Compatibility

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.

OS integration

Android-based operating systems

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.

Traditional Linux-based operating systems

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.

Using the LD_PRELOAD environment variable to load it on a case-by-case basis will not work when AT_SECURE is set such as with setuid binaries. It's also generally not a recommended approach for production usage. The recommendation is to enable it globally and make exceptions for performance critical cases by running the application in a container / namespace without it enabled.

Make sure to raise vm.max_map_count substantially too to accommodate the very large number of guard pages created by hardened_malloc. As an example, in /etc/sysctl.d/hardened_malloc.conf:

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 arm64, make sure your kernel is configured to use 4k pages since we haven't yet added support for 16k and 64k pages. The kernel also has to be configured to use 4 level page tables for the full 48 bit address space instead of only having a 39 bit address space for the default hardened_malloc configuration. It's possible to reduce the class region size substantially to make a 39 bit address space workable but the defaults won't work.

Configuration

You can set some configuration options at compile-time via arguments to the make command as follows:

make CONFIG_EXAMPLE=false

Configuration options are provided when there are significant compromises between portability, performance, memory usage or security. The core design choices are not configurable and the allocator remains very security-focused even with all the optional features disabled.

The configuration system supports a configuration template system with two standard presets: the default configuration (config/default.mk) and a light configuration (config/light.mk). Packagers are strongly encouraged to ship both the standard default and light configuration. You can choose the configuration to build using make VARIANT=light where make VARIANT=default is the same as make. Non-default configuration templates will build a library with the suffix -variant such as libhardened_malloc-light.so and will use an out-variant directory instead of out for the build.

The default configuration template has all normal optional security features enabled (just not the niche CONFIG_SEAL_METADATA) and is quite aggressive in terms of sacrificing performance and memory usage for security. The light configuration template disables the slab quarantines, write after free check, slot randomization and raises the guard slab interval from 1 to 8 but leaves zero-on-free and slab canaries enabled. The light configuration has solid performance and memory usage while still being far more secure than mainstream allocators with much better security properties. Disabling zero-on-free would gain more performance but doesn't make much difference for small allocations without also disabling slab canaries. Slab canaries slightly raise memory use and slightly slow down performance but are quite important to mitigate small overflows and C string overflows. Disabling slab canaries is not recommended in most cases since it would no longer be a strict upgrade over traditional allocators with headers on allocations and basic consistency checks for them.

For reduced memory usage at the expense of performance (this will also reduce the size of the empty slab caches and quarantines, saving a lot of memory, since those are currently based on the size of the largest size class):

make \
N_ARENA=1 \
CONFIG_EXTENDED_SIZE_CLASSES=false

The following boolean configuration options are available:

  • CONFIG_WERROR: true (default) or false to control whether compiler warnings are treated as errors. This is highly recommended, but it can be disabled to avoid patching the Makefile if a compiler version not tested by the project is being used and has warnings. Investigating these warnings is still recommended and the intention is to always be free of any warnings.
  • CONFIG_NATIVE: true (default) or false to control whether the code is optimized for the detected CPU on the host. If this is disabled, setting up a custom -march higher than the baseline architecture is highly recommended due to substantial performance benefits for this code.
  • CONFIG_CXX_ALLOCATOR: true (default) or false to control whether the C++ allocator is replaced for slightly improved performance and detection of mismatched sizes for sized deallocation (often type confusion bugs). This will result in linking against the C++ standard library.
  • CONFIG_ZERO_ON_FREE: true (default) or false to control whether small allocations are zeroed on free, to mitigate use-after-free and uninitialized use vulnerabilities along with purging lots of potentially sensitive data from the process as soon as possible. This has a performance cost scaling to the size of the allocation, which is usually acceptable. This is not relevant to large allocations because the pages are given back to the kernel.
  • CONFIG_WRITE_AFTER_FREE_CHECK: true (default) or false to control sanity checking that new small allocations contain zeroed memory. This can detect writes caused by a write-after-free vulnerability and mixes well with the features for making memory reuse randomized / delayed. This has a performance cost scaling to the size of the allocation, which is usually acceptable. This is not relevant to large allocations because they're always a fresh memory mapping from the kernel.
  • CONFIG_SLOT_RANDOMIZE: true (default) or false to randomize selection of free slots within slabs. This has a measurable performance cost and isn't one of the important security features, but the cost has been deemed more than acceptable to be enabled by default.
  • CONFIG_SLAB_CANARY: true (default) or false to enable support for adding 8 byte canaries to the end of memory allocations. The primary purpose of the canaries is to render small fixed size buffer overflows harmless by absorbing them. The first byte of the canary is always zero, containing overflows caused by a missing C string NUL terminator. The other 7 bytes are a per-slab random value. On free, integrity of the canary is checked to detect attacks like linear overflows or other forms of heap corruption caused by im

Core symbols most depended-on inside this repo

browse all functions →

Shape

Function 181
Method 39
Class 36

Languages

C76%
Python16%
C++8%

Modules by API surface

h_malloc.c118 symbols
test/test_smc.py40 symbols
memory.c11 symbols
random.c10 symbols
new.cc6 symbols
util.h4 symbols
pages.c4 symbols
mutex.h4 symbols
test/malloc_info.c3 symbols
test/mallinfo2.c3 symbols
test/mallinfo.c3 symbols
chacha.c3 symbols

For agents

$ claude mcp add hardened_malloc \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page