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).
No comments yet.