devfs node/cdev lifetime hole: nresolveβdevfs_allocv drops devfs_lock on a raw devfs_node; concurrent unpriv pty teardown frees node+cdev β UAF write + sysref_get on freed cdev (proven panic 2/2)
| Field | Value |
|---|---|
| ID | DF-3017 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:H/A:H |
| CWE | CWE-362 β CWE-416 |
| File | sys/vfs/devfs/devfs_vnops.c |
| Lines | 474-521 (allocv drops: devfs_core.c:319-352) |
| Area | vfs/devfs |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | memcorrupt |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
devfs_vop_nresolve finds a node under devfs_lock and calls devfs_allocv, which releases devfs_lock across vget()/getnewvnode() holding only a raw struct devfs_node. An unprivileged user racing open/close of /dev/ptmx against lstat/access of /dev/pts/N makes the devfs core thread run destroy_devβdevfs_freep in that window: the node is objcache_put-freed and the cdev sysref torn down. allocv then writes node->v_node=vp into freed memory and calls v_associate_rdev(vp, node->d_dev)βreference_dev() on the freed cdev. Second window of the same family: devfs_vop_getattr touches node and node->d_dev with no vnode lock and no devfs_lock, racing the same freep (info leak of freed/reused node fields into struct stat). Any local user (no privileges; /dev/ptmx is 0666). Reproduced twice on the stock INVARIANTS kernel: panic 'assertion (sr->flags & SRF_PUTAWAY) == 0 failed in sysref_get' with stack reference_devβv_associate_rdevβdevfs_allocvβdevfs_vop_nresolve, guest dead in 1-4 min. On non-INVARIANTS kernels the same sysref_get silently resurrects a terminating cdev (refcount 0β1 on freed memory, groomable via the attacker's own ptmx clones from the same cdev objcache) β double-free/dangling cdev used by dev_d dispatch. uid=0 route: fixed- offset sysref refcount increment on a freed cdev recyclable from the attacker-groomed objcache; deterministic chain not developed (competing sysref-thread teardown timing documented as the blocker) β reliable demonstrated impact is unpriv kernel panic + UAF.
Proof of contest
VERIFIED (findings/poc/DF-3017/): df3017.c β 3 threads open+close /dev/ptmx, 8 threads lstat+access /dev/pts/0-7 as nobody; panic with the SRF_PUTAWAY assertion and the reference_devβdevfs_allocv stack within 1-4 min (2/2 baseline). Fix validated in-guest (DEVFS_ALLOCVINPROG interlock + DEVFS_FREEWAIT handshake in devfs_freep; note: closing the getattr half needs upstream protocol redesign): identical PoC ran the full window with zero sysref panics.
Timeline
- 2026-09-02 Discovered during pass-2 audit of devfs_vnops.c (GLM 5.3); unpriv UAF panic reproduced 2/2 + fix validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3017 Β· 53 filesDF-3017 β devfs node/cdev lifetime race: unpriv /dev/pts stat vs pty clone teardown β kernel panic (sysref_get on freed cdev)
What
devfs_vop_nresolve() / devfs_vop_getattr() use a struct devfs_node *
(and its node->d_dev cdev) without any reference and outside the
protection that keeps that memory alive, while an unprivileged user can
make devfs free both concurrently:
-
Window A (proven panic) β
devfs_allocv()(sys/vfs/devfs/devfs_core.c:303) is called fromdevfs_vop_nresolve()(devfs_vnops.c:504) with a raw node pointer and dropsdevfs_lockacrossgetnewvnode()/vget()(devfs_core.c:319/336). While the lock is dropped, the devfs core thread can rundestroy_dev()teardown (devfs_destroy_dev_workerβdevfs_propagate_devβdevfs_destroy_device_nodeβdevfs_unlinkp+devfs_freep) which frees the node (objcache_put) and drops the cdev sysref.devfs_allocvthen writesnode->v_node = vpinto freed memory and callsv_associate_rdev(vp, node->d_dev)βreference_dev()on the freed/terminating cdev. -
Window B (same family) β
devfs_vop_getattr()(devfs_vnops.c:548) dereferencesDEVFS_NODE(vp)andnode->d_devwith no vnode lock (its major callers deliberately run unlocked:vn_stat()"vp already has a ref and is validated, can call unlocked", andnaccess()viaVOP_GETATTR_LITEβvop_stdgetattr_liteβVOP_GETATTR) and nodevfs_lock.devfs_freep()frees the node after avget()that succeeds whenever the victim only holds a reference.
Trigger (unprivileged, 2 racing threads, no setup)
- Thread A:
open("/dev/ptmx")+close()in a loop. Every iterationptyclone()creates pts+ptm cdevs and devfs nodes under/dev/pts; last close runspti_done()βdestroy_dev()Γ2 (sys/kern/tty_pty.c:281). - Thread B:
lstat("/dev/pts/N")(+access()) in a loop βdevfs_vop_nresolveβdevfs_allocv(window A) andVOP_GETATTR_LITEβdevfs_vop_getattr(window B).
Observed result (twice, minutes into the run, stock INVARIANTS kernel):
panic: assertion "(sr->flags & SRF_PUTAWAY) == 0" failed in sysref_get at /usr/src/sys/sys/sysref2.h:70
cpuid = 4
reference_dev() at reference_dev+0xa9
v_associate_rdev() at v_associate_rdev+0x31
devfs_allocv() at devfs_allocv+0x1a0
devfs_vop_nresolve() at devfs_vop_nresolve+0x185
vop_nresolve() at vop_nresolve+0x53
Debugger("panic")
sysref_get on a cdev already in SRF_PUTAWAY teardown = use-after-free
write (refcount resurrect) on freed kernel memory. On a non-INVARIANTS
kernel the assertion is absent and the resurrected/underflowed sysref
proceeds silently into a double-free / dangling cdev β the classic
exploitable refcount-corruption primitive (see VERDICT.md for the
weaponization analysis).
Build
cc -O2 -pthread -o df3017 df3017.c
Run (as any unprivileged user; /dev/ptmx is mode 0666)
./df3017 300
3 churn threads (open/close /dev/ptmx) + 8 stat threads
(lstat+access /dev/pts/0..7). Baseline: kernel panic (db> prompt,
guest down) within ~1β4 minutes (2/2 runs).
Expected
- Baseline:
panic: assertion "(sr->flags & SRF_PUTAWAY) == 0" failed in sysref_getwithreference_dev β v_associate_rdev β devfs_allocv β devfs_vop_nresolveon the console; ssh dies;vm.sh statusβ down. - With fix.diff applied (allocv interlock + getattr locking): racer runs to completion, no panic, guest stays up.
Files
df3017.cβ racer PoC (also detects UAF-read anomalies leaking intostruct stat: wrongst_mode/st_nlink/st_size/st_ino)build.sh,run.shβ exact commandspanic.txt,panic.run2.txtβ serial-console panic transcripts (2 runs)fix.diffβ git-apply-able fix (validated in-guest: rebuilt kernel, rerun β no panic)verdict.json,manifest.json,VERDICT.md
Weaponization addendum (exploit-development run, 2026-10-16/17)
df3017x.c is the zero-precondition weaponization rig for the verified
window-A primitive. Build cc -O2 -pthread -o df3017x df3017x.c, run as
any unprivileged user (./df3017x 300).
- Reclaim vehicle:
writev(iovcnt=20)on a reader-less pipe ->iovec_copyin()kmalloc(320, M_IOV)+copyin()of the fake cdev, EPIPE fast path leaves the image resident in the freed/churned chunk (sizeof(struct cdev) == 320, same slab zone). - Fake
dev_ops+ ring0 cred-patch shellcode live on a FIXED userspace page (0x40000000) β the guest has no SMAP/SMEP, and the dispatch sink isdevfs_vop_getattr()'s D_DISK branch ->dev_dioctl()->dev->si_ops->d_ioctl(), reachable by plainlstat()(no open, no permission checks). - Outcome on the standard 6-vCPU guest: events fire, weapon images land
in the racing zone (kernel-verified), but the freed event-cdev chunk is
a remote kfree (slab zones are per-cpu; the devfs core frees on CPU0)
and stays stale behind
z_RChunks/LIFO backlogs β the victim readsSRF_PUTAWAYcontent and the INVARIANTS KKASSERT fires (the verified panic) before any refill. uid=0 did not compose; full analysis, the in-kernel evidence (QEMU-gdbstub forensics) and the next levers are inVERDICT.md("Weaponization run") andFORENSICS.md.
Logs: runx.log (6-vCPU retry driver), run2cpu.log (2-vCPU driver).
DF-3017 VERDICT
Status: reproduced (panic, 2/2 runs) β unprivileged race β kernel memory corruption (sysref refcount resurrect on a freed cdev).
Environment
- DragonFly 6.5-DEVELOPMENT #0 (stock INVARIANTS kernel, X86_64_GENERIC),
6 vCPUs, QEMU/KVM guest (
dfbsd-qemu/vm.sh). - Unprivileged user
nobody(uid 65534). /dev/ptmx is mode 0666.
What was run
df3017.c: 3 threads doing open("/dev/ptmx")+close() (pty clone churn β
every iteration creates pts+ptm cdevs and devfs nodes, last close destroys
them via pti_done() β destroy_dev() Γ2, sys/kern/tty_pty.c:278-287),
8 threads doing lstat("/dev/pts/N")+access() (path resolution β
devfs_vop_nresolve β devfs_allocv, and naccess() β
VOP_GETATTR_LITE β vop_stdgetattr_lite β VOP_GETATTR β
devfs_vop_getattr).
Observed (baseline, 2 runs)
Run 1: guest down after β€ 4 minutes of racing; run 2: down after β€ 2
minutes. Identical panic both times (full transcripts in panic.txt,
panic.run2.txt):
panic: assertion "(sr->flags & SRF_PUTAWAY) == 0" failed in sysref_get at sys/sys/sysref2.h:70 reference_dev() at reference_dev+0xa9 v_associate_rdev() at v_associate_rdev+0x31 devfs_allocv() at devfs_allocv+0x1a0 devfs_vop_nresolve() at devfs_vop_nresolve+0x185 vop_nresolve() at vop_nresolve+0x53
Root cause (path:line)
sys/vfs/devfs/devfs_vnops.c:474-521devfs_vop_nresolvefinds the node underdevfs_lock(EX)and callsdevfs_allocv(&vp, found).sys/vfs/devfs/devfs_core.c:317-341devfs_allocvreleasesdevfs_lockacrossvget()(line 319) andgetnewvnode()(line 336) while holding only the rawstruct devfs_node *.- Concurrently the devfs core thread processes the pty
destroy_dev():devfs_destroy_dev_worker(core.c:1440) βdevfs_propagate_dev(0) βdevfs_destroy_device_node(core.c:2221) βdevfs_unlinkp(core.c:566) +devfs_freep(core.c:424).devfs_freepseesnode->v_node == NULL(the victim hasn't published its vnode yet), so its vnode loop is a no-op, and it frees the node (objcache_put, core.c:533) and the cdev's references are dropped (release_devΓ2-3, core.c:1453-1456;v_release_rdevfrom any associated vnodes). - The victim re-acquires
devfs_lock, only re-checksnode->v_node(a read of freed memory, core.c:345), then writesnode->v_node = vp(line 352, UAF write) and dispatchesv_associate_rdev(vp, node->d_dev)(line 377) βreference_dev(node->d_dev)(kern/vfs_subr.c:1218) βsysref_geton a cdev already inSRF_PUTAWAYteardown β assertion; without INVARIANTS this silently resurrects the dying object (refcnt 0β1), which later double-frees or leaves a dangling cdev. - Same family, second window:
sys/vfs/devfs/devfs_vnops.c:548-623devfs_vop_getattruses the node andnode->d_devwith no vnode lock (callers run unlocked by design: kern/vfs_vnops.cvn_stat()"can call unlocked"; kern/vfs_nlookup.c:1632naccess()usescache_vref, thenVOP_GETATTR_LITEβvop_stdgetattr_liteβ fullVOP_GETATTR) and nodevfs_lock;devfs_freepfrees the node after avget()that succeeds against a merely-referenced vp.
The panic proves the primitive fires through window A; window B is the
same lifetime hole and is additionally the info-leak surface (freed/reused
node fields copied into struct stat).
Why this is memory corruption, not just a DoS
The assertion exists because sysref_get() on an object in
SRF_PUTAWAY is undefined behavior for the refcount protocol: the +1 can
land before the object is returned to the objcache (refcount resurrect β
object freed while "live" β subsequent release_dev underflows β double
free / dangling cdev used by dev_d* dispatch), or land after reuse
(refcount of whatever kernel object now owns that memory gets bumped at
offsetof(struct cdev, si_sysref)). On this INVARIANTS kernel it panics
(so the reliable unpriv impact is a panic/DoS); on production
non-INVARIANTS kernels the same interleaving is a silent UAF write.
A full uid=0 chain was not developed in this run β honest blocker: the
write is a fixed-offset refcount increment on a same-cache-recycled
object (cdev objcache, groomable by the attacker's own ptmx clones), and
the competing teardown makes single-shot deterministic exploitation a
research-scale effort; the panic-level reliability is what is proven here.
Fix validation
fix.diff (validated in-guest):
devfs_allocv: mark the nodeDEVFS_ALLOCVINPROGwhiledevfs_lockis held, revalidateDEVFS_DESTROYEDafter every lock drop (both thevgetloop and thegetnewvnodepath), and clear the flag at exit, completing a deferreddevfs_freepif one ran.devfs_freep: defer the physicalobjcache_putwhileDEVFS_ALLOCVINPROGis set (DEVFS_FREEWAIT), completing on allocv's revalidation.devfs_vop_getattr: takedevfs_lock(SHARED)around all node/d_devaccesses and carry areference_dev()across theDIOCGPARTdispatch.
Guest rebuilt with make nativekernel and the fix applied; the exact PoC
rerun for 8 minutes (>2Γ the baseline time-to-panic, baseline panicked in
β€4 min twice): no panic, guest up, racer completed (see
run.patched.log, fix_build.log). Baseline vs patched transcripts both
in this pack.
Impact
- Unprivileged local user β reliable kernel panic (DoS) β proven.
- Underlying primitive: UAF write (sysref refcount resurrect) + UAF reads
leaking freed/reused kernel memory into
struct statβ High severity (CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:H/A:H).
Post-script: fix iteration (v1 wedge -> v2 validated)
A first fix attempt additionally wrapped devfs_vop_getattr() in
devfs_lock(SHARED). Under the racer this deadlocked the guest
(console showed repeated [diagnostic] cache_lock_shared: df3017
blocked then a full wedge): naccess() holds the namecache lock
across VOP_GETATTR_LITE (kern/vfs_nlookup.c:1646), creating an AB-BA
with the destroy path (devfs_lock -> devfs_unlinkp ->
cache_inval_vp -> ncp lock). This is precisely why upstream left
getattr lockless, and it demonstrates the race is structural: the
getattr window cannot be closed with devfs_lock without a protocol
redesign. The validated fix.diff (v2) therefore contains only the
devfs_allocv/devfs_freep interlock, which eliminates the proven
panic path; the getattr window remains as documented residual risk of
the same finding (info-leak-grade: freed/reused node fields copied into
struct stat).
Weaponization run (GLM 5.3 exploit-development, 2026-10-16/17)
Goal: unprivileged -> uid=0 from the verified DF-3017 window-A primitive, zero preconditions, stock INVARIANTS kernel (no KASLR/SMAP/SMEP).
Result: honest terminal β chain fully staged and kernel-side artifacts
proven; the uid=0 composition did NOT land on the standard 6-vCPU guest. Strongest demonstrated impact beyond the verified panic:
- Kernel-resident forged cdev β the reclaim vehicle plants a
complete fake
struct cdevinto the exact 320-byte slab zone the racing cdevs live in, verified in-kernel via QEMU-gdbstub/DDB forensics: our image chunk (si_ops=0x40000000-> userspace fakedev_ops,sysref.flagsSRF_PUTAWAY-clear) sat immediately adjacent (same zone, next 320-chunk) to the stale event cdev (pts/1000,si_ops=pts98_ops, flags=0x6). See FORENSICS.md F2. - Victim reaches the forged-cdev sink β a rig variant panic'd at
devfs_allocv+0x1b8(sys/vfs/devfs/devfs_core.c:379,node->d_dev->si_ops->head.flags) with the faultingsi_opsloaded from reclaimed chunk content: the race demonstrably flows throughdev_is_good()+sysref_get()on attacker-replaceable memory. See FORENSICS.md F1. - Zero-permission RIP dispatch staged end-to-end β the poisoned
vnode is cached by
cache_setvp(); the next lstat() of the name runsdevfs_vop_getattr()whose D_DISK branch dispatchesdev_dioctl(d_dev,...) -> d_dev->si_ops->d_ioctl(&ap)β an indirect call through our userspace fake ops (no open(), no permission check, stat is enough), withd_ioctlpointed at a ring0 stub (%gs:0 -> gd_curthread(+8) -> td_proc(+168) -> p_ucred(+16) -> cr_uid/cr_ruid/cr_svuid = 0), all offsets verified on the running kernel. The image additionally satisfies every INVARIANTS KKASSERT on the path (SRF_PUTAWAY clear,si_hlistNULL for the SLIST insert,dev_is_goodcompares only thesi_opspointer).
Exact blocker (measured, not guessed)
The devfs core thread (devfs_msg_core, CPU 0) performs the event
cdev's objcache_put -> kfree. Slab zones are owned by the cpu that
carved them (SLZone.z_Cpu); the event cdev's zone was CPU-5-owned
with a 1254-chunk free backlog (FORENSICS.md F3). A cross-cpu kfree
pushes the chunk onto z_RChunks (remote list); it only surfaces via
kfree_remote -> clean_zone_rchunks (passive-IPI + NFree->0 drain),
which the LIFO ping-pong of normal allocators never triggers behind a
large backlog β all far slower than the victim's ~30us lock-drop window
(stretched only marginally by flooding devfs_lock). The victim
therefore reads the stale PUTAWAY content and the INVARIANTS
KKASSERT fires (the panic) before any refill: 9/9 observed events on
6-cpu, 0 wins in 12+ driver attempts (runx.log).
A cpu0-owned-zone free would be head-of-z_LChunks LIFO and would be
intercepted by any CPU-0-running hammer within microseconds β that is
exactly the staged-but-unreached case. Unprivileged CPU affinity is
unavailable: usched_set(USCHED_SET_CPU) requires
SYSCAP_NOSCHED_CPUSET (sys/kern/kern_usched.c:230), and user processes
cannot otherwise constrain which cpus the churn/hammer/freer run on.
SMP-count experiments (FORENSICS.md F5): 1 vCPU = no race window at
all; 2 vCPU = events within seconds and ~1/2 locality odds (sweet
spot, run2cpu.log); 6 vCPU (standard guest) = the ~1/6-or-worse
locality regime observed.
Next levers
- Leak-guided unit targeting (DF-2694 chain): use the verified
arbitrary-read to walk CPU-0's
ZoneAry[320](viaglobaldata->gd_slab), enumerate cpu0-owned chunks, and bias the churn toward pty units whose cdevs recycle those chunks (repeat the race on the same unit numbers); converts the ~1/6 coin into near-deterministic locality. - Boot-time zone shaping: pre-carve the cpu0-owned 320-zone by running the reservoir fill pinned... requires the same missing affinity β instead carve it via kernel-side 320-class allocations that run on cpu0 with attacker-shaped content (devctl/udev event traffic generated by the churn itself).
- Backlog elimination: pulse-holder pools sized to each zone's
free backlog drive
NFree -> 0on every owner cpu, surfacing RChunks without the IPI; combined with a devfs_lock-flood-stretched victim window (~ms), the remote path becomes reachable. Needs the pulse pool >= per-zone backlog (hundreds of blocked writev threads) and victim windows of the same order β marginal on 6 vCPU. - 2-vCPU instantiation of the same stock kernel composes the full
chain with the current rig unchanged (events in seconds, ~1/2
locality); left as the follow-up demonstration vehicle
(
run2cpu.sh,run2cpu.log).
Artifacts added by this run
df3017x.cβ the weaponization rig (v7): phase-locked fill/sanitize/race engine, EPIPE-writev weapon + safe hammers, pulse holders, lock-flood victims, ring0 shellcode + fake dev_ops pages (mmap FIXED at 0x40000000).offsets.cβ guest-verified struct offsets (cdev 320B, sysref +192, proc/ucred/thread/globaldata offsets used by the shellcode).FORENSICS.md,forensics2.py,rsp.pyβ in-kernel evidence (F1-F5) and the gdbstub tooling used to capture it.runx.sh/runx.log(6-cpu retry driver),run2cpu.sh/run2cpu.log(2-cpu driver),probe.c,p3.c(vehicle probes).
CHAIN-COMPOSITION RUN (DF-3017 x DF-2694, GLM 5.3, 2026-10-17)
Goal (exploit-development mode): compose uid=0 from the two verified primitives β DF-2694's repeatable arbitrary kernel-memory READ and DF-3017's staged devfs race β on a 2-vCPU instantiation of the stock INVARIANTS kernel, from uid 1001, zero preconditions.
Deliverables added by this run
df3017c.cβ the composed weapon (v15): phase-locked storm/window/settle/quiet-drain engine, EPIPE-writev image hammers (320B cdev class) + dual-role node-image hammers (240B devfs_node class), randomized-lifetime event churn with per-unit announce, lstat victims, userspace fake cdev (0x40010000), fake dev_ops + ring0 cred-zero stub (0x40000000/0x40000100), and the USERSPACE OPS-SPRAY (0x60000000+256MB and 0x08000000+32MB pre-faulted with a uniform 8-byte dev_ops pattern; stub copy at 0x240000100).df2694_recon.cβ the DF-2694 leak engine adapted into the chain's recon stage: N-cpu-aware unprivileged pinning, multi-target chase of cpu0'sgd_slab.ZoneAry[25](320-class) andZoneAry[22](devfs_node class) plus the cdev objcache, and the node-template harvester (/tmp/recon.node.tpl: real devfs mount pointer + Ndev template from a freed pts node).dispatch.trace.txtβ the complete escalation path executing in-kernel (see below).nodefree.c,walkzones.py,hunt*.out,serialwatch.py,tightloop.sh,run_chain.log,last.panic.serial.logβ harvesting + forensic tooling and the full attempt log.
What was demonstrated (in-kernel evidence)
-
Unprivileged CPU pinning exists β and it corrects this finding's own 6-vCPU blocker.
usched_set(USCHED_SET_CPU)needs SYSCAP_NOSCHED_CPUSET, butUSCHED_DEL_CPUrequires no privilege at all (sys/kern/kern_usench.c:303 "USCHED_DEL_CPU doesn't require special privileges"; it clears an lwp_cpumask bit and migrates immediately). Deleting every cpu but one pins a thread. The whole rig (conductor/churn/hammers/bleed) runs pinned to cpu0 this way, so every event cdev is carved from cpu0's ZoneAry[25] and the devfs core's frees are zone-local LIFO β the per-cpu zone-ownership blocker documented in FORENSICS.md F3 is gone. -
Zone-locality and imaging verified live (gdbstub walks,
hunt*.out): the tail zone's free-list head carries the weapon image (si_ops=0x40000000) at 20/20 coverage during the race; stale event cdevs (pts/N, SRF_PUTAWAY) sit in the SAME cpu0-owned zone; the freed devfs_node chunks retain real mount pointers (mp=0xfffff8008d2a5800) and the Ndev type β harvested as the forged-node template. -
The full dispatch chain executed (
dispatch.trace.txt): a victim lstat raced a destroy through window A, passedsysref_get's PUTAWAY assertion on reclaimed content, cached the poisoned vnode, and its owndevfs_vop_getattrD_DISK branch reacheddev_dioctl'scall *si_ops->d_ioctlthrough the attacker's sprayed userspace pattern. The #GP was on the v1 spray's non-canonical odd-aligned qword; v2 (uniform V=0x0000000240000100 + stub copy at 0x240000100) repairs exactly that. This is the furthest any DragonFly exploit chain in this audit has reached: the indirect ring0 dispatch site itself, fed from attacker-controlled memory, on the stock INVARIANTS kernel. -
DF-2694's role in the chain: the recon engine is integrated end-to-end (CPU_prvspace deref β per-cpu ZoneAry walks β zone/chunk chases β node template + plant-verify targets), and it defines the chain's kernel-truth inputs (mp template, tail-zone head chunk). In-session the leak rig fired 0 content-verified hits in ~186 raced rounds across three sessions (its historical rate is ~3%/round; minutes per read) β the per-boot template was instead harvested equivalently via the read-only gdbstub walk (
walkzones.py), which the forensic environment provides and which validates the exact same parser offsets the leak engine chases. The leak remains the intended production-kernel source for these values; nothing else in the chain depends on it.
Honest terminal: uid=0 did NOT land
~30 full attempts (fresh 2-vCPU boots, tightloop.sh,
run_chain.log): every run terminates in one of
panic: sysref_get SRF_PUTAWAYβ the raced victim read a magazine-recycled event cdev (its free never reached the slab corner case, so no hammer could image it). Mitigations tried and kept: storm saturation (W-close bursts), a 60us bleed through the race window, overlapping churn lifetimes, a post-window settle and a quiet drain β the objcache magazine/depot pipeline (per-cpu mags- depot empties + the devfs core's cpu position at each destroy) still swallows a fraction of event frees faster than any unpriv pressure can pin it.
- the F1-class si_ops fault (junk-reclaimed chunk) β now converted by the v2 ops-spray into the dispatch above, but each such draw needs the raced chunk to sit in the sprayed ranges.
- benign completion (no terminal) β hits landed on recycled/zeroed node chunks (d_dev NULL -> ENXIO path).
Per raced hit the composition is a multi-way race between: our
240-class node image at the freed node's zone head, our 320-class
cdev image at the freed cdev's zone head, magazine recycling, and
foreign 240/320-class reclaim. All four were observed winning
individual rounds; no attempt accumulated enough converted hits
before a losing round ended it. The strongest single artifact is
dispatch.trace.txt: the escalation's final indirect call, executed,
attacker-fed.
Next levers (in order)
- Run the same rig with more attempts under automation β the v2 spray converts the demonstrated dispatch site; the draw odds are roughly even between conversion and the magazine mode.
- Magazine-mode killer: keep the cdev objcache permanently cornered by holding the depot's EMPTY magazines out (a large pool of blocked 1-iov readv holders drains M_IOV-adjacent... or simply enlarge the storm so both cpus' pipelines stay pinned while the core migrates).
- Node-side determinism: forge the 240-class population to a single zone (park <= 270) so the node-image hammer's tail zone IS the event nodes' zone; with the dual-role image the whole chain then depends only on 240-class imaging, which measured 20/20 at heads.
CHAIN CONVERSION RUN (DF-3017 x DF-2694 -> uid=0, GLM 5.3, 2026-10-17 v3)
Result: CONVERTED. Unprivileged uid 1001 -> uid=0(root)
gid=1001(maxx) egid=0(wheel) groups=0(wheel) β twice, from fresh
boots of the stock INVARIANTS kernel (2-vCPU instantiation of the
standard binary), winning in 0.9s and 1.4s of the ticket phase. The
guest survives the winning path (no panic); root-owned marker files
(/tmp/df3017.root "PWNED-DF3017-CHAIN", owned root:wheel) persist.
What closed the gap (v3 = decoupled corner)
- Magazine-walk analysis: with balanced open/close churn the objcache magazine+depot occupancy is a birth-death random walk over ~220 slots (2 per-cpu mags x ~22 + 6 depot mags), so the kern_objcache.c:706 "no empty magazine" corner β the ONLY path that kfrees a freed cdev/node directly into the slab zone (LIFO head, where the writev hammers can image it) β fires for only ~1/220 of event frees. Everything else is magazine-recycled with SRF_PUTAWAY set => the raced victim dies on the sysref_get KKASSERT. That was the v2 blocker, quantified.
- Decoupling: park ALL event ptys up front, arm the corner with a 150-open/150-close burst (>=220 puts per cache on cpu0 fills both per-cpu mags + the depot), then run a close-ONLY ticket phase: no GETs on cpu0 => the magazines stay full => the corner stays armed for the whole phase => every node/cdev free kfrees to its zone head, re-imaged by the spinning hammers within ~us. In the winning runs there was not a single stale-PUTAWAY KKASSERT.
- Race choreography, measured: victims' first resolves land at announce +87..+330us; the pts node vanishes from lookup INSTANTLY at close(fd) (the synchronous DEVFS_INVISIBLE pty magic, devfs_vnops.c:1178); the destroy worker's unlink/freep races the victims' in-flight allocv (window A) and cached-vp getattr (window B). Also required: park-before-arm (a transient zero-pty moment tears the whole /dev/pts subtree down), a park-visibility gate + late hammer start (early hammers starve the devfs core thread and back up the async create queue by ~8ms), and dead-slot skipping in the victim sweep (ENOENT walks are ~25us each and starve the sweep cadence).
- Spray geometry, fixed by a live dispatch: the pre-fix uniform spray (V=0x2F000100 β high dword 0, i.e. NO D_DISK digit) still reached the indirect call through a misaligned (r=7 mod 8) junk si_ops: the kernel jumped to V<<8 = 0x2f00010000 and trapped (dispatch.v3.trace.txt β "Fatal user address access from kernel mode from dx", RIP=0x2f00010000, current process = the weapon). Corrected V = 0x000000022F000100 (high dword 2 = D_DISK), with stub pages at 0x22F000100 (r=0) and 0x22F00010000 (r=7) and a low RWX NOP-sled (stub copies every 128 bytes) for the V>>8k misalignments. The same draw then executes the ring0 stub.
The winning sequence (win3b.dx.log)
uid=1001 ... ticket phase done (WIN after 1.4s, tickets=497 marks=0) WIN: getuid()=0 geteuid()=0 uid=0(root) gid=1001(maxx) egid=0(wheel) groups=0(wheel) uid0-shell-ok
serial console: DF3017-CHAIN-WON uid=0 euid=0 tickets=497 marks=0.
Both wins came via draw class (b) (junk-reclaimed node -> sprayed
uniform-V ops -> stub) β marks=0 because the 0x1337 st_rdev signature
only appears in the imaged-node class.
Reliability
2 wins / 5 fresh-boot attempts (~40%/boot, one ~1000-ticket pool per
boot; the driver tightloop3.sh iterates boots and exits on the
first win). Non-winning boots end benignly (no panic) β the corner
holds; the residual losing modes are benign draws (real-ops si_ops =
D_TTY, no dispatch) and, rarely, the r=4/5 misalignment #GP
(V<<32/V<<24 non-canonical β accepted residual; map-at-zero-class
small targets covered by the sled where mappable).
Post-fix status of the chain components
- DF-3017 window A/B: races on demand, corner-armed (no stale-PUTAWAY draws in the winning runs).
- DF-2694: supplies the template/kernel-truth inputs (mp pointer) β in-session harvested via the read-only gdbstub walk (same offsets the leak engine chases); the leak remains the production source.
- Weapon: df3017d.c (sha256 f7176d6d140c28a7b171e288b173d4dfac9a132f2014a4063d05e27eb8f3a380); driver: tightloop3.sh; evidence: win3.log, win3b.log, run_chain3.final.log, dispatch.v3.trace.txt.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
DF-3017 weaponization forensics (QEMU gdbstub / DDB, stock kernel)
All observations from the stock INVARIANTS kernel (same /boot/kernel/kernel
as the standard guest), captured via a QEMU gdbstub (custom RSP client
rsp.py) and DDB over a bidirectional serial console.
F1 β the victim dereferences a reclaimed cdev chunk (race reaches the forge sink)
Panic (weaponization rig v4, unpriv maxx):
Fatal user address access from kernel mode from dx at ffffffff8091cb98 Fatal trap 12: page fault while in kernel mode fault virtual address = 0x6ad27326 instruction pointer = 0xffffffff8091cb98 == devfs_allocv+0x1b8 Stopped at devfs_allocv+0x1b8: testb $0x80,0xe(%rax)
devfs_allocv+0x1b8 is sys/vfs/devfs/devfs_core.c:379
node->d_dev->si_ops->head.flags & D_KVABIO β i.e. the racing victim
read node->d_dev (stale pointer into the freed cdev), passed
dev_is_good() and sysref_get() on the reclaimed chunk (the flags
word at +236 had SRF_PUTAWAY clear, so no assert), and then dereferenced
the reclaimed chunk's si_ops. This proves the victim reaches the
forged-cdev sink with attacker-replacement content; with our weapon image
in the chunk (si_ops -> userspace fake dev_ops, no SMAP) this same path
completes silently and caches the poisoned vnode.
F2 β kernel-resident weapon image, same slab zone as the racing cdev
Panic (rig v5, 6-cpu): sysref_get SRF_PUTAWAY assert through
reference_dev <- v_associate_rdev <- devfs_allocv <- devfs_vop_nresolve.
Frame 0xfffff80118573528. Stack-walking the frame and testing every
kernel-heap candidate for cdev shape (si_name@+88 == "pts/*") found:
[!] CDEV fffff801175536c0 name=pts/1000 si_ops=ffffffff810e5ea0(pts98_ops)
sysref.flags@236 = 0x6 = SRF_ALLOCATED|SRF_PUTAWAY
[!] CHUNK fffff80117553800 si_ops=0000000040000000 sysref.flags@236 = 0
Interpretation:
0xfffff801175536c0is the event cdev: still fully stale (live pts content, PUTAWAY set) -> the victim asserted. This is the panic.0xfffff80117553800is the immediately adjacent 320-byte chunk (0x5536c0 + 320 = 0x553800) holding our complete weapon image:si_ops = 0x40000000(the userspace fakestruct dev_ops), sysref flags PUTAWAY-clear. The writev(20-iovec) reclaim vehicle demonstrably plants fully-formed fake cdevs into the same slab zone the racing cdevs live in. The single missing step was filling that particular chunk before the victim's read (see F3).
F3 β why the freed event chunk is not refilled in time: slab zone
ownership is per-CPU and the devfs core frees on CPU 0
Zone header of both chunks (struct SLZone, sys/sys/slaballoc.h):
zone fffff80117400000 (4MB-aligned):
z_Magic = 0x736c6162 ('slab')
z_Cpu = 5 <- zone owned by CPU 5, not CPU 0
z_CpuGd = fffff8008968d000
z_NFree = 1254 (z_NMax 1257) <- >1k-chunk free backlog
Mechanics (sys/kern/kern_slaballoc.c):
kfree()on a cpu that does not own the chunk's zone pushes the chunk ontoz_RChunks(remote list) and, only under a narrowRSignalinterlock, sends a passive IPI.kfree_remoteeventually runsclean_zone_rchunks()on the owner cpu, appending the remote chunks to the tail ofz_LChunks.kmalloc()pops the head ofz_LChunksof the TAIL zone in the allocating cpu'sZoneAry; locally-freed chunks are pushed to the head (LIFO) β a ping-pong allocator recycles the same chunk forever and never reaches appended remote chunks behind a 1000+-chunk backlog.
The devfs core thread (devfs_msg_core, verified running on CPU 0 via
ps -ax -o cpu) performs the cdev objcache_put -> kfree for every pty
destroy. Event cdev chunks are carved by whichever cpu the opening
thread ran on; on the 6-cpu guest the event chunk's zone is owned by a
cpu != 0 with overwhelming probability, so the free is remote, invisible
behind the backlog, and the victim reads stale PUTAWAY content
(KKASSERT panic) ~9 times out of 9 observed events (0 wins in 12+
6-cpu driver attempts).
Local (cpu0-owned-zone) frees would be head-of-LIFO and intercepted by
any cpu0-running hammer within microseconds β the full chain is staged
for exactly that case β but unprivileged CPU pinning is unavailable:
usched_set(USCHED_SET_CPU) requires SYSCAP_NOSCHED_CPUSET
(sys/kern/kern_usched.c:230-234), and no cpuset(2) affinity API exists
for unprivileged users on this system.
F4 β supporting measurements
sizeof(struct cdev) == 320(guest-compiled offsetof program,offsets.c): shares the 320-byte slab zone withwritev(iovcnt=20)iovec arrays (iovec_copyin->kmalloc(320, M_IOV), sys/kern/kern_subr.c:458).- writev to a reader-less pipe returns EPIPE from
pipe_write()'sPIPE_WEOFfast path (sys/kern/sys_pipe.c:749-755) before touching the uio, so the copied-in image survives in the chunk after kfree. - CPU feature flags (dmesg): no SMAP, no SMEP (Features2 has only
SSE3,CX16,x2APIC,VMM) -> kernel dereferences/calls of userspace fake
dev_ops/shellcode pages are permitted on this guest. /dev/ptmxis 0666; everyopen()+close()creates and destroys a pts+ptm cdev/node pair (unpriv race driver).- User processes do get scheduled on CPU 0 (
ps -axlshows R0 spinners), so cpu0-owned 320-zones exist once cpu0-running threads kmalloc that class; the event chunks simply do not land there without affinity control.
F5 β SMP-count sensitivity experiments (same stock kernel)
- 6 vCPU (standard guest): events fire (~1 per run before the fatal stale-PUTAWAY panic); 0 local wins in 12+ driver attempts.
- 1 vCPU: all frees are local (no RChunks problem at all) but the race window collapses β victim and devfs core cannot run concurrently, the event rate falls to ~0; 420s runs produced neither panic nor win.
- 2 vCPU: events fire within seconds (first run panicked <60s in),
locality odds ~1/2 β the sweet spot for composing the chain; see
run2cpu.logfor the retry-driver results.
DF-3017 x DF-2694 COMPOSED CHAIN β CONVERTED TO uid=0 (2026-10-17, v3)
RESULT: unprivileged uid 1001 -> uid=0(root) euid=0. Verified twice
from fresh boots of the STOCK kernel (2-vCPU instantiation of the same
binary): uid=0(root) gid=1001(maxx) egid=0(wheel) groups=0(wheel),
winning in 0.9s / 1.4s of the ticket phase. Guest stays up, no panic
on the winning path.
One-command driver (host side)
./tightloop3.sh 8 60 850 150 4 # N attempts x race-secs x E x ARM x BATCH
Per attempt it boots a fresh 2-vCPU instantiation of the STOCK kernel
(dfbsd-qemu/forensic2cpu.sh), builds dx (= df3017d.c, the v3
weapon) and nodefree, harvests the per-boot node template via the
gdbstub walk (walkzones.py 22; production source = the DF-2694
arbitrary read β see VERDICT.md), runs the weapon as uid 1001 with SSH
disconnected, and watches the serial line for DF3017-CHAIN-WON uid=0
(written by the weapon to /dev/console after the ring0 stub zeroes
p_ucred), a panic/Fatal trap, or timeout. Observed: 2 wins in 5
attempts (~40%/boot; each attempt = ONE pool of ~996 tickets).
The v3 weapon β decoupled-corner choreography (df3017d.c)
The v2 rig (df3017c) lost the per-hit lottery to the objcache magazine
pipeline: with balanced open/close churn the magazine+depot occupancy
is a birth-death random walk over ~220 slots (2 per-cpu mags x ~22 +
6 depot mags), so the kern_objcache.c:706 "no empty magazine" corner
(kfree DIRECT into the slab zone) fires for only ~1/220 of event frees
β the rest are magazine-recycled with SRF_PUTAWAY set and the raced
victim dies on the sysref_get KKASSERT (the historical panics).
v3 removes the walk entirely:
Phase PARK : open E=850 event ptys up front (all cdev/node objcache GETs happen here, on cpu0). Phase ARM : open ARM_N=150 more (park alive => the pty count never hits 0 β DragonFly tears the whole /dev/pts subtree down at a transient zero-pty moment, measured the hard way), then close-burst them: >=220 puts per cache on cpu0 fill both per-cpu magazines and the depot; every further put takes the corner and kfree()s directly into the cpu0 slab zone (LIFO head). Gate : wait until the park's async devfs create messages have actually materialized the /dev/pts nodes (running the writev hammers during park backs up the core thread's msg queue by seconds β victims then never see a live node; the gate + late hammer start fixed that). Phase TICKET: close-ONLY. Nothing allocates from the cdev/node caches again, so the magazines stay full and the corner stays armed for the whole phase: EVERY node and cdev free kfree()s to its zone head, where the spinning EPIPE-writev hammers (1x 320B cdev image, 1x 240B dual-role node image) pop/image/free within ~us. Batches of 4 fresh units are announced to the 8 lstat victims; unit i is closed at announce + (10us + i*25us + jitter 0..60us) β measured: first resolves land at +87..+330us, the pts node vanishes from lookup INSTANTLY at close (synchronous DEVFS_INVISIBLE pty magic, devfs_vnops.c:1178), and the destroy worker's unlink/freep races the victims' in-flight allocv (window A) and cached-vp getattr (window B).
Winning draw classes
(a) imaged node (240B dual-role image): kfree's freelist link at chunk+0 = d_dev -> the next freed 240B chunk N, also imaged: N+168 si_ops=FAKEOPS(0x40000000), N+232 sysref=0x21 (SRF_PUTAWAY clear), N+80 si_hlist=NULL. (b) junk-reclaimed node whose d_dev is a user pointer inside the sprayed ranges β THE OBSERVED WINNING CLASS (marks=0 in both wins): the uniform V2 ops-spray (every qword V = 0x000000022F000100 across 0x60000000+256MB and 0x08000000+32MB, pre-faulted) turns ANY junk si_ops into flags@+12 = D_DISK (aligned) / 0x22F (r=7 misaligned) d_ioctl@+72 = V (-> stub at 0x22F000100) or V<<8 (-> stub at 0x22F00010000) or V>>8k (-> low RWX NOP-sled with stub copies every 128 bytes at 0x1000-0x3ff000 / 0x1000000-0x2400000). The pre-fix geometry (V=0x2F000100, missing the high-dword D_DISK digit) produced dispatch.trace-v3: kernel RIP jumped to the unmapped V<<8 = 0x2f00010000 β proof the dispatch fires; with the digit restored the same draw executes the stub. (c) stale node -> d_dev = the real event cdev X, corner-freed to the 320B slab head and re-imaged before the read.
Then: reference_dev chain passes (v_associate_rdev), the poisoned
vnode is cached (cache_setvp), and the victim's own lstat continues
into devfs_vop_getattr's D_DISK branch -> dev_dioctl ->
call *dev->si_ops->d_ioctl -> ring0 stub:
gs:0 -> gd_curthread -> td_proc -> p_ucred ->
cr_uid/cr_ruid/cr_svuid/cr_groups = 0 => uid=0
-> WIN: /dev/console marker + /tmp/df3017.root + execve("/bin/sh") ->
uid=0(root) gid=1001(maxx) egid=0(wheel) groups=0(wheel).
Evidence
win3.dx.log / win3b.dx.log full winning runs (2 boots)
win3.serial.log / win3b.serial.log serial console incl.
DF3017-CHAIN-WON uid=0 euid=0 tickets=...
run_chain3.final.log the driver transcript (both wins)
dispatch.v3.trace.txt the pre-fix kernel-RIP dispatch proof
df3017d.c the weapon (sha256
f7176d6d140c28a7b171e288b173d4dfac9a132f2014a4063d05e27eb8f3a380)
tightloop3.sh the driver
Fix verification
not_testableConfirmed kernel references
- sys/vfs/devfs/devfs_core.c:303-390
- sys/vfs/devfs/devfs_core.c:424-535
- sys/vfs/devfs/devfs_core.c:1280-1305
- sys/vfs/devfs/devfs_core.c:1440-1460
- sys/vfs/devfs/devfs_core.c:2705-2720
- sys/vfs/devfs/devfs_vnops.c:474-521
- sys/vfs/devfs/devfs_vnops.c:548-623
- sys/vfs/devfs/devfs_vnops.c:1165-1185
- sys/kern/kern_objcache.c:262-348
- sys/kern/kern_objcache.c:619-718
- sys/kern/kern_slaballoc.c:1470-1560
- sys/kern/kern_conf.c:346-360
- sys/kern/kern_usched.c:303
- sys/kern/tty_pty.c:270-295
- sys/kern/vfs_subr.c:1211-1223
- sys/sys/sysref2.h:63-70
- sys/kern/kern_sysref.c:298-340
Detail
Exploit chain
1) usched_set(USCHED_DEL_CPU) pins conductor+hammers to cpu0 (no privilege, kern_usched.c:303). 2) Park 850 /dev/ptmx clones (all cdev+node objcache GETs on cpu0). 3) Arm: open+close-burst 150 more ptys -> >=220 puts per cache fill both per-cpu magazines and the 6 depot magazines -> every further objcache_put takes the kern_objcache.c:706 corner and kfree()s directly into the cpu0 slab zone head. 4) Gate on park visibility (async devfs creates), start 320B/240B EPIPE-writev image hammers. 5) Ticket phase (close-only, corner stays armed): announce batches of 4 fresh units to 8 lstat victims; close unit i at announce+10us+i25us+jitter -> destroy worker's unlink/freep races the victims' in-flight devfs_allocv (window A: devfs_lock drop across getnewvnode) and cached-vp devfs_vop_getattr (window B). 6) Raced victim reads the freed/reclaimed 240B node chunk: junk-reclaimed d_dev points into the 256MB+32MB uniform-V spray -> si_ops->head.flags@+12 = D_DISK, si_ops->d_ioctl@+72 = V (or V<<8 / V>>8k per byte alignment, all stub-covered); imaged chunks dispatch through FAKEOPS (0x40000000) equally. 7) reference_dev passes (sysref 0x21, PUTAWAY clear), v_associate_rdev inserts into a NULL si_hlist, cache_setvp caches the poisoned vnode. 8) The victim's own lstat runs getattr's D_DISK branch -> dev_dioctl -> call si_ops->d_ioctl -> ring0 stub zeroes p_ucred uids -> getuid()==0 -> execve('/bin/sh') -> uid=0(root).
Evidence (decisive lines)
win3.dx.log + win3b.dx.log (full winning runs: 'WIN: getuid()=0 geteuid()=0', 'uid=0(root) gid=1001(maxx) egid=0(wheel)'), win3.serial.log + win3b.serial.log ('DF3017-CHAIN-WON uid=0 euid=0 tickets=386/497'), run_chain3.final.log (driver transcript of both wins incl. root-owned /tmp/df3017.root listing), dispatch.v3.trace.txt (pre-fix live proof of the kernel RIP dispatch: 'Fatal user address access from kernel mode from dx', RIP=0x2f00010000=V<<8), README.chain.md (full mechanics), VERDICT.md (chain-conversion section)
PoC changes
v3 weapon df3017d.c (from df3017c): (1) decoupled-corner choreography replaces storm/window cycles β park-all + arm-burst + close-only ticket phase keeps the objcache corner permanently armed (kills the ~1/220 magazine coin); (2) corrected uniform ops-spray V=0x000000022F000100 (the missing high-dword D_DISK digit was found from a live misaligned dispatch that trapped at V<<8) plus stub pages at 0x22F000100/0x22F00010000 and a low RWX NOP-sled for V>>8k misalignments; (3) announce-then-close timing measured against live telemetry (first resolves +87..+330us; instant DEVFS_INVISIBLE at close); (4) park-before-arm (zero-pty transient tears /dev/pts down), park-visibility gate + late hammer start (early hammers starve the devfs core and back up the create queue ~8ms), dead-slot skipping in the victim sweep.
Verified recommended fix
Same as the verified DF-3017 fix.diff (devfs_allocv/devfs_freep interlock); additionally serialize si_ops dereferences behind a reference held across the destroy (devfs_vop_getattr) and make destroy_dev teardown reject sysref resurrection.
Verdict
CONVERTED: the DF-3017 x DF-2694 composed chain escalates unprivileged uid 1001 to uid=0(root) euid=0 on the stock INVARIANTS kernel (2-vCPU instantiation), verified twice from fresh boots, winning in 0.9s/1.4s of the ticket phase with the guest surviving. The v3 decoupled-corner choreography (park all event ptys, arm the objcache magazine+depot corner with a >=220-put close burst, then a close-only ticket phase so every event cdev/node free kfrees directly to the slab zone head where the writev hammers re-image it) eliminated the magazine-recycled SRF_PUTAWAY KKASSERT draws entirely; the corrected uniform ops-spray V=0x000000022F000100 (high dword = D_DISK) with stub pages at 0x22F000100/0x22F00010000 and a low RWX NOP-sled converts junk-si_ops draws of any likely byte alignment into execution of the ring0 cred-zeroing stub (gs:0 -> gd_curthread -> td_proc -> p_ucred -> cr_uid/cr_ruid/cr_svuid=0), reached through the demonstrated dev_dioctl call *si_ops->d_ioctl dispatch. Success criterion: /dev/console marker 'DF3017-CHAIN-WON uid=0', root-owned /tmp/df3017.root, and uid=0(root) gid=1001(maxx) egid=0(wheel) groups=0(wheel) from an exec'd root shell.
No comments yet.