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 | 65 B | view raw |
| run.sh | run-script | ./hwmp_metric_overflow | 89 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 | 7.1 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 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
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.