pppoe_ticker dereferences hook->private/sp->neg unconditionally and races ng_pppoe_disconnect freeing the session β NULL-deref/UAF panic (DF-3488 twin)
Summary
pppoe_ticker runs from the callout subsystem and reads sp=hook->private, neg=sp->neg before any state check, while ng_pppoe_disconnect stops the callout, frees sp->neg and sp and clears hook->private in the tearing thread. callout_stop cannot recall an already-entered callout on another CPU; a ticker firing during hook teardown (remote PADT during the 2-64s retransmit backoff) reads NULL or freed memory -> panic. Same family: PADS completion frees sp->neg while an armed SINIT/SREQ ticker still dereferences sp->neg->m. Structural (Low, race-timing; analysis in pack README). Fix: guard ticker entry + synchronize teardown with the callout before freeing.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3492 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| manifest.json | β | 648 B | view raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-3492 β v1 pppoe_ticker races ng_pppoe_disconnect freeing the session
(Low / base:net β structural analysis, not QEMU-verified; twin of ng7 DF-3488)
File: sys/netgraph/pppoe/ng_pppoe.c
Mechanism
pppoe_ticker() (:1510-1555) executes from the callout subsystem and
dereferences unconditionally, before any state check:
hook_p hook = arg;
sessp sp = hook->private; /* :1514 NULL after disconnect */
negp neg = sp->neg; /* :1515 deref of sp */
ng_pppoe_disconnect() (:1418-1505) tears a session down in a
different thread context:
if (sp->neg) {
callout_stop(&sp->neg->timeout_ch); /* :1490 */
...
kfree(sp->neg, M_NETGRAPH);
}
kfree(sp, M_NETGRAPH); /* :1495 */
hook->private = NULL; /* :1496 */
callout_stop() cannot recall a callout whose handler has already been
entered on another CPU (DFly callout semantics), and there is no
reference/handoff protecting hook->private across the two contexts. A
ticker that fired (or is executing) while the hook is being destroyed β
e.g. a remote PADT arriving during the 2-64 s retransmit backoff window
(ng_pppoe_rcvdata PADT case :1186-1204 β ng_destroy_hook), or
ethernet-hook loss / node teardown β reads hook->private after
:1496 (NULL deref β panic) or uses sp/sp->neg after kfree
(:1495/:1493) (use-after-free read β panic or worse).
Additional member of the same family: a PADS/PADS-complete transition
frees sp->neg (:1181-1183) while a previously-armed ticker may still
execute sp->neg->m in the SINIT/SREQ case (:1532).
Why not QEMU-verified
The window is the tick-vs-teardown race on a 2-64 s timer β the ng7 twin DF-3488 was likewise filed from structural analysis (Low, race-timing). A reliable trigger needs either a callout hammer (custom module) or many thousands of connect/PADT cycles; not worth destabilizing the single-tenant guest for a Low-severity twin whose mechanism is source-identical to the ng7 finding.
Fix (as ng7 DF-3488)
Re-check sp = hook->private; if (sp == NULL) return; under crit at
ticker entry, and/or convert teardown to callout_stop_sync() /
serialize hook destruction with the callout.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
README.md (line-cited structural analysis); compare ng7 DF-3488 evidence pack
Verified recommended fix
Guard ticker entry: sp = hook->private; if (sp == NULL) return; under crit_enter(), and synchronize teardown with the callout (callout_stop_sync or equivalent) before freeing sp/neg
Verdict
Structural analysis only (same discipline as the ng7 twin DF-3488): pppoe_ticker() dereferences hook->private and sp->neg at :1514-1515 before any state check, while ng_pppoe_disconnect() stops the callout (:1490), frees sp->neg and sp (:1493,:1495) and clears hook->private (:1496) from the tearing thread. callout_stop() cannot recall an already-entered callout on another CPU, so a ticker firing concurrently with hook teardown (remote PADT during the 2-64s retransmit window, or ethernet-hook loss) reads NULL (guaranteed panic) or freed memory (UAF). Not QEMU-verified: Low-severity race requiring a callout hammer to hit reliably; mechanism is source-identical to the already-filed ng7 finding.
No comments yet.