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)
PoC verification
Evidence pack
findings/poc/DF-2833 · 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix_run.log | — | 272 B | view raw | |
| fix.diff | — | 1.9 KB | view raw |
Fix verification
fixedPatched 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)
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.
No comments yet.