fs->backlogged never reset when pipe deletion purges child flowsets: p->sum accounting drift can reach exactly 0 β divide-by-zero panic in WF2Q drain
Summary
Deleting a pipe purges each child flowset's queues via dn_unref_pipe_cb->purge_flow_set without resetting fs->backlogged; the stale counter survives pipe recreation. When such a flowset is later deleted, delete_pipe() subtracts 'p->sum -= fs->weight * fs->backlogged' - weight x (live+stale) - while the weights of that fs's flows still in pipe->idle_heap are never subtracted. Net drift per fs-delete is weight x (stale - idle_count), steerable in both directions; driving p->sum to exactly 0 while any flow sits in scheduler_heap makes 'p->V += (len << MY_M) / p->sum;' a divide-by-zero panic (negative drift only skews WF2Q fairness). Root-gated config surface, deliberate multi-step config sequence + traffic - Low/likely. Not guest-verified (panic-only class, skipped per contract; construction sketch in pack README). Fix: reset fs->backlogged in dn_unref_pipe_cb before purge + count idle-heap weights in the delete_pipe subtraction.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3351 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.9 KB | β raw | |
| verdict.json | file | 1.8 KB | view raw |
DF-3351 β dummynet fs->backlogged accounting drift across pipe delete β p->sum miscalibration β divide-by-zero in WF2Q drain
Analysis (not guest-verified β Low severity, panic-only class)
sys/net/dummynet/ip_dummynet.c maintains the invariant that a pipe's
sum equals the total weight of all flows in its three heaps
(scheduler/not_eligible/idle). Two operations violate it:
-
dn_unref_pipe_cb()(:1627-1638) β pipe delete purges every child flow_set's queues viapurge_flow_set(fs, 0)but never resetsfs->backlogged(purge_flow_set :1189-1239 touches only queues and counters of packets, notbacklogged). The stale counter survives the pipe's deletion and any recreation. -
delete_pipe()flowset path (:1694) trusts it:p->sum -= fs->weight * fs->backlogged;β subtractingweight Γ (live + stale)flows, while the correct amount isweight Γ (live)plus the weights of the fs's flows still sitting inpipe->idle_heap(their weights stay insumuntildn_expire_pipe_cb:650 removes them; the fs-delete path removes the heap entries at :1695-1698 but never subtracts their weights).
Net drift per flowset-delete = weight Γ (stale β idle_count) β attacker
steerable in both directions by choosing when to delete the pipe (creates
staleness) vs. when to delete the fs (applies it). Since
ready_event_wfq() computes
p->V += (len << MY_M) / p->sum; /* :555 */
driving p->sum to exactly 0 while any flow is in scheduler_heap
yields a divide-by-zero panic; negative sum silently corrupts WF2Q
fairness (sign-extended to uint64, division yields ~0).
Construction sketch (all root-gated config, same surface as the known
div0 family): pipe 1 with two child queues fs A and fs B, equal weights;
traffic so both have exactly one backlogged flow; delete pipe 1 (both
fs keep backlogged == 1); recreate pipe 1; push one packet through
fs B (re-attaches it, sum += wB, backlogged 2β1 as it drains to
idle) while fs A has one live flow (sum += wA); delete fs B β
sum -= wB Γ 1 instead of the owed wB Γ 0, hitting 0 exactly when
wA == wB; the next drain tick executes :555 with a live flow in the
scheduler heap β #DE β panic.
Fix
On fs purge/reset, keep the counters coherent with the weights:
--- a/sys/net/dummynet/ip_dummynet.c
+++ b/sys/net/dummynet/ip_dummynet.c
@@ -1633,6 +1633,8 @@ dn_unref_pipe_cb(struct dn_flow_set *fs, void *pipe0)
if (fs->pipe == pipe) {
kprintf("++ ref to pipe %d from fs %d\n",
pipe->pipe_nr, fs->fs_nr);
fs->pipe = NULL;
+ /* keep weight accounting coherent: flows are being purged */
+ fs->backlogged = 0;
purge_flow_set(fs, 0);
}
and, in the fs-delete path, subtract the idle-heap weights that
fs_remove_from_heap(&p->idle_heap, fs) drops, e.g. by counting
p->idle_heap entries of fs before removal and adding
fs->weight * idle_cnt to the subtracted amount.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
['README.md: full accounting-drift derivation with construction sketch and proposed fix']
PoC changes
n/a (no PoC authored; analysis-only)
Verified recommended fix
Reset fs->backlogged when dn_unref_pipe_cb purges the flowset at pipe delete, and subtract the weights of the fs's idle-heap flows in the delete_pipe fs path.
Verdict
Not guest-verified (Low severity, panic-only class, root-gated config surface identical to the already-filed DF-0374/0375 div0 family). Static analysis is line-accurate: dn_unref_pipe_cb() purges child flowset queues at pipe delete without resetting fs->backlogged, and delete_pipe():1694 subtracts fs->weightfs->backlogged from p->sum while the fs's idle-heap flows' weights are never subtracted, so p->sum drifts by weight(stale-idle) per fs-delete and can be steered to exactly 0 with a live flow in scheduler_heap, making ready_event_wfq():555 'p->V += (len << MY_M) / p->sum' a divide-by-zero panic.
No comments yet.