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)
PoC verification
Evidence pack
findings/poc/DF-0510 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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):tdpassed tosocreate() - line 661 (
ng_ksocket_rcvmsg):tdpassed tosobind(),solisten(),soconnect(),sosend(),soreceive() - line 886 (
ng_ksocket_rcvdata):tdpassed tososend()
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β grantedsobind()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:
- 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 cansobind()to a non-jail address. - 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_testablecompile 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.
No comments yet.