ssh-keysign-pwn: the kernel exit race that leaks root-owned files, explained for defenders
Steal SSH host private keys and /etc/shadow via the ptrace_may_access mm-NULL bypass + pidfd_getfd. Pre-31e62c2ebbfd kernels.
At a glance
- What is it?
- CVE-2026-46333 exploits a window in do_exit where ptrace_may_access skips the dumpable check and pidfd_getfd duplicates still-open file descriptors, exposing SSH host keys and /etc/shadow on kernels built before the 2026-05-14 fix.
- Who is it for?
- CVE-2026-46333 is a clean example of a logic bug hiding in plain sight: a dumpable check skipped for mm-less tasks, teardown ordering that leaves descriptors open in that gap, and two decades-old setuid helpers holding root-owned files open at exactly the wrong moment.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 137 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A patched kernel race, documented end to end
The repository 0xdeadbeefnetwork/ssh-keysign-pwn documents CVE-2026-46333, a Linux kernel privilege issue that lets an unprivileged local user read root-owned files such as SSH host private keys and /etc/shadow. The author pairs working exploit code with a precise root cause writeup, a list of confirmed affected systems, and a controlled-target proof built for verification on machines you own. Qualys reported the underlying bug and Linus merged the fix on 2026-05-14, so every kernel built after commit 31e62c2ebbfd is safe. The exposed population is the long tail of stable and embedded systems that have not picked up the patch yet, which is exactly who this analysis is for.
The part worth studying as a defender is not the exploit itself but the shape of the bug. It lives in the intersection of three long-standing kernel behaviors, and the same shape was flagged in public review six years before it turned into a working attack. That gap between known risk and shipped exploit says a lot about how long these windows can stay open in practice.
The bug: a dumpable check skipped when mm is gone
The core defect sits in __ptrace_may_access(). When the target task's mm pointer is NULL, the function skips the dumpable check that would normally block access to privileged process state. That skip becomes exploitable because of ordering inside do_exit(): the kernel tears down the address space with exit_mm() before it closes file descriptors with exit_files(). A task in that window has no mm but still holds every open file descriptor.
Into that window steps pidfd_getfd(2), the syscall built for process managers to duplicate descriptors from another task. When the caller's uid matches the target's, the duplication succeeds in that brief post-exit_mm state, and the caller ends up holding a duplicate of a root-owned file descriptor it could never have opened on its own. No kernel memory corruption is involved at any point. It is a pure logic and ordering bug, which is part of why it survived so long: nothing here looks wrong until you trace the exact sequence of teardown steps against the access checks.
Two setuid targets with the same shape
The repository ships two attack binaries against two long-lived pieces of userland. The first, sshkeysign_pwn, goes after /etc/ssh/ssh_host_ecdsa_key, ssh_host_ed25519_key, and ssh_host_rsa_key. The ssh-keysign helper opens those keys with mode 0600 before permanently_set_uid() drops privileges, and when EnableSSHKeysign=no it bails out with the file descriptors still open. That code shape has been in place since 2002, which means the sensitive descriptors were sitting one scheduling decision away from exposure for over two decades.
The second, chage_pwn, targets /etc/shadow. Running chage -l on a user calls spw_open(O_RDONLY) and then setreuid(ruid, ruid), and because both arguments are set, uid, euid, and suid all drop to the real uid in one step. The race is the same: catch the process during exit, lift the shadow file descriptor through the mm-NULL window, and read the root hash offline. Either binary prints the stolen file on stdout and lands a hit in roughly 100 to 2000 spawn attempts.
How wide the exposure ran
The confirmed list reads like a survey of mainstream Linux: Raspberry Pi OS Bookworm on kernel 6.12.75, Debian 13, Ubuntu 22.04, 24.04, and 26.04, Arch, and CentOS 9. Since the fix landed on 2026-05-14, any of those systems running a kernel built from a source tree before commit 31e62c2ebbfd is exposed to a local attacker who can spawn processes and wait for the race window.
Building the proof of concept from the repository is two commands:
make
./sshkeysign_pwn # host keys
./chage_pwn root # /etc/shadow contentThat simplicity cuts both ways. It makes the bug easy for an administrator to verify on a test box, and it makes the same verification trivial for anyone with a shell account on an unpatched multi-user host. If your fleet includes shared machines, embedded boards, or long-term-support installs that have not seen a kernel update since before mid-May 2026, they belong on the patch list regardless of whether anything suspicious has shown up in the logs.
What defenders should take from it
The practical response is unglamorous. First, inventory kernels against the fixed commit and prioritize hosts where untrusted local users coexist with ssh-keysign enabled or chage installed, since those two binaries are the proven targets. Second, rotate what was exposed once patched: SSH host keys leaked this way let an attacker impersonate the host to clients that trust it, and a cracked root hash from /etc/shadow is a permanent foothold until the password changes.
The structural lesson matters just as much. Jann Horn flagged the file-descriptor-theft shape of this bug in October 2020, six years before a public exploit appeared. A review comment did not stop the bug from shipping in every stable kernel of that period. For teams running multi-user systems, that is an argument for treating local privilege boundaries as hostile territory by default: minimize interactive shell access, keep setuid tooling audited, and never assume that a known-but-unpatched kernel issue is safe because no exploit has surfaced yet. Eventually one does, and the window between disclosure and exploitation keeps shrinking.
The controlled-target proof
Alongside the two system targets, the repository includes a pair of programs that demonstrate the primitive without touching real system files. vuln_target.c opens /etc/shadow and then drops privileges, mimicking the setuid pattern of the real targets. exploit_vuln_target.c shows the contrast that defines the bug: while the target process is alive and has an mm, the descriptor duplication attempt fails with EPERM, and once the target is killed with SIGKILL and passes through the exit window, the same attempt succeeds and the file contents come out.
That before-and-after framing is the most useful thing in the repository for anyone who needs to validate a patch. Run it on a vulnerable kernel and watch EPERM turn into a successful read; run it again after updating and watch the steal fail every time. It converts an abstract CVE description into a two-minute test you can run during a maintenance window, which is exactly the kind of artifact that turns a security advisory into something an operations team can actually act on.
Editorial conclusion
CVE-2026-46333 is a clean example of a logic bug hiding in plain sight: a dumpable check skipped for mm-less tasks, teardown ordering that leaves descriptors open in that gap, and two decades-old setuid helpers holding root-owned files open at exactly the wrong moment. The fix has been in mainline since 2026-05-14, so the remaining work is operational: find the unpatched kernels in your fleet, rotate any host keys and password hashes that could have been read, and file the six-year gap between review flag and public exploit under lessons about how long these windows actually stay quiet.
Frequently asked questions
Is my Linux system affected by CVE-2026-46333?
A system is exposed if it runs a kernel built from sources predating commit 31e62c2ebbfd, merged on 2026-05-14. The repository confirms exploitation against Raspberry Pi OS Bookworm 6.12.75, Debian 13, Ubuntu 22.04/24.04/26.04, Arch, and CentOS 9. Kernels built after that commit contain the fix, so applying your distribution kernel update closes the bug.
What data is at risk from CVE-2026-46333?
The documented attacks read SSH host private keys (/etc/ssh/ssh_host_ecdsa_key, ssh_host_ed25519_key, ssh_host_rsa_key) through the ssh-keysign helper and the contents of /etc/shadow through chage. Leaked host keys allow host impersonation against trusting clients, and the shadow file exposes password hashes for offline cracking.
Who discovered CVE-2026-46333?
Qualys reported the underlying kernel bug, and the fix was merged by Linus on 2026-05-14. The file-descriptor-theft shape of the problem had been flagged publicly by Jann Horn in October 2020, six years before the public proof of concept appeared.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/0xdeadbeefnetwork-ssh-keysign-pwn)