8.8 KiB
hardened_malloc in UniversalisOS — Config Matrix, Page-Size Contract & Integration
Track: T8 (hardened_malloc Import & Separation-Model Adoption)
Source: universalisos/third_party/hardened_malloc/ (see PROVENANCE.md)
Status: T8-1.1 (vendored) ✅ · T8-1.3 host build + make test 51/51 ✅ · T8-1.2 (musl personality) greenfield — see §4
hardened_malloc is the designated stepping stone to running multiple concurrent hardened Android/AOSP/LineageOS/GrapheneOS guests inside UniversalisOS. It is the only GrapheneOS component portable across the guest/host boundary (Bionic/musl/glibc). Master plan §0.14 + TRACK T8.
1. What was built and verified (2026-07-11)
| Artifact | Result |
|---|---|
out/libhardened_malloc.so (VARIANT=default) |
Built clean — 53 KB, C23, -O3 -flto -fPIC, all hardening flags active |
make test (acceptance gate) |
51/51 OK (double-free, overflow, quarantine, canary, usable-size, object-size, realloc/calloc edge cases) |
| Exported malloc API surface | malloc calloc realloc free aligned_alloc posix_memalign memalign cfree reallocarray + hardened extensions malloc_object_size{,_fast} free_sized free_aligned_sized malloc_usable_size malloc_stats malloc_info malloc_trim |
| Live LD_PRELOAD smoke test | PASS — real program allocates/uses/frees correctly under LD_PRELOAD=out/libhardened_malloc.so |
Build host: GCC 16.1.1 / Clang 22.1.8 (both C23-capable). CONFIG_NATIVE=true → -march=native.
2. Config matrix — default vs light (from config/{default,light}.mk)
| Flag | default |
light |
Meaning |
|---|---|---|---|
CONFIG_ZERO_ON_FREE |
true | true | Clear memory on free (anti data-leak) |
CONFIG_WRITE_AFTER_FREE_CHECK |
true | false | Detect write-after-free via canary |
CONFIG_SLOT_RANDOMIZE |
true | false | Randomize slot allocation order |
CONFIG_SLAB_CANARY |
true | true | Per-allocation canary |
CONFIG_SLAB_QUARANTINE_RANDOM_LENGTH |
1 | 0 | Random quarantine (use-after-free detection) |
CONFIG_SLAB_QUARANTINE_QUEUE_LENGTH |
1 | 0 | FIFO quarantine |
CONFIG_GUARD_SLABS_INTERVAL |
1 | 8 | How often slabs get guard pages |
CONFIG_EXTENDED_SIZE_CLASSES |
true | true | Finer-grained size classes |
CONFIG_LARGE_SIZE_CLASSES |
true | true | Large-allocation size classes |
CONFIG_REGION_QUARANTINE_RANDOM_LENGTH |
256 | 256 | Large-region quarantine |
CONFIG_REGION_QUARANTINE_QUEUE_LENGTH |
1024 | 1024 | Large-region FIFO quarantine |
CONFIG_CLASS_REGION_SIZE |
32 GiB | 32 GiB | Per-size-class virtual region |
CONFIG_N_ARENA |
4 | 4 | Independent arenas (scalability/isolation) |
CONFIG_SELF_INIT |
true | true | Self-initializing via constructor(101) (correct for musl/standalone) |
CONFIG_STATS |
false | false | Runtime stats (enable for debugging) |
Selection guidance (T8-1.4):
- musl POSIX personality (primary target):
default— full hardening; the personality is a controlled, trusted environment where the overhead is acceptable and the isolation value is highest. - Constrained / RTOS-class guests:
light— drops write-after-free check, slot randomization, and slab quarantine for lower overhead while keeping zero-on-free + canary + region quarantine. - Host dev tools (opportunistic, glibc):
defaultviapreload.shLD_PRELOAD. - Android guests: inherited — Bionic ships its own hardened_malloc build (
platform_bionich_malloc_wrapper.cpp,Android.bpwithCONFIG_SELF_INIT=false,N_ARENA=1). UniversalisOS does not rebuild it; it provides the Stage-2 MM features (§5).
Build a variant: make VARIANT=default (or light) → out/libhardened_malloc.so (or out-light/libhardened_malloc-light.so).
3. Page-size contract (HARD)
hardened_malloc assumes a 4 KiB page size, enforced at compile time:
// h_malloc.c:326
static_assert(PAGE_SIZE == 4096, "bitmap handling will need adjustment for other page sizes");
(The subagent noted a runtime sysconf(_SC_PAGESIZE) check; the binding constraint is actually this
compile-time static_assert plus the runtime sysconf guard.)
Consequence for UniversalisOS: any personality linking hardened_malloc must use 4 KiB pages.
- AArch64: 4 KiB translation granule is the common case → ✅ compatible. (UOS Stage-2 uses 4 KiB.)
- riscv64 / armv7: must confirm 4 KiB granule; 16 KiB/64 KiB granule configs are incompatible without porting the bitmap handling.
- This is a hard contract — a personality reporting a non-4 KiB page size will fail to build/boot with hardened_malloc.
4. T8-1.2 — musl POSIX personality baseline (GREENFIELD)
Current state (verified 2026-07-11): universalisos/kernel/src/core/posix/ contains only
config stubs (posix_config.c/h). There is no musl/libc userspace personality yet. The existing
guests (guests/linux-aarch64, riscv-sampling, wasm3-app) are full OS guests, not a libc-bearing
POSIX personality. So T8-1.2 is genuinely greenfield.
Why musl: hardened_malloc explicitly prefers musl ("a much more robust and cleaner base",
README.md:38). A musl-based personality also gives UniversalisOS a clean, non-Google libc story —
fits the "freedom from technology" north star.
Path to stand it up (next actions, in order):
- Toolchain: obtain an
aarch64-linux-muslcross toolchain (buildroot can emit one — the existingguests/linux-aarch64/buildroot-*trees supportBR2_TOOLCHAIN_BUILDROOT_LIBC="musl"; or use a prebuilt musl-cross). Host currently has onlyaarch64-linux-gnu-*(glibc). - Personality skeleton: a minimal EL1/EL0 userspace that the UOS kernel loads as a partition
(reuse the
guests/boot path +partition.h), with musl as its libc and ahello-worldthat callsmalloc/printf. - Wire hardened_malloc: link
libhardened_malloc.so(built for musl/aarch64 via the standalone Makefile,CONFIG_SELF_INIT=true) ahead of musl's malloc so it becomes the system allocator. - Gate: personality boots under UOS,
hello-worldallocates/frees via hardened_malloc, and hardened_malloc'smake test(cross-built) passes in the personality.
This is the next concrete T8 milestone after the host proof (done).
5. KERNEL_FEATURE_WISHLIST.md → UniversalisOS personality-MM (T8-2.1)
UniversalisOS is the kernel, so it can natively provide what hardened_malloc merely wishes Linux had:
Wishlist item (KERNEL_FEATURE_WISHLIST.md) |
UOS opportunity |
|---|---|
much higher vm.max_map_count |
hardened_malloc creates many VMAs (guard + per-class regions); UOS personality MM sets a high count natively |
| disable brk heap / mmap grows upwards | from-scratch personality simply doesn't provide brk |
alternative to RLIMIT_AS for accountable mappings |
map to the partition memory domain (partition.h memory partitioning) |
MREMAP_DONTUNMAP with expansion |
memory.c:92-110 uses mremap under HAVE_COMPATIBLE_MREMAP; UOS implements the exact semantics |
| first-class arbitrarily-sized guard pages | hardened_malloc emulates them with separate PROT_NONE VMAs (pages.c:13-30); UOS Stage-2/personality MMU provides them natively — halves VMA count + syscalls |
| virtual memory quarantine | UOS quarantines freed virtual regions at the personality level |
Design consequence: treat KERNEL_FEATURE_WISHLIST.md as a requirements document for the UOS
personality-MM/syscall layer. UOS can give hardened_malloc a better substrate than Linux — a
genuine differentiator and a concrete T8-2 workstream.
6. Why this is the stepping stone to multi-Android guests (recap)
- Only portable component: hardened_malloc is the sole GrapheneOS component that crosses the guest/host boundary (Bionic/musl/glibc). Importing it into the musl POSIX personality proves the toolchain + CMake integration + musl personality + MM-feature substrate before a full Android guest.
- Per-partition hardening = the separation model made concrete: each PikeOS partition gets its own hardened_malloc instance (independent random bases, quarantine queues, guard regions). A heap-corruption exploit in one guest is contained by (a) isolated Stage-2 address spaces and (b) independently-randomized allocator metadata. Two independent layers.
- Forces the MM substrate early: guard pages, high map counts, mremap — exactly what a multi-guest hypervisor needs anyway. Building it for hardened_malloc first de-risks the multi-guest Stage-2 work.
7. Acceptance status
- T8-1.1 vendor + provenance — ✅ DONE (
third_party/hardened_malloc/,PROVENANCE.md) - T8-1.3 host build
libhardened_malloc.so+make test51/51 + LD_PRELOAD smoke — ✅ DONE (host proof) - T8-1.4 config matrix + page-size contract + this doc — ✅ DONE
- T8-1.2 musl POSIX personality baseline — ⏳ GREENFIELD, next milestone (§4)