β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0030

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_path is reached from the VOP journal hooks via jrecord_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.

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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0030 Β· 5 files
FileTypeDescriptionSize
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
README.md readme reproduce instructions
↓ download 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.

VERDICT.md verdict source-trace and fix validation
↓ download raw

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

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 -p1 and patch -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).

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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED via combined kernel build rc=0.

baseline: unbounded goto again / patched: bounded retries, build rc=0.
↓ fix.diffDragonFly 6.5-DEVELOPMENT combined-build rc=0

Confirmed kernel references

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.