SSH-Keysign-Pwn: Reading Root-Owned Files via a Ptrace Logic Bug

mbeavitt1 pts0 comments

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

&larr; 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...

keysign needhelp data slot size dropdown

Related Articles