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

journal_setextattr journals the post-VOP, fully consumed uio: the extended-attribute VALUE is never recorded in the REDO stream

Field Value
ID DF-2764
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
CWE CWE-1051 Incomplete Data Processing
File sys/kern/vfs_jops.c
Lines 1027 (op), 1039 (post-op uio journaling)
Area kern
Confidence certain
Discovered 2026-08-30
Pass 2 (GLM 5.3 second pass)
Bucket base:kern
Reported pending
Known CVE none
CVE match novel

Summary

journal_setextattr runs the op first and then journals JLEAF_FILEDATA from the SAME uio. VFS uio consumers advance iovecs in place, so after a successful op every iov_len==0 and jrecord_leaf_uio skips them all: the record carries only ATTRNAME and a post-op SEEKPOS β€” the attribute value never reaches the journal. Replicas replay empty extattrs; reversible undo cannot restore old values. journal_write demonstrates the required pattern (uio copied before the op). Latent today (no in-tree FS implements vop_setextattr) β€” activates the moment one does.

Snapshot the uio before vop_journal_operate_ap (mirroring journal_write :859-868) β€” fix.diff in the pack (authored; not kernel-validated because no FS reaches the branch).

Timeline

  • 2026-08-30 Discovered during pass-2 audit of vfs_jops.c (GLM 5.3).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2764 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 1.7 KB ↓ raw
fs_check.log β€” 376 B view raw
fix.diff β€” 1.3 KB view raw
verdict.json β€” 2.0 KB view raw

DF-2764 β€” journal_setextattr journals the post-VOP (fully consumed) uio: the extattr VALUE is never recorded

What

journal_setextattr() (sys/kern/vfs_jops.c:1014-1045) runs the underlying op first (:1027 vop_journal_operate_ap) and then journals the REDO payload with the SAME uio:

error = vop_journal_operate_ap(&ap->a_head);   /* consumes a_uio */
if (error == 0) {
    ...
    save = jrecord_push(jrec, JTYPE_REDO);
    jrecord_write_uio(jrec, JLEAF_FILEDATA, ap->a_uio);  /* already consumed! */
    jrecord_pop(jrec, save);
}

VFS uio consumers advance the iovecs in place (uiomove() / kern_subr.c:151 iov->iov_len -= cnt, iov_base += cnt) so after a successful VOP_SETEXTATTR every iov_len is 0. jrecord_write_uio() -> jrecord_leaf_uio() (sys/kern/vfs_journal.c:962-973) skips zero-length iovecs, so the JLEAF_FILEDATA leaf carrying the attribute value is never emitted; only JLEAF_ATTRNAME and a JLEAF_SEEKPOS with the end offset are recorded. A replica replaying the stream sets an empty/missing extended attribute; reversable-journal undo cannot restore the old value. Contrast journal_write() (:859-868) which explicitly copies the uio BEFORE the op ("UIO's don't retain sufficient information to be reused once they've gone through the VOP chain").

Reachability: any user able to set an extattr on a file in a root-journaled mount (the journal must be installed first; the trigger itself is unprivileged). NOTE: no in-tree DragonFly filesystem currently implements vop_setextattr (only smbfs, commented out, sys/vfs/smbfs/smbfs_vnops.c:121) and the guest has no extattr(1) userland β€” the defect is latent until an FS implements the VOP.

PoC status: not testable on this guest

  • setextattr/extattr userland absent (fs_check.log).
  • No in-tree FS returns success from VOP_SETEXTATTR, so the error == 0 branch that emits the broken record cannot be reached on the stock guest; a failing setextattr aborts the jrecord (jreclist_done(error)) and journals nothing.

Fix

Copy the uio before the op, exactly like journal_write() does (see fix.diff; not kernel-validated β€” feature absent on the guest).

VERDICT.md
↓ download raw

DF-2764 β€” VERDICT

Status: not reproduced / not testable on this guest β€” static proof of mechanism (certain), feature absent at runtime.

Why not testable

The broken record is only emitted when the underlying VOP_SETEXTATTR returns 0 (if (error == 0) in journal_setextattr, vfs_jops.c:1028). - No in-tree DragonFly filesystem implements vop_setextattr (only smbfs, commented out: sys/vfs/smbfs/smbfs_vnops.c:121); every FS returns EOPNOTSUPP through the default vector, so the shim always aborts its jrecord (jreclist_done(error)) and journals nothing. - The guest also lacks extattr userland (setextattr not found; fs_check.log).

Static proof (certain)

  1. journal_setextattr journals ap->a_uio AFTER the op (vfs_jops.c:1039 jrecord_write_uio(jrec, JLEAF_FILEDATA, ap->a_uio)).
  2. VFS uio consumers mutate the iovec array in place (sys/kern/kern_subr.c:151 iov->iov_len -= cnt, :150-ish iov_base += cnt; uiomove). After a successful op all iov_len == 0.
  3. jrecord_leaf_uio skips zero-length iovecs (sys/kern/vfs_journal.c:964-965 if (iov->iov_len == 0) continue;). => The JLEAF_FILEDATA leaf carrying the attribute value is never emitted; only JLEAF_ATTRNAME and a post-op SEEKPOS are recorded. journal_write() demonstrates the required pattern (uio copied BEFORE the op, vfs_jops.c:859-868, with the exact comment "UIO's don't retain sufficient information to be reused once they've gone through the VOP chain").

The defect is latent: it activates the moment any filesystem implements vop_setextattr (e.g. smbfs uncommenting it).

Fix

fix.diff (uio snapshot before the op, mirroring journal_write); authored but not kernel-validated β€” the code path cannot return success on the stock guest.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix authored from the line-accurate mechanism; cannot be runtime-validated on the stock guest (no FS support)

['fix.diff']
↓ fix.diffper-fix-DF-2764

Confirmed kernel references

Detail

Evidence (decisive lines)

['fs_check.log (setextattr absent; no FS support)', 'VERDICT.md static proof chain with path:line']

PoC changes

n/a - feature absent

Verified recommended fix

copy the uio before vop_journal_operate_ap in journal_setextattr, exactly like journal_write (fix.diff)

Verdict

journal_setextattr journals ap->a_uio after the underlying VOP consumed it (vfs_jops.c:1039); uiomove advances iov/iov_len in place (kern_subr.c:151) and jrecord_leaf_uio skips zero-length iovecs (vfs_journal.c:964), so the extattr VALUE leaf is never recorded - replication/undo of extattr sets is silently broken. Not testable on the stock guest: no in-tree FS implements vop_setextattr (smbfs's is commented out) and extattr userland is absent, so the error==0 branch that emits the broken record cannot be reached; mechanism proven statically with certainty.