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

revoke(2) on a FIFO silently does nothing (DTYPE_VNODE vs DTYPE_FIFO mismatch)

Summary

vrevoke() forwards non-VCHR vnodes to fdrevoke(vp, DTYPE_VNODE) (vfs_subr.c:1450-1452), but vop_stdopen tags fifo file pointers f_type=DTYPE_FIFO (vfs_default.c:1172-1175); fdrevoke's matcher requires type equality (kern_descrip.c:1999), so for fifos nothing matches and revoke() returns success having closed nothing - the revocation security mechanism fail-opens silently for fifos (works for regular files and devices). Unpriv fifo owner observes directly; realistic ceiling is admin/automation relying on revoke to cut access through a fifo (e.g. after compromise) while every descriptor stays live. VERIFIED unpriv uid 1001: mkfifo+open r+w, revoke returns 0, a byte still flows rfd<->wfd. Fix validated (pass DTYPE_FIFO for VFIFO): write-after-revoke fails EBADF.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3162 Β· 7 files
FileTypeDescriptionSize
revoke_fifo_noop.c β€” 2.2 KB view raw
run.log β€” 286 B view raw
fix_run.log β€” 531 B view raw
env.txt β€” 210 B view raw
fix.diff β€” 494 B view raw
VERDICT.md β€” 2.3 KB ↓ raw
README.md β€” 1.4 KB ↓ raw

DF-3162 β€” revoke(2) on a FIFO silently does nothing

Root cause

vrevoke() forwards non-VCHR vnodes to fdrevoke(vp, DTYPE_VNODE, cred) (sys/kern/vfs_subr.c:1450-1452). But fifo file pointers are tagged f_type = DTYPE_FIFO by vop_stdopen() (sys/kern/vfs_default.c:1172-1175), and fdrevoke's matcher requires info->data == fp->f_data && info->type == fp->f_type (sys/kern/kern_descrip.c:1999). DTYPE_VNODE (1) != DTYPE_FIFO (4) (sys/sys/file.h:125-128), so no fp is ever marked FREVOKED and no descriptor is closed: revoke(2) on a fifo returns success while every descriptor stays open and functional.

revoke(2)'s privilege gate passes for the fifo's owner (sys/kern/vfs_syscalls.c:4741-4742), so this is reachable by an unprivileged user on fifos they own.

PoC

revoke_fifo_noop.c β€” mkfifo, open reader+writer, revoke(path) (as owner), then push a byte through the descriptors.

Build / run

cc -O -Wall -o revoke_fifo_noop revoke_fifo_noop.c
./revoke_fifo_noop          # as any user; fifo is owned by the caller

Expected output (bug present)

fifo opened: rfd=3 wfd=4 (uid=1001)
revoke(/tmp/df3162_fifo) returned 0 (owner is allowed to revoke)
BUG-DF-3162: fds still functional after revoke(2) (data rfd<->wfd still flows)
VERDICT: reproduced

Expected output (fixed kernel)

OK: write after revoke failed: EPIPE (or read side EBADF/EOF)
VERDICT.md
↓ download raw

DF-3162 VERDICT β€” reproduced (security revocation mechanism fail-open for FIFOs)

Reproduced? Yes.

On the stock INVARIANTS guest kernel #0 (2026-09-07), as unprivileged user uid=1001 owning the fifo: revoke(2) returned 0 and the previously-opened descriptors still passed data afterwards (run.log). A working revoke must invalidate the descriptors (writes fail EPIPE / reads EOF), as it does for regular files on this system.

Why (line-level)

Impact

revoke(2) is an administrative/defensive mechanism ("cut off all access through this file now"). For FIFOs it fails silently: the caller believes revocation happened; every reader/writer keeps its access until natural close. An unprivileged user can verify this on their own fifos; exploiting it requires a scenario where someone relies on revoke for isolation (e.g. revoking a fifo used as a service channel after a compromise). Severity Low (mechanism failure, no direct memory safety impact).

Fix validation

fix.diff (this directory): vrevoke passes DTYPE_FIFO for VFIFO vnodes. Applied on the rebuilt guest kernel (same build as DF-3161's fix validation), the PoC output changes from "fds still functional" to descriptors being revoked (see fix_run.log) β€” the previously-opened descriptors no longer pass data after revoke(2).

Chain summary

Unpriv user -> revoke(own fifo) returns success but is a no-op. No escalation path; correctness/defense-in-depth defect.

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

On rebuilt kernel #1 with fix.diff, revoke(2) on the owned fifo invalidates the descriptors (write fails EBADF); bug marker absent.

fix_run.log
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Mon Sep 7 10:27:31 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Evidence (decisive lines)

['run.log: revoke()==0 then data still flows through rfd/wfd (stock kernel)', 'fix_run.log: write after revoke fails EBADF (patched kernel #1)']

PoC changes

Authored from scratch; no seed. Trivial mkfifo/open/revoke/write probe.

Verified recommended fix

vrevoke: fdrevoke(vp, DTYPE_FIFO, cred) for VFIFO vnodes.

Verdict

Reproduced as unprivileged user (fifo owner, uid=1001) on stock kernel #0: revoke(2) returns success but the previously-opened fifo descriptors still pass data - fdrevoke is called with DTYPE_VNODE while fifo fps carry DTYPE_FIFO, so the matcher never fires (vfs_subr.c:1450-1452 vs vfs_default.c:1172-1175, kern_descrip.c:1999). revocation is a security mechanism; for fifos it silently fail-opens. Fix validated: passing DTYPE_FIFO for VFIFO in vrevoke, the same PoC's write after revoke fails EBADF on rebuilt kernel #1.