Est.

Position Independent Executables and Sandbox Escape Resistance

Contributing Editor · · 12 min read
Cover illustration for “Position Independent Executables and Sandbox Escape Resistance”
Sandbox Isolation Primitives · August 26, 2026 · 12 min read · 2,791 words

PIE, short for Position Independent Executable, does one job that matters for sandbox security: it lets ASLR randomize the main program's code, not just its libraries. Follow that single fact through the rest of the hardening stack, and you get a decent explanation for why any serious isolation environment running untrusted code, especially AI-generated code, treats PIE as table stakes rather than a nice-to-have. I've spent enough time staring at crash dumps and CVE writeups to know that the gap between "hardened" and "hardened where it counts" usually comes down to details exactly this small.

How ASLR depends on PIE to randomize the full process address space

Address Space Layout Randomization is supposed to scatter a process's memory on every run, so nothing sits where an attacker expects it. But here's what a lot of people miss: ASLR without PIE randomizes the stack, the heap, and the shared libraries, and then leaves the executable's own code sitting at the exact same virtual address every single time. That's a fixed anchor in an otherwise shuffled memory space, and attackers have built entire exploitation strategies around exactly that kind of anchor. A fixed point is a fixed point, no matter how much noise you scatter around it.

PIE closes that gap by stripping out the executable's hardcoded load address entirely. A position-independent executable makes no assumptions about where it lives in memory; it uses relative references internally, so the loader, the Linux kernel in most cases, can drop it anywhere and it still runs correctly. Once that holds true, the kernel randomizes the executable's base address on every launch, the same way it already handles everything else.

So what actually breaks for an attacker once PIE is in place? A hardcoded return address baked into shellcode stops pointing anywhere useful. A ROP chain built from one static disassembly of the binary gets invalidated the moment the process restarts, because the gadget addresses moved underneath it. Jump-to-libc attacks lose their fixed target too, since even the executable that would redirect into libc no longer sits at a known offset.

Entropy quality still matters here, and it isn't uniform across architectures. The width of the address space constrains how much randomization is actually possible, which is part of why modern hardening guidance cares about the target architecture almost without saying so out loud.

ASLR is the defense; PIE is what makes the main executable eligible for it. Without PIE, you can pile all the entropy in the world onto your libraries and your heap, and the attacker still has a fixed building to work from.

The exploit classes PIE and ASLR are specifically designed to obstruct

Classic buffer overflow exploitation works by overwriting a return address with a known code address, then letting the CPU jump there on return. PIE and ASLR don't stop the overflow itself; they take away the "known" part of that sentence, which was most of what made the attack reliable to begin with.

Return-Oriented Programming is the more sophisticated descendant of that technique, and it's the exploit class where PIE's contribution shows up most clearly. An attacker strings together small existing instruction fragments, gadgets, each ending in a return instruction, and chains them into arbitrary computation without ever injecting new code. That only works, though, if the attacker knows where those gadgets sit in memory ahead of time. ASLR scrambles those addresses between runs, and because PIE extends randomization to the executable itself, the attacker loses the gadgets inside the main binary too, not just the ones sitting in libc or libssl or whatever shared library happened to load.

Ret2libc attacks follow the same logic from a different angle: redirect execution to a known libc function, system() being the favorite, and let that function do the dirty work. It only works if libc's base address is predictable. Once ASLR randomizes that base, the attacker is guessing, and guessing wrong at the wrong moment usually means a crash and a fresh set of addresses on the next attempt.

None of this eliminates exploitation as a category, and it would be a mistake to read it that way. What it does is push attackers toward a second class of bug: an information leak that discloses a live pointer, from which they can compute the real base address at runtime. Format string bugs, where a misused printf-family call spills stack or heap addresses, are the classic example. Heap spraying answers the same problem more crudely, flooding large regions of the address space with shellcode to improve the odds of landing on it despite randomization. Partial overwrites, where the attacker only touches the low bytes of a return address, are cruder still, shrinking the entropy they need to defeat down to something brute-forceable. In some environments, timing side-channels leak just enough signal to narrow the guess even further.

PIE and ASLR raise the cost and the skill floor of exploitation considerably. They don't remove the possibility, and I'd be lying if I claimed otherwise. That's exactly why they're one layer in a stack rather than the whole stack, which is where the compiler hardening running alongside PIE comes in.

The compiler hardening stack that works alongside PIE

Stack canaries are one of the oldest tricks here and still one of the most effective against a specific failure mode. A random value sits between the local variables and the return address on the stack. If a buffer overflow tries to smash its way up to that return address, it has to overwrite the canary first, and a mismatched canary gets caught before the corrupted return address ever gets used.

RELRO, short for Relocation Read-Only, comes in two strengths worth telling apart. Partial RELRO marks certain sections read-only but leaves the Global Offset Table exposed. Full RELRO resolves every relocation at load time and then locks the entire GOT read-only, closing off a favorite pivot point for attackers who've already found a way to write to memory. It costs a bit more at startup for that resolution step. For anything running untrusted code, that's a fair trade, and honestly not a close call.

NX enforces one simple rule: a memory region is either writable or executable, never both. Shellcode dropped into a data buffer can sit there all day; it can't be jumped to and run, because the processor's page tables won't allow execution from that region. Control Flow Integrity works from the other direction, constraining indirect calls and returns so the program only jumps to targets considered valid. Even after an attacker has defeated ASLR through a leak, CFI narrows what a ROP chain can actually accomplish, since most of the gadget chain that would otherwise work no longer represents a legal control-flow transfer.

Fortify Source quietly swaps unsafe standard library calls for bounds-checked versions at compile time, catching a category of buffer overflow that would otherwise sail through untouched.

Every one of these mitigations assumes ASLR is doing its job of making addresses unpredictable. Canaries catch the overflow before the corrupted return address fires. NX blocks the injected payload from running. RELRO removes the GOT as a pivot. CFI constrains what's reachable even after a leak. All of that depends on the address space actually being randomized, and the address space can't be fully randomized without PIE covering the main executable. Compile a binary without PIE, and you've quietly undercut the value of every other flag sitting next to it in the build command.

Why kernel-level isolation determines whether binary hardening can hold

All of this hardening operates in userspace, and userspace has a ceiling. PIE, ASLR, canaries, CFI: they harden a process against exploitation within that process. Once an attacker has kernel access, none of it matters anymore, because those controls get bypassed from underneath rather than defeated head-on.

gVisor's own documentation says this plainly: with standard containers, a workload sits one system call away from host compromise. That's a description of the attack surface, not a scare tactic. The interface code where a process crosses from userspace into the kernel, shared memory queues, syscall handlers, socket implementations, is where the exploit landscape actually lives in 2025. The breaks happen in the boundary code beneath the application logic, not in the application logic itself.

CVE-2024-1086 makes the point well: a use-after-free in the Linux kernel's netfilter subsystem, actively exploited in ransomware campaigns that CISA confirmed in October 2025, with RansomHub and Akira both using it for post-compromise privilege escalation. A process hardened at the binary level, PIE, canaries, CFI, all of it, cannot block a bug that lives in the kernel itself. The hardening was never built to reach that far down, and no amount of wishing changes that.

There's a Crypto API vulnerability that works through a different door entirely: it abuses the kernel's AF_ALG sockets, letting an unprivileged user perform controlled 4-byte writes into the page cache. That primitive exists entirely below the userspace hardening layer. No amount of stack protection in the application touches it, because the application was never in the blast path to begin with.

Seccomp, AppArmor, and SELinux reduce this exposure by narrowing which syscalls a process can even make in the first place, and denying AF_ALG sockets or unprivileged user namespaces closes off both of the exploits above. The overhead of evaluating a seccomp filter runs in the nanoseconds per call, close to free as a complement to binary hardening. But a shared kernel still means a shared blast radius. Reducing syscall surface buys margin; it doesn't retire the category of risk. Which raises the obvious next question: what happens when the surface reduction isn't enough on its own?

How real sandbox escapes have exploited the gap between binary hardening and kernel exposure

Frontier AI models' success rate on apprentice-level cybersecurity tasks moved from under 10% in late 2023 and early 2024 to roughly 50% in 2025, with the first expert-level task completed sometime during that same stretch. Sandbox architectures calibrated to what models could do in 2023 are now being asked to hold against models that can do considerably more. The escapes on record show what that gap looks like once it stops being hypothetical.

Leaky Vessels, CVE-2024-21626, worked by crafting a Dockerfile that set WORKDIR to a /proc/self/fd/[ID] path pointing back at the host filesystem. runc mishandled the working directory resolution, letting an attacker walk the host's directory tree without defeating a single binary-level mitigation. The exploit went around ASLR and PIE entirely, through the container runtime underneath them.

A runc vulnerability disclosed in November 2025 followed a similar shape: replace /dev/null with a symlink pointing at a procfs file like /proc/sys/kernel/core_pattern, bypassing runc's maskedPaths protection and granting arbitrary host file write. It touches Docker, Kubernetes, containerd, and CRI-O, which gives some sense of how much of the container ecosystem shares this exposure through a common runtime layer.

NVIDIAScape, CVE-2025-23266, sits in the NVIDIA Container Toolkit and allows arbitrary code execution, privilege escalation, and data tampering on the host in GPU-accelerated environments, a category that matters more every year as AI workloads increasingly need GPU access just to run at all. And on the hypervisor side, Firecracker's escape-class findings arrived later than most people assume: an out-of-bounds write in virtio-pci scoring 8.7 on CVSS, alongside a jailer symlink issue enabling host writes scored at 6.0. That closes out a prior stretch where Firecracker had no published hypervisor-escape CVE at all, which is worth sitting with, because it means the hardware-isolation layer people lean on as a backstop isn't immune either.

There's also a path into all of this that has nothing to do with a human attacker sitting at a keyboard. A 2024 OWASP report identifies prompt injection as a vector that can lead an agent to generate code that exploits a kernel CVE on its own, with no human ever touching the machine directly. The attacker reaches kernel-level access through what looked, from the outside, like an ordinary user request. PIE and ASLR offer no defense here, because the danger sits in the code the agent writes and then runs, not in the agent's own process.

None of the cases above got caught by binary hardening, for a simple reason: none of them attacked the binary. They went after the container runtime, the kernel interface, or the device driver layer sitting underneath all of it.

The isolation technology stack and where PIE-hardened binaries fit within it

Table: Isolation Layer Stack for AI Sandbox Environments. Compares Examples, What It Protects, Shared Host Kernel and Key Limitation by Binary Hardening, Syscall Filtering, User-Space Kernel and Hardware VM Isolation.

Line these mitigations up and a hierarchy emerges, one where each layer assumes the layer below it might fail.

Layer one is binary hardening: PIE, ASLR, canaries, CFI, RELRO. This raises the cost of exploiting a vulnerability that lives inside the process itself, and it's the baseline every workload should carry regardless of what else sits underneath it.

Layer two is syscall filtering: seccomp, AppArmor, SELinux. This narrows the kernel surface a process can even touch, at an overhead that's close to nothing in practical terms. It still shares the host kernel, though, so the ceiling from the previous section still applies.

Layer three moves into user-space kernels, the approach gVisor takes with its Sentry component. Guest syscalls get intercepted before they ever reach the host kernel, so an attacker who breaks out of the sandboxed process still has to break out of Sentry, and then defeat a hardened seccomp profile protecting Sentry itself. Two independent layers stand between the workload and the host. Performance overhead sits close to zero on compute-bound tasks, though it climbs to 10 to 30% on I/O-heavy workloads, and GPU support stays limited because of how the interception architecture works.

Layer four is hardware-virtualized isolation, the approach Firecracker microVMs and Kata Containers both take. There's no shared kernel at all here; each sandbox runs its own kernel behind a hardware virtual machine boundary. Firecracker allowlists 55 syscalls under its mode-2 seccomp filter and skips GPU passthrough entirely (VFIO isn't implemented in its deliberately minimal device model), but it stands up a sandbox in roughly 78 milliseconds at the median. Kata Containers takes an OCI-compatible route to the same hardware VM boundary, and Northflank runs it at a scale of over 2 million isolated workloads processed monthly.

Binary hardening assumes ASLR is doing its job. Syscall filtering assumes the process stays inside its permitted operations. Kernel isolation assumes the hypervisor boundary holds. Each layer reduces the blast radius left by the layer beneath it rather than eliminating risk outright. For AI-generated code specifically, the executable inside the sandbox should be PIE-hardened, and the sandbox around it needs to provide real kernel separation; both, not either. A standard container without that separation leaves the workload one unfiltered syscall away from host compromise, which is precisely the situation gVisor's documentation describes.

What PIE-aware hardening looks like in a purpose-built sandbox for AI-generated code

Here's the distinctive problem with AI-generated code that doesn't come up when you're running something a human engineer wrote and signed their name to: nobody compiled that binary with your hardening flags in mind, and there's no developer to hold accountable for whether it got built safely in the first place. The sandbox can't assume the incoming code is PIE-compiled. Honestly, it can't assume much about the incoming code at all.

That flips the usual hardening question around. Instead of asking whether this particular binary was built correctly, a purpose-built runtime has to ask what it can guarantee regardless of how the binary was built. Practically, that means running every workload inside a kernel-isolated environment, so that even a total ASLR bypass inside the guest never turns into host-level access. It means applying syscall filtering aggressively enough to deny the exact primitives, AF_ALG sockets, unprivileged user namespaces, that underlie the documented kernel escapes above. And it means the runtime's own infrastructure, the agent coordinator, the API surface, the code deciding what gets run and how, is itself compiled PIE, with full RELRO and CFI switched on, because that infrastructure is the single highest-value target in the whole system.

Statefulness adds a wrinkle that's easy to overlook. Sandboxes that carry state across multiple steps of an agent's work, rather than spinning up fresh for every single call, need to snapshot and restore kernel-level process state, not just the filesystem. A sandbox that pauses on one host and resumes on another has to guarantee nothing exploitable rides across that boundary along with it.

None of this comes at the cost of speed, worth saying plainly since it's often assumed to be a trade-off. Sub-100 millisecond provisioning, the roughly 78ms p50 that Firecracker already hits, means there's no real performance argument left for reusing sandbox instances across untrusted workloads just to shave off a few milliseconds. The isolation runs fast enough that weakening it to gain speed isn't a trade anyone actually needs to make.

Sources

  1. redhat.com
  2. github.com
  3. bordergate.co.uk
  4. rahalkar.dev
  5. github.com

More in Sandbox Isolation Primitives