Files
CHARON/README.md
T
leviathan 95b37066df v1.1.0 — auto-discovery + --list-baits + ANSI Shadow banner
* New --auto flag: walk /usr/bin, /usr/sbin, /usr/local/{bin,sbin},
  /usr/lib/openssh, /usr/libexec, /bin, /sbin; try every SUID/SGID
  regular file as a bait against the requested target. Skips
  known-interactive baits (su, sudo, newgrp, pkexec, mount, …).
  Per-bait budget capped (5 rounds × 2000 inner) + 60s wall clock,
  so a full scan finishes in ~10s even on systems where no bait
  opens the requested file.

* New --list-baits flag: enumerate built-in + discoverable bait
  candidates without firing the exploit. Useful for distro surveys.

* SIGKILL daemonic baits (ssh-agent etc.) instead of waiting
  forever in waitpid().

* Accept SGID-shadow baits, not just SUID-root — chage on
  Debian/Ubuntu is mode 2755 not 4755 and we kept skipping it.

* Banner upgraded to ANSI Shadow block letters with a Styx wave
  motif beneath the version line.

* Cleaner diagnostics: distinguish "primitive fires but no bait
  opens this file" from "kernel patched (no pidfd_getfd success)".

Tested on Debian 13 / kernel 6.12.88-kctf-poc:
- default run hits /etc/shadow in 1 fork (~160 tries, <1s)
- --auto on /etc/sudoers correctly times out in 11s with diagnostic
- --quiet pipes 35 lines of pure shadow to stdout
- --verbose shows per-hit + final stats
- --list-baits enumerates 26 candidates incl. /usr/bin/chage
2026-05-15 23:33:59 -04:00

188 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
```
____ _ _ _ ____ ___ _ _
/ ___|| | | | / \ | _ \ / _ \| \ | |
| | | |_| | / _ \ | |_) | | | | \| |
| |___ | _ |/ ___ \| _ <| |_| | |\ |
\____||_| |_/_/ \_\_| \_\\___/|_| \_|
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`](https://github.com/torvalds/linux/commit/31e62c2ebbfdc3fe3dbdf5e02c92a9dc67087a3a)
(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
```sh
# 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
```sh
./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 bait)
./charon -a # auto-discover SUID/SGID baits if built-ins miss
./charon -L # list candidate baits without trying any
./charon --help
```
### Auto-discovery
`--auto` walks `/usr/bin`, `/usr/sbin`, `/usr/local/{bin,sbin}`,
`/usr/lib/openssh`, `/usr/libexec`, `/bin`, `/sbin`, finds every
SUID/SGID regular file (excluding interactive baits like `su`,
`sudo`, `newgrp`, `pkexec`), and tries each as a bait against the
requested target. Per-bait budget is tight (5 rounds × 2000 inner)
so a full scan finishes in ~10 seconds even when nothing matches.
`--list-baits` is the read-only version — it enumerates the same
candidates without firing the exploit. Useful for surveying which
distros ship which baits.
### 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](https://github.com/0xdeadbeefnetwork/ssh-keysign-pwn).
- 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() ║
╚══════════════════════════════╝
```