β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3328

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3328 Β· 14 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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):

  1. step 31 β€” PREP, origaddr = victim (propagation block skipped at ieee80211_hwmp.c:1521), ttl=1, targetaddr = 02:00:00:00:e1:01, targetseq=0x100 β†’ victim now holds a VALID route for Y with nexthop = attacker (hwmp.c:1427-1513).
  2. step 32 β€” PERR, dest_flags = 62 (numerically == IEEE80211_REASON_MESH_PERR_NO_FI, hitting the switch-on-wrong-field at hwmp.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-537 all use M_ZERO).
  • pperr->perr_ttl is written by nobody; the only touch is hwmp.c:1823 pperr->perr_ttl--.
  • hwmp_send_perr sets perr_ie/perr_len only (:1861-1871); hwmp_add_meshperr then 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_testable
baseline reproduced→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-3328

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.