// HackTricks · Linux

Linux ptrace exit-race pidfdgetfd() FD theft

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

  1. Identify a setuid / setgid / file-capability binary or root daemon that opens sensitive files or keeps useful IPC connections.[2]
  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]
  3. Race the process while it is exiting, dropping credentials, or otherwise entering a state where ptrace access should have become unavailable.[2]
  4. Use pidfd_open() + pidfd_getfd() to duplicate the target FD during the narrow authorization window.[2][3][5]
  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 behavior
  • 1: typically allows parent -> child tracing, which can keep some public exploit paths reachable
  • 2: requires CAP_SYS_PTRACE for attach-style access and blocks unprivileged pidfd_getfd() abuse in this path
  • 3: 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]

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.