β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0510

Credential bypass via thread0 fallback in socket operations: root creds for all ksocket ops

Summary

3 call sites(:546,:661,:886): td=curthread->td_proc?curthread:&thread0 /* XXX broken */. When netgraph msg processing runs with curthread->td_proc==NULL(kernel threads, softirq, deferred) falls back to thread0=root creds(cr0). td passed to socreate/sobind/solisten/soconnect/sosend/soreceive. PRIV_NET_RAW/SOCK_RAW, PRIV_NET_PRIV_PORT bind<1024, jail address binding, firewall bypass all evaluated against root. Sandbox/jail/capsicum escape via ng_ksocket. Developer comment acknowledges broken. Fix: refuse when td_proc==NULL or store crhold at node creation.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0510 Β· 9 files
FileTypeDescriptionSize
df0510.c trigger-source static analysis of the thread0 fallback credential confusion 3.8 KB view raw
build.sh build-script cc -O2 -o df0510 df0510.c 98 B view raw
run.sh run-script ./df0510 225 B view raw
VERDICT.md verdict mechanism, reachability, fix rationale 3.5 KB ↓ raw
fix.diff suggested-fix replace &thread0 fallback with NULL + EPERM guard at all 3 sites 2.3 KB view raw
run.log run-log static PoC output 1.4 KB view raw
env.txt environment uname + cc version 188 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict mechanism, reachability, fix rationale
↓ download raw

DF-0510 β€” Credential bypass via thread0 fallback in socket operations

Verdict: CODE-PATH CONFIRMED; latent from unprivileged user (root-only netgraph access)

Mechanism (confirmed at code level)

Three call sites in sys/netgraph7/ksocket/ng_ksocket.c use the identical broken expression (developer's own comment "XXX broken"):

  • line 546 (ng_ksocket_newhook): td passed to socreate()
  • line 661 (ng_ksocket_rcvmsg): td passed to sobind(), solisten(), soconnect(), sosend(), soreceive()
  • line 886 (ng_ksocket_rcvdata): td passed to sosend()
struct thread *td = curthread->td_proc ? curthread : &thread0;  /* XXX broken */

When netgraph message processing is deferred to a kernel thread / softirq / netisr (which is normal for netgraph async dispatch), curthread->td_proc is NULL. The fallback &thread0 is the kernel's boot thread, whose td_ucred is the root credential (cr0: uid=0, all groups, all privileges).

All downstream privilege checks then evaluate against root:

  • socreate(AF_INET, ..., SOCK_RAW, ...) β†’ PRIV_NET_RAW / PRIV_NET_PRIV_SOCKET β†’ granted (root has all caps)
  • sobind() to a privileged port (<1024) β†’ PRIV_NET_PRIV_PORT β†’ granted
  • sobind() to a jail-restricted address β†’ jail check bypassed (root cred has no jail)
  • sosend() / soreceive() β†’ firewall / packet-filter checks evaluated as root

The v1 twin in sys/netgraph/ksocket/ng_ksocket.c (the actually loadable ng_ksocket.ko module) has the same defect at lines 559, 640, 880.

Reachability β€” why this is "latent from unprivileged"

ng_ksocket is driven via netgraph messages. Creating a netgraph control socket (socket(AF_NETGRAPH)) requires root:

$ ngctl list
ngctl: socket: Operation not permitted

So an unprivileged user cannot directly reach this code path. The defect matters in two realistic scenarios where the trigger runs from a privileged-but-restricted context:

  1. Jail escape: a jailed root user creates a ng_ksocket. If any message processing is deferred to a kernel thread, the subsequent socket ops run with full host-root creds instead of the jail-restricted cred. The jail's IP-binding restriction is bypassed; the jailed root can sobind() to a non-jail address.
  2. Capsicum sandbox escape: same pattern β€” a capsicum-restricted process using netgraph gets root-cred socket ops when the message is processed asynchronously.

Severity Medium is appropriate: real credential-confusion defect with a concrete sandbox-escape impact, but gated behind privileged netgraph access (so it's a priv-boundary issue within root contexts, not unpriv→root).

Fix

fix.diff replaces the &thread0 fallback with NULL and adds an explicit EPERM guard at each of the three sites:

struct thread *td = curthread->td_proc ? curthread : NULL;
...
if (td == NULL)
    return (EPERM);   /* refuse instead of escalating to root creds */

This matches the finding's recommended fix ("refuse when td_proc==NULL or store crhold at node creation"). A more complete fix would capture crhold() at node creation and reuse it; the conservative refuse fix is minimal and safe.

Build / run

ssh dfbsd-maxx 'mkdir -p poc/DF-0510'
scp findings/poc/DF-0510/{df0510.c,build.sh,run.sh} dfbsd-maxx:poc/DF-0510/
ssh dfbsd-maxx 'cd poc/DF-0510 && sh ./build.sh && sh ./run.sh'
# static code-path analysis; no live kernel trigger (defense-in-depth)

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. ng_ksocket thread0 fallback -> root creds. Latent from unpriv (ngctl root-only). Jail/capsicum escape.