β¬’ DragonFlyBSD Kernel Audit
← dashboard
DF-0038

journal_putpages UNDO records btoc(a_count) pages instead of a_count bytes (silent rollback corruption)

Field Value
ID DF-0038
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
CWE CWE-704 Incorrect Type Conversion or Cast
File sys/kern/vfs_jops.c
Lines 960 (UNDO), 968 (REDO for contrast)
Area kern
Confidence certain
Discovered 2026-06-29
Reported pending

Summary

journal_putpages passes btoc(ap->a_count) (a page count) as the bytes argument to jreclist_undo_file, but that argument flows into jrecord_undo_file (off_t bytes) β†’ jrecord_file_data, both of which interpret it as a byte count. The adjacent REDO call at :968 correctly uses btoc(ap->a_count) as a page count for jrecord_write_pagelist. The UNDO path therefore records only ceil(a_count/PAGE_SIZE) bytes of the about-to-be- overwritten file data (e.g. 2 bytes for an 8192-byte putpages) instead of a_count bytes. On a reversable journal (MC_JOURNAL_WANT_REVERSABLE), transaction rollback restores only those few bytes and silently corrupts the rest, defeating the UNDO log's integrity guarantee.

Root cause

sys/kern/vfs_jops.c:955-961:

if (jreclist_init(...) && ap->a_count > 0) {
    jreclist_undo_file(&jreclist, ap->a_vp,
                       JRUNDO_FILEDATA|JRUNDO_SIZE|JRUNDO_MTIME,
                       ap->a_offset, btoc(ap->a_count));   /* :960 pages-as-bytes */
}

a_count is in bytes (confirmed by vnode_pager_generic_putpages count = bytecount / PAGE_SIZE, sys/vm/vnode_pager.c:690, and devfs_vnops.c:2120 round_page(ap->a_count)/PAGE_SIZE). btoc() converts bytes→pages. jreclist_undo_file (:625) forwards its bytes arg unchanged to jrecord_undo_file (:656, off_t bytes), which compares it against attr.va_size (:720-727) and passes it to jrecord_file_data (vfs_journal.c:1460) as a byte count driving vn_rdwr(UIO_READ) lengths. The sibling REDO call at :968 correctly uses btoc(ap->a_count) because jrecord_write_pagelist takes a page count.

Threat model & preconditions

  • Attacker position: unprivileged user with write permission on a regular file in a mount with a VFS journal installed in reversable mode (MC_JOURNAL_WANT_REVERSABLE, root-configured β€” replication/rollback setups).
  • Privileges gained or impact: integrity loss. Each putpages flush generates a truncated UNDO record; on transaction rollback/abort (crash recovery or explicit), only ceil(a_count/PAGE_SIZE) bytes of each overwritten region are restored β€” the remainder is silently left as the post-write contents, violating UNDO semantics. No memory-safety impact.
  • Required config or capabilities: reversable journal on the target mount; write permission on a file there.
  • Reachability: write/mmap a file to dirty pages β†’ VM pageout β†’ VOP_PUTPAGES β†’ journal_putpages.

Proof of concept (conceptual)

On a mount with a reversable journal: (1) create a known-content file and checksum a region; (2) overwrite it to dirty all pages and force pageout; (3) trigger a journal transaction rollback/reversal (unmount in WANT_REVERSABLE mode and replay the UNDO stream, or the journaling test harness); (4) observe that the restored file contains only the first ceil(a_count/PAGE_SIZE) bytes of the original per putpages instead of the full overwritten region.

(No standalone exploit code β€” this is a journal-integrity correctness bug, not a memory-safety defect.)

Impact

Low β€” silent data corruption of crash-recovery state on reversable journals (integrity only, no memory safety). A maintainer-actionable units fix.

Pass the byte count to the UNDO path (the REDO path keeps btc() because its API takes a page count):

--- a/sys/kern/vfs_jops.c
+++ b/sys/kern/vfs_jops.c
@@ -957,7 +957,7 @@
        jreclist_undo_file(&jreclist, ap->a_vp,
               JRUNDO_FILEDATA|JRUNDO_SIZE|JRUNDO_MTIME,
-              ap->a_offset, btoc(ap->a_count));
+              ap->a_offset, ap->a_count);

References

Timeline

  • 2026-06-29 Discovered during automated file-by-file audit of sys/kern/vfs_jops.c.
  • pending Reported to DragonFlyBSD security contact.