β¬’ 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 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
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 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.