del_vif() leaves tbf_reprocess_q_ch armed with the dying vif β v_tbf NULL dereference panic in tbf_update_tokens()
Summary
tbf_control() arms the global tbf_reprocess_q_ch callout with vifp when a packet queues on a rate-limited vif. del_vif() frees the queued mbufs and bzeros the vif - v_tbf becomes NULL - but never stops the callout (X_ip_mrouter_done does). When the 10 ms callout fires, tbf_reprocess_q() only checks ip_mrouter and tbf_update_tokens() dereferences t->tbf_last_pkt_t at NULL+0x8. REPRODUCED on guest iteration 0: 'Fatal trap 12, fault VA = 0x8, Stopped at tbf_update_tokens+0x2a'. Root-only trigger (MRT_DEL_VIF), deterministic window; a routing-daemon vif reconfiguration is enough. Fix validated (callout_stop in del_vif + v_tbf NULL guard): 200 iterations clean.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3305 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| tbfcallout.c | β | 3.9 KB | view raw | |
| ip_mroute_common.h | β | 2.2 KB | view raw | |
| build.sh | β | 94 B | view raw | |
| run.sh | β | 93 B | view raw | |
| build.log | β | 120 B | view raw | |
| run.log | β | 731 B | view raw | |
| panic.txt | β | 666 B | view raw | |
| fix_run.log | β | 10.3 KB | view raw | |
| env.txt | β | 181 B | view raw | |
| VERDICT.md | β | 2.7 KB | β raw | |
| fix.diff | β | 2.7 KB | view raw | |
| verdict.json | β | 2.8 KB | view raw |
DF-3305 VERDICT β REPRODUCED (deterministic NULL deref β kernel panic)
Bottom line
del_vif() leaves the tbf_reprocess_q_ch callout armed with vifp as
its argument; it then bzeros the vif (and its tbf) so vifp->v_tbf
becomes NULL, and when the 10 ms callout fires it dereferences
t->tbf_last_pkt_t β Fatal trap 12 at virtual address 0x8. Reproduced
on the guest on the first qualifying iteration (after fixing the PoC
to actually reach the tbf path). Trigger is privileged (MRT_DEL_VIF), so
severity is Low (root can crash the box β but a routing daemon restart
sequence del_vif at the wrong moment is enough; no malicious intent
needed).
Root cause (citations sys/net/ip_mroute/ip_mroute.c)
:1791-1793tbf_control()arms the global callout withvifpwhen a packet is queued on a rate-limited vif:callout_reset(&tbf_reprocess_q_ch, TBF_REPROCESS, tbf_reprocess_q, vifp);:873-923del_vif()frees the queued mbufs andbzerosvifp(v_tbf becomes NULL) under mroute_token β but never stops the callout. (X_ip_mrouter_donedoes stop it, at:644β del_vif forgot.):1873-1885tbf_reprocess_q(vifp)only checksip_mrouter == NULL, then:1961-1972tbf_update_tokens()doesstruct tbf *t = vifp->v_tbf;andTV_DELTA(tp, t->tbf_last_pkt_t, tm)β NULL+0x8 read.
Reproduction (guest, PIMTEST kernel #0)
MRT_INIT: 0 iter 0: sent, DEL_VIF rc=0; waiting for callout... Fatal trap 12: page fault while in kernel mode fault virtual address = 0x8 <- &tbf_last_pkt_t.tv_usec - NULL Stopped at tbf_update_tokens.isra.11+0x2a: subq 0x8(%rbx),%rax <- rbx = t = NULL
(runs: first attempt with 200 iterations did not panic because a raw
socket without IP_MULTICAST_TTL gets its TTL forced to 1 by ip_output
and X_ip_mforward drops TTL<=1 before reaching the vif; with the TTL
option attached, the panic is immediate β see poc_changes.)
Fix validation (kernel #1)
Patched del_vif stops the callout and tbf_reprocess_q guards
v_tbf == NULL; ./tbfcallout completes 200 iterations: no panic
after 200 iterations (fix_run.log).
Recommended fix
+ callout_stop(&tbf_reprocess_q_ch);
bzero((caddr_t)vifp->v_tbf, sizeof(*(vifp->v_tbf)));
plus if (ip_mrouter == NULL || vifp == NULL || vifp->v_tbf == NULL)
return; in tbf_reprocess_q (see fix.diff).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedPatched kernel: tbfcallout runs 200 add/send/del cycles, 'no panic after 200 iterations' (fix_run.log). Baseline panicked on iteration 0.
fix_run.log; fix_build.log
Confirmed kernel references
Detail
Exploit chain
root: MRT_INIT -> MRT_ADD_VIF(phyint, rate_limit>0) -> MRT_ADD_MFC -> inject one matching multicast (queues in tbf, arms 10ms callout with vifp) -> MRT_DEL_VIF (bzero, v_tbf=NULL, callout not stopped) -> callout fires -> tbf_update_tokens derefs NULL -> panic.
Evidence (decisive lines)
['run.log: iter 0 sent, DEL_VIF rc=0 then Fatal trap 12', 'panic.txt: fault virtual address 0x8, Stopped at tbf_update_tokens.isra.11+0x2a subq 0x8(%rbx),%rax', 'fix_run.log: no panic after 200 iterations']
PoC changes
First version never reached the vif: a raw socket without imo gets ip_ttl forced to 1 by ip_output and X_ip_mforward drops TTL<=1. Added IP_MULTICAST_TTL=5 (+loop off) on the raw socket -> panic on iteration 0.
Verified recommended fix
del_vif(): callout_stop(&tbf_reprocess_q_ch) before bzero; tbf_reprocess_q(): return if vifp==NULL || vifp->v_tbf==NULL.
Verdict
del_vif() does not stop tbf_reprocess_q_ch which tbf_control() armed with vifp; del_vif bzeros the vif (v_tbf=NULL) and the 10ms callout fires into tbf_update_tokens() which reads t->tbf_last_pkt_t at NULL+0x8: Fatal trap 12, Stopped at tbf_update_tokens.isra.11+0x2a subq 0x8(%rbx),%rax, on iteration 0. Root-only trigger (MRT_DEL_VIF) so Low severity, but deterministic and a plain daemon-restart sequence can hit it. Fixed kernel stops the callout in del_vif and guards v_tbf==NULL: 200 iterations, no panic.
No comments yet.