ssh-keysign-pwn: Reading Root-Owned Files via a ptrace Logic Bug - needhelp<br>svg]:opacity-70 hover:[&>svg]:opacity-100 focus-visible:ring-outline/50 transition-[color,box-shadow] outline-none focus-visible:ring-3" data-slot="sheet-close" aria-label="Close dialog">
Close
needhelp<br>here to help
if (!validateArgs(args)) throw new AstroError({<br>...InvalidComponentArgs,<br>message: InvalidComponentArgs.message(name)<br>});<br>return cb(...args);<br>}" variant="ghost" size="sm" data-slot="dropdown-trigger">
Switch language
svg]:size-4 [&>svg]:shrink-0 bg-accent" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','en')"><br>English<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/zh/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','zh')"><br>中文<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/es/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','es')"><br>Español<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/ja/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','ja')"><br>日本語<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/fr/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','fr')"><br>Français<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/de/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','de')"><br>Deutsch<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/pt/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','pt')"><br>Português<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/ru/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','ru')"><br>Русский<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/hi/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','hi')"><br>हिन्दी<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/ar/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','ar')"><br>العربية<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/bn/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','bn')"><br>বাংলা<br>svg]:size-4 [&>svg]:shrink-0" role="menuitem" tabindex="0" data-slot="dropdown-item" href="/id/blogs/ssh-keysign-pwn/" onclick="localStorage.setItem('needhelp-locale','id')"><br>Bahasa Indonesia
← Back to blog ssh-keysign-pwn: Reading Root-Owned Files via a ptrace Logic Bug<br>Published on 5/15/2026 by xingwangzhe<br>Linux
Kernel
Security
Qualys
ptrace
CVE
ssh-keysign-pwn : A six-year-old logic bug in the kernel’s __ptrace_may_access() function, reported by Qualys and patched by Linus Torvalds on May 14, 2026. Unprivileged users can read root-owned files including SSH host private keys and /etc/shadow. All pre-31e62c2ebbfd kernels affected.
Another One
May 2026 has not been kind to the Linux kernel. Dirty Frag, Fragnesia, and now—ssh-keysign-pwn —disclosed by Qualys on May 14 and fixed by Linus Torvalds the same day.
This one is different from the “copy something into page cache” family. It’s a pure logic bug in __ptrace_may_access(), the kernel function that decides whether one process can inspect another.
MetricValueReported by Qualys Security AdvisoryFixed by Linus TorvaldsFix commit 31e62c2ebbfdLurked for ~6 yearsFirst flagged by Jann Horn (Google), October 2020PoC published by _SiCkImpact Read root-owned files as unprivileged userExploit complexity 100–2000 spawns per successful steal
How It Works
The bug lives in __ptrace_may_access(). This function is the gatekeeper for process introspection — it checks whether a process is allowed to poke around in another process’s state.
There’s a special case: when task->mm == NULL (the target process has no memory descriptor — happens when a thread is exiting, or for kernel threads), the function skips the dumpable check entirely .
Here’s the code path that makes this exploitable:
A process calls do_exit() to terminate
do_exit() runs exit_mm() first — this tears down the memory descriptor (mm)
Then it runs exit_files() — but the file descriptors are still alive at this point
With task->mm == NULL but file descriptors still open, pidfd_getfd(2) can steal those fds if the caller’s uid matches
Normally, ptrace access checks would block a less-privileged process from reaching into a root process’s open file handles. But the mm == NULL bypass knocks out the dumpable check, and pidfd_getfd(2) does the rest.
This is a textbook TOCTOU race, but the window is wide enough that the PoC hits in 100–2000 attempts.
Exploit Targets
Two tools were published:
sshkeysign_pwn — targets...