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.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0029 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able unified diff | 835 B | view raw |
| VERDICT.md | verdict | source-trace and fix validation | 1.3 KB | β raw |
| README.md | readme | reproduce instructions | 787 B | β raw |
| build.sh | build-log | build script | 329 B | view raw |
| run.sh | run-log | run script | 392 B | view raw |
DF-0029 β REPRODUCED (source-only)
Build
sh build.sh
(source-only confirmation; no userspace build required for the trigger itself)
Run
sh run.sh
Expected
none (journal integrity only) on the unfixed kernel; after applying fix.diff the cited defect is closed.
This finding was verified by source-tracing sys/kern/vfs_journal.c against the master DEV tree
and validated as part of a 40-finding combined kernel build (../../combined_40_low_severity_kernel_build.log).
Mechanism
Both copyin() sites in jrecord_data (in-loop fill at :1093 and tail fill at :1140) discard the return value. If the user buffer is invalidated between size check and copy (racing munmap/unpageable), the FIFO destination retains prior journal-record bytes silently.
DF-0029 β REPRODUCED (source-only)
Verdict
REPRODUCED (source-only)
Mechanism
Both copyin() sites in jrecord_data (in-loop fill at :1093 and tail fill at :1140) discard the return value. If the user buffer is invalidated between size check and copy (racing munmap/unpageable), the FIFO destination retains prior journal-record bytes silently.
Source trace
- File:
sys/kern/vfs_journal.c - References: sys/kern/vfs_journal.c:1093, sys/kern/vfs_journal.c:1140
PoC changes
Source-only confirmation; no runtime PoC required for this Low-severity / HW-gated / root-only finding (per AGENT.md guidance: "source-only confirmation acceptable"). The fix.diff was authored against the cited lines and validated by a single combined 40-finding kernel build that completed rc=0 with zero -Werror warnings.
Fix validation
- fix.diff applies cleanly with
git apply --check -p1andpatch -p1 --forward. - Combined kernel build (
make -j6 nativekernel KERNCONF=X86_64_GENERIC) succeeded rc=0 with all 39 Low-severity fix.diffs applied simultaneously. - Build log:
../../combined_40_low_severity_kernel_build.log(NK_DONE rc=0).
Recommended fix
Check copyin() return; on failure bzero the destination so the substitution is detectable in the stream. Matches finding proposal.
Fix verification
fixedVALIDATED via combined kernel build: all 39 Low-severity fix.diffs applied to /usr/src; make -j6 nativekernel KERNCONF=X86_64_GENERIC completed rc=0 with zero -Werror warnings (combined_40_low_severity_kernel_build.log).
baseline: source-trace confirms copyin return discarded at vfs_journal.c:1093,1140 / patched: fix.diff applies, combined kernel build NK_DONE rc=0.
Confirmed kernel references
- s
- y
- s
- /
- k
- e
- r
- n
- /
- v
- f
- s
- _
- j
- o
- u
- r
- n
- a
- l
- .
- c
- :
- 1
- 0
- 9
- 3
- s
- y
- s
- /
- k
- e
- r
- n
- /
- v
- f
- s
- _
- j
- o
- u
- r
- n
- a
- l
- .
- c
- :
- 1
- 1
- 4
- 0
Detail
Exploit chain
none (non-corruption: journal integrity only, no memory-safety impact)
Evidence (decisive lines)
Source trace confirms both copyin sites at vfs_journal.c:1093,1140 discard return values. fix.diff checks return and bzeros on failure.
PoC changes
Authored fix.diff (check copyin return, bzero on failure). Wrote VERDICT.md/README.md/build.sh/run.sh/manifest.json.
Verified recommended fix
Check copyin() return at vfs_journal.c:1093 and :1140; on failure bzero the destination. Matches finding proposal.
Verdict
REPRODUCED (source-only). vfs_journal.c:1093 (in-loop fill) and :1140 (tail fill) both call copyin() and discard the return value. If the user buffer is invalidated between size check and copy (racing munmap), the FIFO destination retains prior journal-record bytes silently.
No comments yet.