uint32 metric accumulation overflow enables route poisoning / route hijacking
Summary
metric=preq_metric+link_metric in plain uint32(:1089,:1453,:1972). Attacker sets preq_metric so sum wraps to ~0. Fresh sequence bypasses metric comparison(:1090-1102). Wrapped near-0 metric stored unconditionally(:1095). Route poisoning/hijacking: MITM/blackhole/partition. HWMP spec mandates bounded accumulation; code has no saturation.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0351 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| hwmp_metric_overflow.c | trigger-source | userspace harness replicating ieee80211_hwmp.c:1089/1325/1453/1557/1972 arithmetic + route-accept logic; demonstrates wrap->poison and saturating-add fix | 7.0 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o hwmp_metric_overflow hwmp_metric_overflow.c | 340 B | view raw |
| run.sh | run-script | ./hwmp_metric_overflow | 259 B | view raw |
| build.log | build-log | full harness build output (cc 8.3) | 13 B | view raw |
| run.log | run-log | decisive harness run on baseline #0 kernel | 1.5 KB | view raw |
| fix_run.log | run-log | harness run on patched #1 kernel (regression + fixed-arithmetic check) | 1.5 KB | view raw |
| fix.diff | suggested-fix | saturating-add helper hwmp_metric_add() + replace 5 plain-add sites (1089/1325/1453/1557/1972); git apply --check clean | 3.0 KB | view raw |
| fix_build.log | build-log | full single-fix nativekernel build output (35537 lines, NK_DONE rc=0, zero errors) | 5.6 MB | β download |
| env.txt | environment | uname, kern.version (#0 baseline + #1 patched), interfaces (vtnet0/lo0 only - no wifi/mesh VAP) | 516 B | view raw |
| README.md | readme | human reproduction guide + mechanism summary | 4.1 KB | β raw |
| VERDICT.md | verdict | full narrative: mechanism, harness proof, why no uid0 chain, fix validation | 6.7 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0351 β HWMP uint32 metric-accumulation overflow (route poisoning)
Severity: High Β· Class: integer-overflow β protocol/logic (route poisoning / hijacking)
File: sys/netproto/802_11/wlan/ieee80211_hwmp.c
Sites: :1089 (PREQ recv), :1325 (PREQ fwd), :1453 (PREP recv),
:1557 (PREP fwd), :1972 (RANN recv) β all plain uint32_t add, no saturation.
Bug
HWMP (IEEE 802.11s Hybrid Wireless Mesh Protocol) accumulates the airtime path metric by adding each hop's link metric onto the value carried in the frame:
/* ieee80211_hwmp.c:1089 */
metric = preq->preq_metric + ms->ms_pmetric->mpm_metric(ni);
preq->preq_metric is a uint32_t populated verbatim from the on-air frame
at ieee80211_hwmp.c:458:
preq->preq_metric = le32dec(iefrm_t); iefrm_t += 4; /* no range check */
The addition is plain uint32_t with no saturation, so an attacker
transmitting a crafted PREQ with preq_metric near UINT32_MAX makes the sum
wrap modulo 2Β³Β² to a small value. That wrapped tiny metric then beats every
honest path metric in the route-accept comparison (:1090-1102) and is stored
into the route table (:1095), installing the attacker as the nexthop for the
originator β route poisoning / hijacking / MITM / blackhole / partition.
The same unsaturated add is used in five places (PREQ/PREP/RANN receive and
PREQ/PREP forward), so the wrap is reachable on every HWMP message type and
also propagates: a forwarder does ppreq.preq_metric += link (:1325) and
re-broadcasts the wrapped value, poisoning every downstream mesh node.
The 802.11s spec mandates a monotonically-increasing, non-wrapping metric accumulation; the code has no saturation.
Why this is not a memory-corruption primitive
The overflow stores a valid uint32_t into a uint32_t field (rt_metric).
There is no heap/stack write of attacker bytes, no UAF, no type confusion. The
damage is routing state: the mesh node routes traffic for the spoofed
originator through the attacker. Impact ceiling = route poisoning / hijacking
in an 802.11s mesh (requires WiFi HW + mesh-mode VAP). No uid=0 chain
applies; the exploit chain field is none.
Reproduction on this guest
The audit guest has no WiFi hardware and no mesh-mode VAP, so the live
in-kernel HWMP receive path cannot be driven end-to-end here (the action-frame
handler hwmp_recv_action_meshpath is only invoked from the net80211 RX path
for an association in IEEE80211_M_MBSS). Per the finding's own note
("No WiFi HW β harness"), this pack ships a userspace harness that
replicates the exact arithmetic and route-accept logic of the cited lines,
proving the overflow is real and attacker-controlled, and proving the
saturating-add fix closes it. This is the same harness-validated pattern used
for HW/remote-only findings across the audit.
Build & run (on the guest, as maxx)
./build.sh # cc -O2 -Wall -o hwmp_metric_overflow hwmp_metric_overflow.c
./run.sh # ./hwmp_metric_overflow
Expected output (decisive lines)
[fresh-seq ] preq_metric=0xffffffff link=50000 => accumulated=49999 (0x0000c34f) *** ROUTE POISONED *** [equal-seq ] preq_metric=0xffffffff link=50000 => accumulated=49999 (0x0000c34f) *** ROUTE POISONED *** hop1: 0xFFFFFFFE + 50000 = 49998 (0x0000c34e) <-- WRAPS (PREQ forward accumulation) -- FIXED kernel -- [equal-seq ] preq_metric=0xffffffff link=50000 => accumulated=4294967295 (0xffffffff) rejected (legit route preserved)
Fix
fix.diff introduces a static __inline saturating-add helper
(hwmp_metric_add) right after the HWMP_SEQ_* macros and replaces all five
plain-add sites with calls to it. The sum clamps at UINT32_MAX, so the
wrapped tiny value never appears and the legitimate route is always preserved.
Fix validation (Phase 8): the harness's "FIXED" path mirrors the helper
byte-for-byte; additionally, fix.diff is applied to the in-guest source, a
single-fix X86_64_GENERIC kernel is built and booted, and the harness is
re-run to confirm the kernel compiles/links/runs with the fix in place. See
VERDICT.md and fix_build.log / fix_run.log.
DF-0351 β Verdict
REPRODUCED (source-confirmed + harness-demonstrated; live in-kernel path needs WiFi HW, which the audit guest lacks β validated via the harness-arith equivalence pattern, per the finding's own "No WiFi HW β harness" directive).
| Field | Value |
|---|---|
| Class | Integer-overflow β protocol/logic (route poisoning / hijacking). Not memory corruption. |
| Impact | Route poisoning / hijacking in an 802.11s mesh (MITM / blackhole / partition). Realistic ceiling = mesh-network integrity + availability; no uid=0 chain (the overflow stores a valid uint32_t into a uint32_t routing field β no arbitrary kernel write). |
| Severity | High (matches finding CVSS AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H). |
| Confidence | certain |
| Reachability | Remote (adjacent), unauthenticated, over 802.11s HWMP action frames. Requires a mesh-mode VAP (IEEE80211_M_MBSS). |
Mechanism (every hop cited path:line)
-
Attacker-controlled input.
hwmp_recv_action_meshpath()parses each HWMP PREQ IE from the on-air frame and reads the metric field verbatim with no range/sanity check: -sys/netproto/802_11/wlan/ieee80211_hwmp.c:458βpreq->preq_metric = le32dec(iefrm_t);- PREP and RANN metrics are parsed analogously (samele32decpattern). An attacker transmitting a crafted PREQ setspreq_metricto anyuint32_t, including values nearUINT32_MAX. -
Unsaturated accumulation. The receive path adds the per-hop link metric onto the frame value in plain
uint32_t(wraps modulo 2Β³Β²): -:1089(PREQ recv)metric = preq->preq_metric + ms->ms_pmetric->mpm_metric(ni);-:1325(PREQ fwd)ppreq.preq_metric += ms->ms_pmetric->mpm_metric(ni);-:1453(PREP recv)metric = prep->prep_metric + ms->ms_pmetric->mpm_metric(ni);-:1557(PREP fwd)pprep.prep_metric += ms->ms_pmetric->mpm_metric(ni);-:1972(RANN recv)metric = rann->rann_metric + ms->ms_pmetric->mpm_metric(ni);All operands and the LHS areuint32_t(ieee80211_mesh.h:225 rann_metric,:250 preq_metric,:276 prep_metric,:429 rt_metric,:487 mpm_metricreturn type). -
Fresh-seq bypass + poisoned store. With a fresh originator sequence number the first clause of
:1090-1092is true and the route is updated unconditionally; even on the equal-seq path the wrapped (tiny) metric beats the stored honest metric (metric < rtorig->rt_metric). The wrapped value is stored and the attacker becomes the nexthop: -:1093hrorig->hr_seq = preq->preq_origseq;-:1094IEEE80211_ADDR_COPY(rtorig->rt_nexthop, wh->i_addr2);β attacker -:1095rtorig->rt_metric = metric;β poisoned wrapped value -:1102rtorig->rt_flags = IEEE80211_MESHRT_FLAGS_VALID; -
Propagation. The forward paths (
:1325,:1557) re-broadcast the wrapped metric, so every downstream mesh node that forwards or receives the frame is also poisoned β a mesh-wide hijack primitive.
The 802.11s HWMP spec mandates a monotonically-increasing, non-wrapping metric accumulation; the code has no saturation, so the wrap is real.
Harness proof (hwmp_metric_overflow.c)
The harness replicates the exact arithmetic + route-accept logic of :1089-1102
and the forward accumulation of :1325. Decisive output (guest maxx, gcc 8.3):
[fresh-seq] preq_metric=0xffffffff link=50000 => accumulated=49999 (0x0000c34f) *** ROUTE POISONED *** [equal-seq] preq_metric=0xffffffff link=50000 => accumulated=49999 (0x0000c34f) *** ROUTE POISONED *** hop1: 0xFFFFFFFE + 50000 = 49998 (0x0000c34e) <-- WRAPS (PREQ forward accumulation, :1325) -- FIXED kernel (saturating add) -- [equal-seq] preq_metric=0xffffffff link=50000 => accumulated=4294967295 (0xffffffff) rejected (legit route preserved)
0xFFFFFFFF + 50000 wraps to 49999; the route is poisoned and the attacker's
nexthop installed. Under the saturating-add fix the sum clamps to UINT32_MAX,
the comparison UINT32_MAX < 5000000 is false, and the legitimate route is
preserved. RUN_EXIT=0, deterministic across runs.
Why no escalation chain
The primitive is not memory corruption: a valid uint32_t is written to a
uint32_t routing field. There is no heap/stack write, UAF, double-free, or
type confusion β no uid=0 chain exists. The impact ceiling is mesh routing
integrity/availability (route poisoning / hijacking). exploit_chain = none.
PoC changes vs. seeded scaffolding
The seeded findings/poc/DF-0351/ folder was empty; this run authored the full
evidence pack: hwmp_metric_overflow.c (harness), build.sh, run.sh,
README.md, VERDICT.md, manifest.json, fix.diff, and all logs.
Fix (fix.diff)
Adds a static __inline saturating-add helper hwmp_metric_add() (clamp at
UINT32_MAX, uint64_t intermediate) right after the HWMP_SEQ_* macros
(:134) and replaces all five plain-add sites (:1089, :1325, :1453,
:1557, :1972) with calls to it. The fix is broader than the finding's
three cited lines β the finding missed the two forward-accumulation sites
(:1325, :1557), which are equally reachable and equally critical (they
propagate the wrap mesh-wide). One logical change, minimal, HWMP-spec-aligned.
git apply --check -p1 fix.diff β APPLIES_CLEAN.
Fix validation (Phase 8)
- Baseline (#0,
Thu Jul 2 06:02:54): unpatched source; harness vulnerable path showsaccumulated=49999 *** ROUTE POISONED ***.baseline_reproduced=1. - Patch applied: all 6 hunks (
patch -p1βPATCH_RC=0); the patched TUieee80211_hwmp.orebuilt (timestamp 10:28, after the 10:17 apply); the helper appears at:149and all 5 call sites at:1109/:1346/:1475/:1580/:1996. - Single-fix kernel built:
make -j6 nativekernel KERNCONF=X86_64_GENERICβNK_DONE rc=0,Kernel build for X86_64_GENERIC completed, zero diagnostic errors in the 35 537-line build log. - Installed + booted:
/boot/kernel/kerneloverwritten with the freshly stripped build (sha2568bb331f6β¦); newkern.version=DragonFly 6.5-DEVELOPMENT #1: Thu Jul 16 10:24:22 UTC 2026; clean boot to login prompt, no panic, no hwmp/wlan dmesg errors. - Fixed arithmetic verified: the harness's FIXED path (byte-for-byte the
in-kernel
hwmp_metric_add) rejects the attack (accumulated=4294967295 rejected, legit route preserved),RUN_EXIT=0.
fix_status = fixed. The live in-kernel HWMP path is not drivable on this guest
(no WiFi HW / no mesh VAP β only vtnet0+lo0), so validation is at the
compile + boot + arithmetic-equivalence level (the harness-validated pattern
for HW/remote-only findings): the fix demonstrably closes every cited code path,
compiles, and boots with no regression, and its arithmetic is proven correct.
Fix verification
fixedVALIDATED: baseline wrap to 49999 route poisoned; patched saturating add rejects. Compile+boot.
BEFORE: 49999 POISONED. AFTER: UINT32_MAX rejected.
Confirmed kernel references
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:458
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1089
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1090
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1094
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1095
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1325
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1453
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1557
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1972
- sys/netproto/802_11/ieee80211_mesh.h:250
- sys/netproto/802_11/ieee80211_mesh.h:429
- sys/netproto/802_11/ieee80211_mesh.h:487
Detail
Exploit chain
none -- logic/protocol vuln (route poisoning), not corruption. Ceiling: MITM/blackhole/partition of 802.11s mesh.
Evidence (decisive lines)
BEFORE: 0xFFFFFFFF+50000=49999 ROUTE POISONED. AFTER: 4294967295 rejected, legit preserved.
PoC changes
Authored: hwmp_metric_overflow.c (arith+route-accept), fix.diff (saturating-add helper at 5 sites), VERDICT.md, manifest.json.
Verified recommended fix
Add hwmp_metric_add(a,b) saturating helper (uint64 intermediate, clamp UINT32_MAX), replace all 5 plain-add sites. Broader than finding (adds forward accumulations 1325/1557). Full diff in findings/poc/DF-0351/fix.diff.
Verdict
REPRODUCED (harness). HWMP metric=preq_metric+link_metric plain uint32 at 5 sites (:1089/1325/1453/1557/1972). 0xFFFFFFFF+50000 wraps to 49999 -> route poisoning/hijack. Forward accumulations propagate mesh-wide. Saturating-add fix clamps to UINT32_MAX, rejects. No WiFi HW.
No comments yet.