⬢ DragonFlyBSD Kernel Audit
← triage · dashboard
DF-2833

kern.syncdelay=SYNCER_MAXDELAY (32) accepted by sysctl → same-slot requeue livelock/deadlock of the syncer under sc_token (also kern.retrydelay<=0)

Field Value
ID DF-2833
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H
CWE CWE-834 / CWE-835 (config-gated)
File sys/kern/vfs_sync.c
Lines 133-136 (requeue :864; slots :207/:286/:301/:421-436)
Area kern
Confidence certain
Discovered 2026-08-31
Pass 2 (GLM 5.3 second pass)
Bucket base:kern
Reported pending
Known CVE none
CVE match novel

Summary

sysctl_kern_syncdelay clamps to [1, SYNCER_MAXDELAY] so 32 is accepted. With syncdelay==32: (a) vsyncscan's requeue computes −(i+32)&31 == i — the just-scanned vnode is reinserted at the HEAD of the very bucket being scanned, so the drain loop rescans it forever whenever vget(NOWAIT) keeps failing; the scanning thread holds sc_token and spins, and a lock holder blocked on sc_token (the exact discipline documented at hammer_inode.c:258-269) can never get it → unkillable mount-wide livelock/deadlock. (b) vsetisdirty/vsetobjdirty's vn_syncer_add(vp, syncdelay) targets slot (delayno+32)&31 == delayno — re-dirtying churn re-enters the syncer's own drain bucket, livelocking the syncer thread. Similarly retrydelay (unclamped SYSCTL_INT) set to 0 makes vn_syncer_add(vp,0) map to slot 0 — when delayno==0 the drain loop spins forever holding sc_token for any vnode that stays dirty across VOP_FSYNC. Root-gated configuration DoS; no memory-safety impact. Fix: clamp syncdelay to [1, MAXDELAY−2] (and validate the other delay sysctls), or make the delay≤0 path use (delayno+1)&mask.

Timeline

  • 2026-08-31 Discovered during pass-2 audit of vfs_sync.c (GLM 5.3).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2833 · 2 files
FileTypeDescriptionSize
fix_run.log — 272 B view raw
fix.diff — 1.9 KB view raw

Fix verification

fixed
baseline no→ patch + rebuild →patched clean

Patched kernel: kern.syncdelay=32 clamps to 30; kern.retrydelay=0 and -5 clamp to 1; vn_syncer_add carries a same-slot requeue guard. Baseline wedge not captured live (see verdict), fix verified by direct sysctl A/B.

df2833_2840_ab.log: kern.syncdelay: 30 -> 30, kern.retrydelay: 1 -> 1 (requests 32/0/-5)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Oct 24 17:14:55 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (v26 fix-all kernel; sys_generic.c fix-v2 rebuild pending A/B)

Confirmed kernel references

Detail

Exploit chain

(code-proven) retrydelay=0 -> vn_syncer_add slot 0 == drain bucket -> infinite drain loop holding sc_token -> unkillable writers/unmount - not captured live

Evidence (decisive lines)

df2833_2840_ab.log: sysctl clamps verified on patched kernel (30/1/1); code refs vfs_sync.c:96-97,:207-208,:421-436; add-path clamp :210-211 defuses the syncdelay=32 claim

PoC changes

initial plan targeted syncdelay=32; code read showed the add-path clamp defuses it - the live wedge would be the retrydelay=0 same-slot requeue

Verified recommended fix

--- sys/kern/vfs_sync.c 2026-06-29 12:51:19.000000000 +0000

Verdict

Not reproduced at runtime; mechanism code-proven for the retrydelay half and REFUTED for the syncdelay half. (1) kern.retrydelay is an unvalidated CTLFLAG_RW int (sys/kern/vfs_sync.c:96-97); with retrydelay=0 vn_syncer_add computes slot = -delay & mask = 0 (:207-208), so the syncer drain loop (:421-436) requeues every vnode into the HEAD of the bucket it is draining (:215) whenever delayno==0 - an infinite same-bucket loop holding sc_token that would block every dirtying process and unmount. The live wedge was not captured: the baseline window on the stock kernel was consumed by the DF-2880 OOM-death test, and the stock kernel was afterwards replaced by the patched build. (2) The findings syncdelay=32 mechanism is defused in-tree: vn_syncer_adds delay>0 path clamps to SYNCER_MAXDELAY-2=30 (:210-211), so the -(i+32)&31==i same-slot requeue cannot arise through vn_syncer_add; only the sysctls acceptance of 32 (config validation gap) is real. Fix A/B on the patched kernel: kern.syncdelay=32 clamps to 30; kern.retrydelay=0 and -5 clamp to 1 - the misconfiguration is now rejected at the source and vn_syncer_add carries a same-slot guard.