Linux ptrace exit-race pidfd_getfd() FD theft
A useful Linux kernel privesc pattern is to turn a ptrace authorization bug into file descriptor theft from a privileged process.
In the Qualys __ptrace_may_access() case study (CVE-2026-46333), the attacker races a privileged process that is exiting or dropping credentials and uses pidfd_getfd() to duplicate an FD into the attacker process.[1][2]
Core idea
pidfd_getfd() duplicates a file descriptor from another process, but first checks ptrace-style permissions against the target.[3] If that authorization is incorrectly granted during a teardown window, an unprivileged attacker can copy:
- FDs for sensitive files already opened by a privileged helper
- FDs for authenticated IPC channels already authorized as root
This transforms a kernel-side authorization bug into a very practical userspace primitive.[1]
Why the primitive is dangerous
The attack does not need a bug in the privileged helper itself. The helper only needs to temporarily hold something valuable:
/etc/shadow/etc/ssh/*_key- a privileged D-Bus / systemd connection
- any other already-open secret or authorized channel
Once duplicated into the attacker process, the duplicate refers to the same open file description, so subsequent reads or IPC requests use the already-open FD rather than reopening the original pathname or starting a fresh authentication flow.[2][3]
Exploitation pattern
- Identify a setuid / setgid / file-capability binary or root daemon that opens sensitive files or keeps useful IPC connections.[2]
- Gain a relationship that satisfies the relevant ptrace policy checks for the target path (for example, being the parent of a spawned privileged child under permissive YAMA settings).[2][4]
- Race the process while it is exiting, dropping credentials, or otherwise entering a state where ptrace access should have become unavailable.[2]
- Use
pidfd_open()+pidfd_getfd()to duplicate the target FD during the narrow authorization window.[2][3][5] - Reuse the stolen FD from the unprivileged context.[2]
read()secrets from a privileged file descriptor- send requests over a stolen authenticated IPC channel to get root-side actions
Minimal primitive shape.[1][3][5]
int p = pidfd_open(victim_pid, 0);
int stolen = pidfd_getfd(p, victim_fd, 0);
/* use stolen with read()/write()/sendmsg()/ioctl() depending on target */
Practical targets to audit
Prioritize binaries and daemons that, even briefly, do one of these:[1][2]
- open root-only files before finishing privilege transitions
- connect to the system bus and keep an already-authorized channel
- pass privileged FDs across helper boundaries
- perform security-sensitive work during
do_exit()-adjacent teardown
Good hunting candidates:[1]
- password / account management helpers
- SSH helpers
- PolicyKit / D-Bus mediated helpers
- root desktop daemons that expose D-Bus methods
YAMA as an exploit gate
kernel.yama.ptrace_scope is a major practical gate for ptrace-family abuse:[3][4]
0: classical same-UID ptrace behavior1: typically allows parent -> child tracing, which can keep some public exploit paths reachable2: requiresCAP_SYS_PTRACEfor attach-style access and blocks unprivilegedpidfd_getfd()abuse in this path3: disables ptrace attach entirely until reboot
For this technique, ptrace_scope=2 is a strong temporary mitigation because it breaks the public pidfd_getfd() exploitation path with -EPERM for unprivileged users.[1][2][3]
Detection / review ideas
When auditing privileged Linux software, look for these combinations:
- privileged child process + attacker-controlled parent.[2][4]
- temporary access to valuable open files
- temporary access to authenticated D-Bus/systemd channels.[2]
- security decisions that reuse ptrace-style authorization outside classic
ptrace(2) - kernel APIs that can duplicate, inherit, or re-export existing privileged FDs
When auditing the kernel, treat any path that does ptrace-equivalent authorization during task teardown as high risk, especially if success yields direct access to task->files or other already-authorized process resources.[2]