hardware-enforced compartments inside one C process
A stray read or write in one library cannot reach another's memory.
IA2 splits a C or C++ program into compartments where the build already drew
boundaries, at its shared libraries, and has the CPU enforce them: memory
protection keys on x86-64, memory tagging on 64-bit Arm. One process, one
address space, no messages.
Every load and store IA2's compiler emits carries x18's tag in the top byte of its pointer, and the processor compares that tag with the tag on the 16 bytes it reaches. Equal, the access goes ahead; different, it faults at that instruction. Shared memory carries no tag and is never checked.
What the CPU checks
x86-64memory protection keys
memory is marked by
a 4-bit key in each page table entry
the running compartment is held in
PKRU, a per-thread register with two bits per key
a gate switches compartments with
WRPKRU, one unprivileged instruction
a denied access raises
SIGSEGV, with si_code SEGV_PKUERR
compartments
15: keys 1 to 15; key 0 is shared
compiler
GCC or Clang, unmodified
64-bit Armthe Memory Tagging Extension
memory is marked by
a 4-bit tag on each 16-byte granule
the running compartment is held in
register x18, reserved by the compiler
a gate switches compartments with
movz x18
a denied access raises
SIGSEGV, with si_code SEGV_MTESERR
compartments
15: tags 1 to 15; untagged memory is shared
compiler
a modified Clang, for every library in the process
On x86-64 the permission belongs to the thread, so even code IA2 never compiled is held to the running compartment’s key. On Arm it travels in the pointer, and code IA2 did not compile uses whatever tag it was handed.
Using it
IA2 adds a step to a C build. Its rewriter, a tool built on Clang, finds every call that crosses a boundary and writes a gate for it, into a directory of its own; the original sources are left alone. IA2’s CMake functions do the whole build: its video player example is two calls to one of them.
Annotations
Only where no tool can infer them: data that is shared, function pointers handed to code IA2 does not rewrite, and functions that run in their caller’s compartment.
A gate at every crossing
About 200 instructions for a round trip on Arm, against two for a plain call and return. The cost in real applications is not yet measured.
On Arm, a toolchain
A modified Clang, and every library in the process rebuilt with it, the C library included. Each load and store carries two or three extra instructions.
IA2 gives coarse spatial memory safety: a stray read or write in one compartment cannot reach another’s private stack, heap, globals or thread-local storage, in either direction. That is narrower than memory safety.
Bugs inside a compartment
IA2 bounds what a bug reaches. It does not prevent the bug.
Shared and read-only data
Both are reachable from every compartment. In the nginx demo, about two thousand function pointers that nginx core calls sit in shared memory.
Control flow
Code that takes over control flow can jump straight to a gate’s WRPKRU or movz x18. The design relies on control-flow integrity to close that gap.
Variadic calls
A gate is generated per function, and a variadic function’s arguments differ at every call, so a variadic call across a boundary runs ungated, in the caller’s compartment. The rewriter reports each such function, and the private data it could reach.
Forged tags, on Arm
Code that does not clamp pointers, the kernel and hand-written assembly among it, uses whatever tag a pointer carries.
Memory system calls, on Arm
On x86-64 an optional tracer checks every call that maps, unmaps or protects memory against which compartment owns it. Arm has none yet.
Where it stands
nginx demo
Runs with enforcement on: HTTP served from compartment 1, a stream published over RTMP, HLS and DASH written from compartment 2, and the playlist served back and decoded.
ahead
Closing the gaps above, and measuring what a boundary costs.
why one buggy library can compromise a whole C program, how IA2 splits a process into compartments along its shared libraries, and how two CPU features already in shipping hardware, x86 protection keys and Arm memory tagging, enforce the split.