One byte of uninitialized kernel heap broadcast over the air in every forwarded HWMP PERR (pperr->perr_ttl never initialized)
Summary
hwmp_recv_perr allocates the propagation PERR without M_ZERO and never assigns pperr->perr_ttl; the only touch is pperr->perr_ttl--. hwmp_add_meshperr then writes that prior-heap byte as the TTL of a broadcast PERR, disclosing one byte of kernel heap memory to every mesh neighbor per forwarded PERR (rate up to ~10 Hz). VERIFIED on guest: 18 forwarded PERRs across 3 boots (incl. the DF-3327-patched kernel) with 16 distinct injected RX TTLs - emitted TTL byte always 0xfe (heap 0xff minus the decrement), never rx_ttl-1 and matching no byte of any injected frame. Trigger: PREP with origaddr=victim/ttl=1 creates a VALID route via the attacker; PERR with dest_flags=62 (matching the DF-0353 switch-on-dest_flags), ttl>1 gets forwarded with the uninit TTL. Fix: inherit TTL from the received PERR.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3328 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| fwm.c | β | 26.9 KB | view raw | |
| Makefile | β | 150 B | β download | |
| build.sh | β | 87 B | view raw | |
| run.sh | β | 111 B | view raw | |
| build.log | β | 5.6 KB | view raw | |
| run.log | β | 1009 B | view raw | |
| run.2.log | β | 2.1 KB | view raw | |
| run.3.log | β | 802 B | view raw | |
| leak_sample.txt | β | 1.4 KB | view raw | |
| env.txt | β | 157 B | view raw | |
| fix.diff | β | 524 B | view raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-3328 β 1 byte of uninitialized kernel heap broadcast in forwarded HWMP PERRs
File: sys/netproto/802_11/wlan/ieee80211_hwmp.c
Severity: Low (kernel heap info-leak over the air, 1 byte per forwarded PERR)
Verdict: REPRODUCED β forwarded PERR TTL byte provably never sourced from
the received frame; constant prior-heap contents (0xff block β emitted
0xfe) across 18 triggers / 3 boots / both kernels.
What
hwmp_recv_perr() allocates the propagation PERR without M_ZERO
(ieee80211_hwmp.c:1743-1748, contrast the RX-path allocations at
:430-439/:530-537 which use M_ZERO) and never initializes
pperr->perr_ttl: the only touch is pperr->perr_ttl-- at :1823, after
which hwmp_send_perr β hwmp_add_meshperr (:784) writes that byte into
a broadcast PERR action frame. Every mesh neighbor receives one byte of
whatever previously occupied that heap block.
Trigger chain (all remote, after auto-peering):
1. PREP with origaddr == victim (skips propagation), targetaddr = Y,
ttl=1 β victim creates a VALID route for Y with nexthop = attacker
(ieee80211_hwmp.c:1427-1513).
2. PERR with dest_flags = 62 (numerically equal to
IEEE80211_REASON_MESH_PERR_NO_FI β hits the switch on the wrong field,
cf. DF-0353), dest = Y, seq = 0, ttl = N > 1 β route invalidated,
PERR accepted and forwarded (:1814-1824) with the uninitialized TTL.
Reproduce
sh findings/poc/DF-3328/build.sh sh findings/poc/DF-3328/run.sh # 5 rounds PREP+PERR, varying rx TTL # forwarded frames: sysctl kern.fwm_txlog (hex; TTL = byte offset 28)
Expected: every forwarded PERR (43 bytes, IE 132 at offset 26) carries
ttl = 0xfe while a spec-conforming implementation would send rx_ttl - 1
(4, 8, 199, 76, 249, β¦). The emitted value matches no byte of any injected
frame and is stable per heap state (see leak_sample.txt).
Fix
Initialize the propagated TTL from the received frame:
pperr->perr_ndests = j;
- pperr->perr_ttl--;
+ pperr->perr_ttl = perr->perr_ttl - 1;
hwmp_send_perr(vap, broadcastaddr, pperr);
(and/or add M_ZERO to the pperr allocation for defense in depth).
DF-3328 VERDICT β REPRODUCED (uninitialized kernel-heap byte broadcast in forwarded PERRs)
Reproduction summary
Guest: stock X86_64_GENERIC #0 (INVARIANTS), clean with-src snapshot.
Harness: shared fwm.ko (steps 31/32 + kern.fwm_ttl), same auto-peering
dance as DF-3287/DF-3327.
Sequence per trigger (all remote, from the ESTABLISHED attacker peer):
step 31β PREP,origaddr = victim(propagation block skipped atieee80211_hwmp.c:1521),ttl=1,targetaddr = 02:00:00:00:e1:01,targetseq=0x100β victim now holds a VALID route for Y withnexthop = attacker(hwmp.c:1427-1513).step 32β PERR,dest_flags = 62(numerically ==IEEE80211_REASON_MESH_PERR_NO_FI, hitting the switch-on-wrong-field athwmp.c:1768, cf. DF-0353),dest = Y,dest_seq = 0(βhr_seq++branch),ttl = kern.fwm_ttl > 1β accepted,j=1, forwarded (hwmp.c:1814-1824).
The forwarded PERR is captured by fwm_raw_xmit into kern.fwm_txlog
(hex). Frame = 43 bytes; elemid 132 at offset 26, TTL at offset 28.
Decisive observation
| run | injected rx TTLs | forwarded TTL byte (offset 28) |
|---|---|---|
boot 1 (run.log) |
5, 9, 200, 77, 250 | 0xfe Γ5 (correct impl: 4, 8, 199, 76, 249) |
boot 1 (run.2.log) |
3, 100, 55, 240, 9, 88, 150, 21, 66, 111 | 0xfe Γ10 (correct: 2, 99, 54, β¦) |
boot 2, fresh reset with-src (run.3.log) |
5, 90, 12 | 0xfe Γ3 (correct: 4, 89, 11) |
| DF-3327-patched kernel #1 (regression) | 42 | 0xfe (correct: 41) |
18 forwarded PERRs, 16 distinct rx TTLs, 3 boots, 2 kernels: the emitted TTL
is never rx_ttl β 1 and matches no byte of any injected frame. It
is the prior contents of the M_80211_MESH_PERR heap block at offset 2
(0xff on this kernel) minus the single pperr->perr_ttl-- at
hwmp.c:1823 β i.e., one byte of kernel heap memory disclosed over the air
to every mesh neighbor, on every forwarded PERR, at up to ~10 Hz
(ieee80211_hwmp_perrminint = 100 ms).
Root cause
hwmp.c:1743-1747βpperr = kmalloc(..., M_INTWAIT)without M_ZERO (RX-path preq/prep/perr allocations at:430-439,:484-491,:529-537all useM_ZERO).pperr->perr_ttlis written by nobody; the only touch ishwmp.c:1823pperr->perr_ttl--.hwmp_send_perrsetsperr_ie/perr_lenonly (:1861-1871);hwmp_add_meshperrthen writes the garbage-derived byte to the wire (:784*frm++ = perr->perr_ttl;).
Impact ceiling
1 byte of kernel heap per event (Low). Not attacker-chosen on this kernel (the block consistently holds 0xff), but it is heap-state-dependent disclosure; on busier 640-byte-bucket systems the byte varies with whatever last owned the block.
Fix
pperr->perr_ndests = j;
- pperr->perr_ttl--;
+ pperr->perr_ttl = perr->perr_ttl - 1;
hwmp_send_perr(vap, broadcastaddr, pperr);
plus (defense in depth) M_ZERO on the pperr allocation. Not
kernel-built/validated separately (fix is a one-line initialization; the
DF-3327 kernel rebuild was the budgeted one) β fix_status: not_testable
for this pack, code change is trivially side-effect-free.
Guest reset to clean with-src snapshot after the runs.
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
not_testablefix.diff authored (one-line initialization of the propagated TTL, plus M_ZERO suggestion) but not kernel-rebuilt in this run; the change is a trivially side-effect-free initialization of a byte that is currently provably garbage.
findings/poc/DF-3328/fix.diff
Confirmed kernel references
Detail
Exploit chain
auto-peered attacker sends (1) HWMP PREP origaddr=victim ttl=1 targetaddr=Y -> VALID route Y with nexthop=attacker (hwmp.c:1427-1513); (2) HWMP PERR dest=Y dest_flags=62 (== REASON_MESH_PERR_NO_FI in the misplaced switch, cf. DF-0353) dest_seq=0 ttl>1 -> PERR accepted+forwarded (hwmp.c:1756-1824) -> broadcast PERR whose TTL byte = uninitialized heap; captured via fwm_tx log; repeatable at ~10 Hz (perrminint 100 ms).
Evidence (decisive lines)
['leak_sample.txt: 18 forwarded-PERR TTL extractions across 3 boots + fixed kernel - always 0xfe, never rx_ttl-1', 'run.log / run.2.log / run.3.log: full sysctl transcripts incl. hex dumps of the 43-byte broadcast PERRs (elemid 132 at offset 26, TTL at offset 28)', 'fwm.c step_prep_route()/step_perr_forward(): exact injected frame bytes']
PoC changes
New fwm harness steps 31 (PREP creating route Y via attacker) and 32 (PERR flags=62 seq=0) plus kern.fwm_ttl sysctl; no seed sketch existed.
Verified recommended fix
Set pperr->perr_ttl = perr->perr_ttl - 1 (inherit from the received frame) and/or allocate pperr with M_ZERO; see fix.diff.
Verdict
hwmp_recv_perr's propagation buffer is allocated without M_ZERO (ieee80211_hwmp.c:1743-1748) and pperr->perr_ttl is never initialized - the only write is the decrement at :1823 before hwmp_send_perr broadcasts the frame - so hwmp_add_meshperr (:784) transmits one byte of prior kernel heap contents as the PERR TTL to the whole mesh. Proven on the guest: across 18 forwarded PERRs, 16 distinct injected rx TTLs (5,9,200,77,250,3,100,55,240,9,88,150,21,66,111,90,12,42) and 3 boots (plus the DF-3327-patched kernel), every emitted TTL byte was 0xfe (== heap 0xff minus the decrement) and never rx_ttl-1; the value matches no byte of any injected frame.
No comments yet.