Unchecked copyin() in jrecord_data leaves stale kernel data in the journal stream
| Field | Value |
|---|---|
| ID | DF-0029 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N |
| CWE | CWE-252 Unchecked Return Value |
| File | sys/kern/vfs_journal.c |
| Lines | 1093, 1140 |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
Both copyin() call sites in jrecord_data() discard the return value. If the
user buffer is invalidated between the size check and the copy (e.g. another
thread munmap()s the range, or the page cannot be paged in), the FIFO
destination retains whatever bytes it previously held (typically prior journal
records) and the journal stream has no in-band marker to detect the
substitution โ silent corruption of the authoritative recovery stream.
Root cause
sys/kern/vfs_journal.c:1093 (in-loop fill, while iterating
stream_residual chunks) and :1140 (tail fill) both call copyin(buf,
jrec->stream_ptr, ...) without inspecting the return value. On a non-zero
return the destination is left unwritten (stale).
Threat model & preconditions
- Attacker position: unprivileged user with write permission on a file on a journaled mount (journal installed by root).
- Privileges gained or impact: no kernel memory-safety impact. Silent
corruption of the journal stream: a recovery consumer replays substituted
(stale) bytes as if they were the user's data, undermining trust in recovered
filesystem state. Independent of DF-0028 โ any normal-sized
writev()can trigger it via a racingmunmap. - Reachability: thread A holds/munmaps the user buffer while thread B
writev()s on the journaled FS; intermittently the journalFILEDATAleaf contains previous-transaction bytes.
Proof of concept (sketch)
thread A: mmap a page, writev() it on the journaled FS, and munmap() it in a
tight loop from another thread racing the journal's copyin.
thread B: repeatedly writev(fd, &iov{mmap'd page}, 1).
Observe (offline, from the journal consumer) that some FILEDATA leaves carry
bytes that were not in the user buffer (previous-transaction residue).
Impact
Low โ journal integrity only, no kernel memory-safety impact. A maintainer- actionable robustness/correctness fix.
Recommended fix
Check the copyin() return; on failure zero-fill the destination and log (the
zero-fill makes the substitution detectable in the stream):
--- a/sys/kern/vfs_journal.c
+++ b/sys/kern/vfs_journal.c
@@ -1093 +1093,4 @@
- copyin(buf, jrec->stream_ptr, jrec->stream_residual);
+ if (copyin(buf, jrec->stream_ptr, jrec->stream_residual) != 0)
+ bzero(jrec->stream_ptr, jrec->stream_residual);
@@ -1140 +1143,4 @@
- copyin(buf, jrec->stream_ptr, bytes);
+ if (copyin(buf, jrec->stream_ptr, bytes) != 0)
+ bzero(jrec->stream_ptr, bytes);
References
sys/kern/vfs_journal.c:1093,1140โ uncheckedcopyin.- CWE-252 Unchecked Return Value.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/vfs_journal.c. - pending Reported to DragonFlyBSD security contact.