Files
CHARON/README.md
T
leviathan a0d7d0b75b charon: initial release — CVE-2026-46333 PoC
CHARON ferries file descriptors out of dying SUID/SGID processes
through the __ptrace_may_access mm==NULL window in do_exit(),
disclosed by Qualys 2026-05-15 (CVE-2026-46333).

Default behavior: dump /etc/shadow to stdout, banner + progress on
stderr. --quiet for pure-pipe output, --verbose for stats.

Built-in lures cover Debian/Ubuntu (chage SGID-shadow), RHEL family
(chage SUID-root), and ssh-keysign. Patched-kernel detection
distinguishes "primitive fires but lure didn't open target" from
"pidfd_getfd never succeeded → fix is in place".

Pre-built 46KB musl-static binary included as charon-static.
2026-05-15 23:15:58 -04:00

5.9 KiB

  ____  _   _    _    ____   ___  _   _
 / ___|| | | |  / \  |  _ \ / _ \| \ | |
| |    | |_| | / _ \ | |_) | | | |  \| |
| |___ |  _  |/ ___ \|  _ <| |_| | |\  |
 \____||_| |_/_/   \_\_| \_\\___/|_| \_|

  ferries fds across the exit-mm() Styx
  CVE-2026-46333  /  Linux <= 6.12.89

"It is a fearful thing to fall into the hands of the living God." — Hebrews 10:31

What this is

A tight, dependency-free PoC for CVE-2026-46333: the __ptrace_may_access mm==NULL bypass disclosed by Qualys on 2026-05-15. Charon races pidfd_getfd(2) against a dying SUID-root process to lift its open /etc/shadow file descriptor through the brief mm-NULL window in do_exit(). Run it as an unprivileged user on an affected box; it dumps /etc/shadow to stdout.

$ ./charon
[banner on stderr]
[*] lure /usr/bin/chage   target /etc/shadow
root:$y$j9T$ztS5H...$hz9W87TlqxEW...:...
daemon:*:20582:0:99999:7:::
bin:*:20582:0:99999:7:::
...

Typical hit rate: under one second on a 4-core VM, ~137 tries in the smoke test.

The bug, in 30 seconds

__ptrace_may_access() short-circuits its dumpability check when task->mm == NULL. The fast-path was written for kernel threads (swapper et al.), which legitimately have no mm and should never be ptraced. But do_exit() runs exit_mm() before exit_files(), which means a userspace SUID process briefly has:

  • task->mm == NULL (mm reaped) → dumpable check skipped
  • file table still populated → fds still gettable
  • creds reflect the post-setreuid() drop → access check passes

pidfd_getfd(2) trusts that access check and hands the attacker the SUID process's open file descriptors.

do_exit()
  ├── exit_mm()      ← task->mm = NULL
  ├── ...            ← __ptrace_may_access() now lies
  └── exit_files()   ← fd table reaped

Jann Horn flagged the FD-theft shape on lore.kernel.org in October 2020. The fix sat in maintainer review for ~6 years before Qualys brought it back to the front of the queue.

Upstream fix: 31e62c2ebbfd (Linus 2026-05-14). As of 2026-05-15 the backport has not landed in linux-6.12.y or linux-6.6.y stable.

Affected kernels

Stable tree Status
linux-6.12.y (≤ 6.12.89) vulnerable
linux-6.6.y (pre-fix backport) vulnerable
mainline ≥ 6.15-rc1 patched (31e62c2ebbfd)
Distro Kernel Status (2026-05-15)
Debian trixie 6.12.86+deb13
AlmaLinux 10.1 6.12.0-124.55.3
Ubuntu 26.04 7.0.0-15 ⚠️ check
Fedora 44 7.0.4-200 ⚠️ check

The PR / rolling-status table will be updated as backports land.

Build

# Tiny 38 KB static binary (recommended)
sudo apt-get install musl-tools
make static

# Or just the standard glibc build
make

Output: a single ELF ./charon.

Run

./charon                     # dump /etc/shadow (default)
./charon -q                  # no banner / progress, just shadow on stdout
./charon -v                  # show per-hit + final stats
./charon -r 5000             # more patience for slow systems
./charon -t /etc/ssh/ssh_host_ecdsa_key   # different target (uses ssh-keysign lure)
./charon --help

Exit codes

Code Meaning
0 Success — file contents on stdout
1 No SUID lure on this system opens the requested file
2 Kernel appears patched (CVE-2026-46333 closed)
3 Ran out of rounds without a hit (rare; try -r 5000)
4 CLI / IO error

Lures

Charon ships with four known SUID lures:

Binary File it opens Distro coverage
/usr/bin/chage (chage -l <user>) /etc/shadow Most Debian, Ubuntu, Fedora
/usr/sbin/chage /etc/shadow RHEL / Rocky / Alma family
/usr/bin/passwd (passwd -S <user>) /etc/shadow Most distros
/usr/lib/openssh/ssh-keysign /etc/ssh/ssh_host_*_key Distros with HostbasedAuthentication enabled

Adding a lure is a 3-line edit to the lures[] array in charon.c.

Mitigations until your distro ships the backport

  • Apply 31e62c2ebbfd directly.
  • Disable pidfd_getfd(2) via seccomp on production hosts.
  • Remove the setuid bit from chage and passwd if you do not need unprivileged users to query password aging.
  • For containerized workloads, enabling no_new_privs on the host blocks the primitive entirely — every "SUID" inside the container becomes inert, leaving Charon with no prey.

Not a kernelctf VRP candidate

The Google kernelctf VRP challenge VM runs the player's bash inside an nsjail sandbox with clone_newuser:true (uid 0 unmapped), chroot:/chroot, and no_new_privs:1. Under no_new_privs the setuid bit is inert, so there are no real SUID prey inside the sandbox, and /flag lives on the host outside the chroot. Charon therefore cannot win kCTF VRP. It remains a legitimate Linux LPE on bare-metal Debian / Ubuntu / RHEL family installations.

Provenance

  • Bug discovered & disclosed by Qualys → oss-security 2026-05-15.
  • Reference PoCs by @0xdeadbeefnetwork.
  • Charon rewrites the lure-and-race loop into a single hardened binary, adds CLI ergonomics, patched-kernel auto-detection, and per-distro lure fallback.

License

Educational and authorized-defensive use only.

                        ⛵ STYX ⛵
              ╔══════════════════════════════╗
              ║  do_exit():                  ║
              ║   ├── exit_mm()   ← task->mm ║
              ║   │                = NULL    ║
              ║   ├── ...      ←  ferry      ║
              ║   └── exit_files()           ║
              ╚══════════════════════════════╝