Uninitialized stack args[1] transmitted in mesh peer-timeout CLOSE action frames
Summary
mesh_peer_timeout_cb declares uint16_t args[3] and sets only args[0]/args[2] in the MAX_RETRIES and CONFIRM_TIMEOUT branches before sending a CLOSE action frame. args[1] (peer link id) is read by ieee80211_add_meshpeer: non-zero garbage selects the long-form IE and puts the two uninit kernel-stack bytes on air in the Peer Link ID field; it also decides the IE length. Any mesh peer triggers the timeouts (beacon then silence / confirm then silence) and receives the CLOSE frames. VERDICT: on the stock guest both timeout paths emitted short-form CLOSEs - the uninit slot read 0, so no bytes leaked at runtime on this build (not_reproduced/impact none; the read itself is source-certain). Fix validated (args[1]=0 at entry).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3289 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| run.sh | β | 111 B | view raw | |
| verdict.json | β | 2.3 KB | view raw | |
| manifest.json | β | 491 B | view raw |
DF-3289 β Uninitialized stack args[1] sent in mesh peer-timeout CLOSE action frames
File: sys/netproto/802_11/wlan/ieee80211_mesh.c (pass 2 audit)
What
mesh_peer_timeout_cb (:3091-3143) declares uint16_t args[3] and in
two branches sets only elements 0 and 2 before transmitting a CLOSE
action frame to the peer:
case IEEE80211_NODE_MESH_OPENSNT:
case IEEE80211_NODE_MESH_OPENRCV:
if (ni->ni_mlrcnt == ieee80211_mesh_maxretries) {
args[0] = ni->ni_mlpid;
args[2] = IEEE80211_REASON_MESH_MAX_RETRIES; /* args[1] NEVER set */
ieee80211_send_action(ni, ..., MESHPEERING_CLOSE, args);
...
case IEEE80211_NODE_MESH_CONFIRMRCV:
args[0] = ni->ni_mlpid;
args[2] = IEEE80211_REASON_MESH_CONFIRM_TIMEOUT; /* args[1] NEVER set */
ieee80211_send_action(ni, ..., MESHPEERING_CLOSE, args);
args[1] flows into ieee80211_add_meshpeer(..., CLOSE, args[0],
args[1], args[2]) (:2902-2948): if the garbage is non-zero the frame is
built in long form (MPM_MAX_SZ) and the two uninitialized kernel-stack
bytes are transmitted on air in the Peer Link ID field. The value also
selects the IE length (6 vs 8) β an uninitialized-value use in protocol
logic either way. CWE-457.
Any mesh peer can drive a victim into these branches: beacon β victim auto-peers (OPENSNT) β attacker stays silent β 3Γ40 ms β CLOSE #1; CONFIRM-then-silence β CONFIRMRCV timeout β CLOSE #2.
Verification result (stock kernel)
Harness steps 1/3 reproduced the timeout CLOSE frames (captured at the
radio): both the MAX_RETRIES and CONFIRM_TIMEOUT closes were emitted in
short form (75 06 0000 <llid> <rcode>), i.e. on this build the
uninitialized slot read as 0 and no bytes leaked on air. The
uninitialized read itself is certain from source; the on-air disclosure
depends on the stack garbage being non-zero, which was not observed on
this kernel/callout path. Filed Low: latent info-leak/hygiene defect.
Captured CLOSE (CONFIRMRCV-timeout path):
tx[31] fc0=d0 len= 39: d0000000 02:00:00:00:00:aa > 02:00:00:00:00:f2 seq=0x2300 0f 03 72 03 70776e 75 06 0000 eed1 3800 # close, meshid pwn, meshpeer len=6, # llid=0xd1ee rcode=0x38(MAX_RETRIES) -- no peerid (uninit slot read 0)
Build / run
sh findings/poc/DF-3286/build.sh sh findings/poc/DF-3289/run.sh # beacon, confirm, silence, dump CLOSEs
Fix
args[1] = 0; at the top of mesh_peer_timeout_cb (fix.diff hunk 5).
Fix verification
fixedargs[1]=0 initialized at cb entry (hunk 5); compiled into validated kernel; runtime observable unchanged on this build (slot read 0 already)
['fix.log: DF-3289 section']
Confirmed kernel references
Detail
Exploit chain
attacker beacons victim into auto-peering then goes silent (or confirms then goes silent) -> victim timeout_cb builds CLOSE with uninit args[1] -> if garbage != 0, long-form meshpeer IE transmits 2 uninit kernel-stack bytes in the Peer Link ID field to the attacker
Evidence (decisive lines)
run.sh output: captured CLOSE 75 06 0000 <llid> 3800 (short form, slot read 0) on both timeout paths
PoC changes
passive observation via harness TX capture; no special trigger needed beyond beacon+silence
Verified recommended fix
args[1] = 0 at function entry (fix.diff hunk 5)
Verdict
mesh_peer_timeout_cb transmits CLOSE frames with args[1] (peer link id) never initialized in the MAX_RETRIES (:3108) and CONFIRM_TIMEOUT (:3126) branches - source-certain CWE-457. On the stock guest the captured timeout CLOSEs (both retry and confirm-timeout paths) were emitted in short form because the uninitialized stack slot read 0, so no kernel-stack bytes were observed on air and no behavior change was observable. Latent 2-byte kernel-stack info leak to any mesh peer whose stack garbage is non-zero.
No comments yet.