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
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
putpagesflush generates a truncated UNDO record; on transaction rollback/abort (crash recovery or explicit), onlyceil(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.
Recommended 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
sys/kern/vfs_jops.c:960β UNDO units mismatch (pages-as-bytes).sys/kern/vfs_jops.c:968β REDO correct (page-count API).sys/vm/vnode_pager.c:690β confirmsa_countis bytes.- CWE-704 Incorrect Type Conversion or Cast.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/vfs_jops.c. - pending Reported to DragonFlyBSD security contact.