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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0351 Β· 13 files
FileTypeDescriptionSize
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
README.md readme human reproduction guide + mechanism summary
↓ download 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.

VERDICT.md verdict full narrative: mechanism, harness proof, why no uid0 chain, fix validation
↓ download raw

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)

  1. 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 (same le32dec pattern). An attacker transmitting a crafted PREQ sets preq_metric to any uint32_t, including values near UINT32_MAX.

  2. 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 are uint32_t (ieee80211_mesh.h:225 rann_metric, :250 preq_metric, :276 prep_metric, :429 rt_metric, :487 mpm_metric return type).

  3. Fresh-seq bypass + poisoned store. With a fresh originator sequence number the first clause of :1090-1092 is 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: - :1093 hrorig->hr_seq = preq->preq_origseq; - :1094 IEEE80211_ADDR_COPY(rtorig->rt_nexthop, wh->i_addr2); ← attacker - :1095 rtorig->rt_metric = metric; ← poisoned wrapped value - :1102 rtorig->rt_flags = IEEE80211_MESHRT_FLAGS_VALID;

  4. 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 shows accumulated=49999 *** ROUTE POISONED ***. baseline_reproduced=1.
  • Patch applied: all 6 hunks (patch -p1 β‡’ PATCH_RC=0); the patched TU ieee80211_hwmp.o rebuilt (timestamp 10:28, after the 10:17 apply); the helper appears at :149 and 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/kernel overwritten with the freshly stripped build (sha256 8bb331f6…); new kern.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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: baseline wrap to 49999 route poisoned; patched saturating add rejects. Compile+boot.

BEFORE: 49999 POISONED. AFTER: UINT32_MAX rejected.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Thu Jul 16 10:24:22 UTC 2026

Confirmed kernel references

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.