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)
PoC verification
Evidence pack
findings/poc/DF-3162 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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)
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)
- sys/kern/vfs_syscalls.c:4746-4747 β non-device vnodes go to
vrevoke(vp, cred). - sys/kern/vfs_subr.c:1450-1452 β
vrevokecallsfdrevoke(vp, DTYPE_VNODE, cred)for every non-VCHR vnode. - sys/kern/vfs_default.c:1172-1175 β
vop_stdopentags fifo fpsfp->f_type = DTYPE_FIFO. - sys/kern/kern_descrip.c:1999 β the match requires
info->type == fp->f_type;DTYPE_VNODE(1) != DTYPE_FIFO(4)(sys/sys/file.h:125,128). Nothing matches;info.foundstays 0; fdrevoke returns 0 having done nothing.
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
fixedOn rebuilt kernel #1 with fix.diff, revoke(2) on the owned fifo invalidates the descriptors (write fails EBADF); bug marker absent.
fix_run.log
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.
No comments yet.