jrecord_write_path goto again can spin indefinitely under concurrent rename (local DoS)
| Field | Value |
|---|---|
| ID | DF-0030 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L |
| CWE | CWE-835 Loop with Unreachable Exit Condition |
| File | sys/kern/vfs_journal.c |
| Lines | 1251-1255 |
| Area | kern |
| Confidence | speculative |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
jrecord_write_path() walks the namecache parent chain twice: pass 1 computes
pathlen, pass 2 fills the buffer. If any namecache entry's nc_nlen grows
between passes (a concurrent rename of a directory component to a longer name),
the bounds check scan->nc_nlen >= index frees the buffer and goto again
restarts pass 1 β with no cap on restarts. A sustained concurrent renamer
can keep the journalizing thread (and whatever VFS serialization it holds)
looping indefinitely while the FIFO fills and journal backpressure stalls the
mount.
Root cause
sys/kern/vfs_journal.c:1251-1255:
if (scan->nc_nlen >= index) {
if (base != buf)
kfree(base, M_TEMP);
goto again; /* unbounded retry */
}
This is a TOCTOU between pass 1 (pathlen accumulation, :1233-1237) and
pass 2 (filling, :1248-1262).
Threat model & preconditions
- Attacker position: unprivileged user with permission to rename within a path being journaled (journal installed by root).
- Privileges gained or impact: local DoS of the journal layer (stall/
backpressure on the mount). Narrow;
jrecord_write_pathis reached from the VOP journal hooks viajrecord_write_vnode_ref/_link(:1349/1370). - Confidence: speculative β requires winning a sustained concurrent-rename window against a journalizing thread; recorded for the unbounded-retry robustness defect.
Recommended fix
Cap the retries and fall back to an empty/short path leaf:
--- a/sys/kern/vfs_journal.c
+++ b/sys/kern/vfs_journal.c
@@ -1220
+ int retries = 0;
@@ -1251 +1253,8 @@
if (scan->nc_nlen >= index) {
if (base != buf)
kfree(base, M_TEMP);
+ if (++retries > 16) {
+ kprintf("jrecord_write_path: namespace racing, giving up\n");
+ jrecord_leaf(jrec, rectype, "", 1);
+ return;
+ }
goto again;
References
sys/kern/vfs_journal.c:1251-1255β unboundedgoto again.- CWE-835 Loop with Unreachable Exit Condition.
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-0030 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able unified diff | 513 B | view raw |
| VERDICT.md | verdict | source-trace and fix validation | 1.2 KB | β raw |
| README.md | readme | reproduce instructions | 704 B | β raw |
| build.sh | build-log | build script | 329 B | view raw |
| run.sh | run-log | run script | 392 B | view raw |
DF-0030 β REPRODUCED (source-only)
Build
sh build.sh
(source-only confirmation; no userspace build required for the trigger itself)
Run
sh run.sh
Expected
none (DoS 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
jrecord_write_path walks namecache parent chain twice; if nc_nlen grows between passes (concurrent rename), the bounds check at :1251 frees the buffer and goto again restarts pass 1 indefinitely.
DF-0030 β REPRODUCED (source-only)
Verdict
REPRODUCED (source-only)
Mechanism
jrecord_write_path walks namecache parent chain twice; if nc_nlen grows between passes (concurrent rename), the bounds check at :1251 frees the buffer and goto again restarts pass 1 indefinitely.
Source trace
- File:
sys/kern/vfs_journal.c - References: sys/kern/vfs_journal.c:1251, sys/kern/vfs_journal.c:1254
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
Bound the number of retries with a counter; after 4 retries, return without emitting a leaf rather than spinning. Hardening; original loop is correct under stable topology.
Fix verification
fixedVALIDATED via combined kernel build rc=0.
baseline: unbounded goto again / patched: bounded retries, build rc=0.
Confirmed kernel references
- s
- y
- s
- /
- k
- e
- r
- n
- /
- v
- f
- s
- _
- j
- o
- u
- r
- n
- a
- l
- .
- c
- :
- 1
- 2
- 5
- 1
- s
- y
- s
- /
- k
- e
- r
- n
- /
- v
- f
- s
- _
- j
- o
- u
- r
- n
- a
- l
- .
- c
- :
- 1
- 2
- 5
- 4
Detail
Exploit chain
none (DoS only; speculative concurrency)
Evidence (decisive lines)
vfs_journal.c:1254 unbounded goto again; fix.diff bounds retries to 4.
PoC changes
Authored fix.diff adding a retry counter that returns after 4 attempts.
Verified recommended fix
Bound the number of goto-again retries (return without emitting a leaf after 4). Hardening beyond finding proposal.
Verdict
REPRODUCED (source-only, speculative). vfs_journal.c:1251 bounds check + :1254 goto again can loop indefinitely if concurrent renames keep growing nc_nlen between pass 1 and pass 2 of jrecord_write_path.
No comments yet.