A drop-in memory-hardening allocator for Linux, written in Rust.
glibc's allocator prioritizes throughput over safety. Heap vulnerabilities -- use-after-free, buffer overflows, double frees, metadata corruption -- remain the most exploited bug classes in native software.
compatmalloc retrofits memory safety onto legacy C/C++ binaries without recompilation. Load it via LD_PRELOAD and every malloc/free call gains defense-in-depth hardening at near-native speed.
git clone https://github.com/t-cun/compatmalloc.git
cd compatmalloc
cargo build --releaseOutput: target/release/libcompatmalloc.so
LD_PRELOAD=./target/release/libcompatmalloc.so whoamiLD_PRELOAD=./target/release/libcompatmalloc.so python3 -c "
import json
data = [{'key': str(i), 'value': list(range(100))} for i in range(10000)]
json.dumps(data)
print('Success -- all allocations hardened')
"If LD_PRELOAD appears to have no effect:
- Use an absolute path.
LD_PRELOADrequires a full path or a library name findable viald.so. Relative paths like./target/...work from the repo root but not elsewhere. - setuid/setgid binaries ignore
LD_PRELOAD. This is a kernel security policy. Use/etc/ld.so.preloadfor system-wide coverage instead. - Verify it loaded. Run
LD_DEBUG=libs LD_PRELOAD=./target/release/libcompatmalloc.so ls 2>&1 | grep compatmalloc-- you should see it being loaded.
compatmalloc delivers hardened-allocator security at near-glibc throughput -- 3-4x faster than Scudo in microbenchmarks with comparable protections.
| Allocator | Weighted Overhead | Latency (64B) | Throughput (1T) | vs glibc | Throughput (4T) | vs glibc |
|---|---|---|---|---|---|---|
| compatmalloc | +11.6% | 14.5 ns | 65.48 Mops/s | 0.88x | 150.78 Mops/s | 0.88x |
| glibc | 0% | 12.3 ns | 74.53 Mops/s | 1.00x | 171.84 Mops/s | 1.00x |
| jemalloc | +58.2% | 9.9 ns | 95.32 Mops/s | 1.28x | 257.40 Mops/s | 1.50x |
| mimalloc | +16.7% | 8.9 ns | 81.17 Mops/s | 1.09x | 199.16 Mops/s | 1.16x |
| scudo | +310.2% | 53.4 ns | 18.34 Mops/s | 0.25x | 38.73 Mops/s | 0.23x |
Latency ratio < 1.0 = faster than glibc. Throughput ratio > 1.0 = faster than glibc. Hardened allocators: compatmalloc, scudo. Both have security features (guard pages, quarantine, etc.) that add overhead vs pure-performance allocators.
Auto-generated from CI. See full benchmark results for ARM64, per-size latency, and application overhead data.
| Application | glibc | compatmalloc | Overhead |
|---|---|---|---|
| python-json | 0.073s | 0.086s | +17% |
| redis | 3.338s | 3.355s | 0% |
| nginx | 5.104s | 5.104s | -1% |
| sqlite | 0.210s | 0.128s | -40% |
| git | 0.320s | 0.182s | -44% |
Wall-clock time on shared GitHub Actions runners (no CPU pinning, no isolated cores). Results vary between runs due to noisy-neighbor effects, ASLR, and cache alignment. Negative overhead does not necessarily mean compatmalloc is faster -- it means the difference is within noise. These numbers show that overhead is low in practice, not that it is zero.
| Exploit Primitive | Mitigation | Mechanism |
|---|---|---|
| Heap buffer overflow | Canary bytes | Non-invertible hash canaries in gap between requested size and slot boundary. Checked on free(). |
| Use-after-free | Quarantine + Poison | Freed memory held in per-arena ring buffer (default 4 MiB). Poison bytes (0xFE) detect stale writes on eviction. |
| Double free | Metadata flags | Out-of-band FLAG_FREED set atomically on free(). Second free() detects the flag and aborts immediately. |
| Heap metadata corruption | Out-of-band metadata | Metadata stored in separate mmap region. No inline freelist pointers for attackers to corrupt. |
| Adjacent chunk corruption | Guard pages | PROT_NONE pages before and after allocations. Hardware-enforced boundary. |
| Heap spraying / grooming | Slot randomization | Random slot selection within size classes defeats deterministic heap layout. |
| Information leaks | Zero-on-free | Memory cleared on free, preventing data from persisting across allocations. |
- Canary checks happen on
free(), not on write. A 1-byte overflow is only detected when the overflowed chunk is freed. If it's never freed, the overflow goes undetected. - Not async-signal-safe. Per-arena mutexes prevent safe use from signal handlers that may contend with the interrupted thread.
- Quarantine has a finite budget. After eviction, use-after-free on that address becomes undetectable.
- No inline bounds checking. Read overflows that stay within the slot size are invisible to canary checks.
compatmalloc has been tested against real-world exploits:
- CVE-2024-2961 -- iconv buffer overflow (CVSS 8.8). Tcache poisoning via 1-byte overflow. Caught by canaries + out-of-band metadata.
- CVE-2023-6246 -- syslog heap overflow (CVSS 7.8). Local privilege escalation via
su. Caught by canaries + guard pages. - Double-Free Detection -- Immediate abort via out-of-band metadata flags.
A multi-stage Dockerfile is provided for container integration. Build the .so artifact first, then copy it into your application image:
# Build the artifact image (one-time)
docker build --target artifact -t compatmalloc-artifact .Then in your application's Dockerfile:
# Copy the prebuilt .so from the artifact image
COPY --from=compatmalloc-artifact /libcompatmalloc.so /usr/lib/libcompatmalloc.so
ENV LD_PRELOAD=/usr/lib/libcompatmalloc.soOr use the ready-made hardened base image directly:
docker build --target hardened-base -t my-hardened-app .
docker run --rm my-hardened-app whoami# Once published to AUR:
yay -S compatmallocSystem-wide hardening:
# Add to /etc/ld.so.preload for system-wide protection
echo "/usr/lib/libcompatmalloc.so" | sudo tee -a /etc/ld.so.preloadSee the PKGBUILD for packaging details.
compatmalloc supports musl-based systems (Alpine Linux) via both LD_PRELOAD and #[global_allocator].
LD_PRELOAD (container hardening):
docker build -f Dockerfile.alpine --target hardened-base -t myapp-hardened .Rust native (#[global_allocator]):
cargo add compatmalloc --features global-allocator
cargo build --target x86_64-unknown-linux-muslSee the Dockerfile.alpine for packaging details.
For Rust projects, skip LD_PRELOAD entirely. Add to Cargo.toml:
[dependencies]
compatmalloc = { git = "https://github.com/t-cun/compatmalloc.git", features = ["global-allocator"] }Then in your main.rs:
use compatmalloc::CompatMalloc;
#[global_allocator]
static GLOBAL: CompatMalloc = CompatMalloc;
fn main() {
// Every Box, Vec, String, and HashMap is now hardened.
let data = vec![0u8; 1024];
println!("Allocated {} bytes with compatmalloc", data.len());
}This statically links the hardened allocator into your binary -- no .so file, no environment variables, no runtime dependencies.
Note: The
global-allocatorfeature implieshardened(all security features enabled). To select individual features, useLD_PRELOADwith compile-time feature flags instead.
| Variable | Default | Description |
|---|---|---|
COMPATMALLOC_DISABLE |
not set | Kill-switch: bypass all hardening, passthrough to glibc. Presence-based -- any value (even =0) triggers disable. Unset the variable entirely to re-enable. |
COMPATMALLOC_ARENA_COUNT=N |
CPU count (max 32) | Number of per-thread slab arenas |
COMPATMALLOC_QUARANTINE_SIZE=N |
4194304 (4 MiB) | Maximum quarantine queue size in bytes |
Build with selective hardening for performance tuning:
# All hardening (default)
cargo build --release
# No hardening (baseline performance)
cargo build --release --no-default-features
# Selective: only quarantine + canaries
cargo build --release --no-default-features --features quarantine,canaries| Feature | Default | Per-alloc cost |
|---|---|---|
canaries |
on | ~2ns (hash + memset) |
quarantine |
on | ~1ns (ring buffer enqueue) |
guard-pages |
on | ~0ns small / mprotect large |
slot-randomization |
on | ~1ns (RNG) |
poison-on-free |
on | ~1-5ns (memset) |
write-after-free-check |
on | ~1-5ns (memcmp on eviction) |
zero-on-free |
on | ~1-5ns (memset) |
- LD_PRELOAD drop-in replacement (full glibc ABI)
- Multi-allocator CI benchmarks (x86_64 + ARM64)
- CVE case studies with proof-of-concept programs
- Docker integration
- Arch Linux PKGBUILD
- Native Rust
#[global_allocator]support - Alpine / musl support (LD_PRELOAD +
#[global_allocator]) - Publish to crates.io
- Yocto/OpenEmbedded recipe for IoT devices
- Android (Bionic libc) compatibility
- ARM64 Memory Tagging Extension (MTE) integration
Zero-days and bugs found by fuzzing with compatmalloc.
No trophies yet -- want to be first? Run your C/C++ programs with
LD_PRELOAD=libcompatmalloc.soand report crashes to the upstream projects. Mention compatmalloc and we'll add it here.
Full documentation: t-cun.github.io/compatmalloc
- ABI Contract -- every exported symbol and its semantics
- Hardening Details -- how each defense mechanism works
- Benchmarks -- full performance data with methodology
- Configuration -- all environment variables and feature flags
- Deviations from glibc -- known behavioral differences
See SECURITY.md for reporting vulnerabilities.
If your company uses compatmalloc to harden production infrastructure, consider sponsoring the project.
Dual-licensed under MIT and Apache 2.0.