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

gre_mobile_input bcopy size underflow (m_len-msiz-ip_hl*4) β€” huge overwrite when mobile header larger than mbuf

Summary

gre_mobile_input :237-238 bcopy((ip)+(ip_hl<<2)+msiz,(ip)+(ip_hl<<2),m->m_len-msiz-(ip_hl<<2)). Third arg signed int: m_len<msiz+ip_hl*4 -> negative -> implicit conversion to size_t ~2^64 -> bcopy faults. gre_in_cksum :232 check bypassable via chained mbuf (m_len<m_pkthdr.len mobile header straddles mbufs) or IP options (DF-0743 checksum reads wrong offset). Trigger: outer IP fragmented -> ip_reass chained mbuf short head mbuf. Remote unauth requires configured IPPROTO_MOBILE tunnel. Fix: if(m->m_len<iphlen+msiz){m_freem;return IPPROTO_DONE}.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0741 Β· 15 files
FileTypeDescriptionSize
trigger.c trigger-source userspace raw-socket injector: 3 short IPPROTO_MOBILE packets 8.9 KB view raw
build.sh build-script cc -O2 -Wall -o trigger trigger.c 409 B view raw
run.sh run-script sets up gre-mobile tunnel, runs trigger 1.1 KB view raw
README.md readme summary + how to reproduce 4.1 KB ↓ raw
VERDICT.md verdict full narrative: trigger -> primitive -> effect -> fix 5.8 KB ↓ raw
build.log build-log baseline trigger build output 8 B view raw
panic.txt panic-signature baseline panic: vm_fault stack-guard from memmove/encap4_input 715 B view raw
run.log.baseline_panic.txt run-log full interleaved baseline panic from boot.log 1.0 KB view raw
fix.diff suggested-fix git-apply-able: add m_len >= ip_hl*4+msiz guard before bcopy 991 B view raw
fix_build.log build-log excerpt of nativekernel log showing ip_gre.c + if_gre.ko rebuild 7.7 KB view raw
fix_nativekernel.log build-log full 5.8MB nativekernel build log 5.6 MB ↓ download
fix_run.log run-log patched-kernel trigger: exits 0, no panic 740 B view raw
env.txt environment uname, cc, sysctl vm.randomize_mmap 190 B view 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 summary + how to reproduce
↓ download raw

DF-0741 β€” gre_mobile_input bcopy size underflow

Severity: Medium (CVSS 3.1: AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H) Class: CWE-787 OOB write + CWE-191 integer underflow Location: sys/netinet/ip_gre.c:237-238 (gre_mobile_input)

Bug

gre_mobile_input (the IP-in-IP mobile-IP decapsulation handler, registered for IPPROTO_MOBILE = 55 in sys/net/gre/if_gre.c:140-154) computes a bcopy length without validating that the head mbuf is large enough:

bcopy((caddr_t)(ip) + (ip->ip_hl << 2) + msiz,
      (caddr_t)(ip) + (ip->ip_hl << 2),
      m->m_len - msiz - (ip->ip_hl << 2));          /* signed int -> size_t */

All three operands are int. When a short / chained-mbuf packet has m->m_len < ip_hl*4 + msiz, the third argument goes negative and is then implicitly widened to size_t for bcopy() β†’ near-2^64 byte copy β†’ immediate page fault β†’ kernel panic.

Reachability

gre is a loadable module (if_gre.ko). When an admin creates a gre interface in mobile mode (ifconfig greN -link0 β†’ g_proto = IPPROTO_MOBILE, sys/net/gre/if_gre.c:461-470) and brings it up, encap_attach registers in_mobile_protosw whose .pr_input = gre_mobile_input for IPPROTO_MOBILE. Any packet routed to the host with ip_p = 55 and matching outer src/dst then takes the path:

ip_input -> ip_protox[55] -> encap4_input
  -> mask_match -> (*psw->pr_input)() = gre_mobile_input

gre_mobile_input does no m_pullup() and no length check.

PoC

trigger.c (compile: cc -O2 -Wall -o trigger trigger.c, run: ./run.sh) opens a raw socket SOCK_RAW/IPPROTO_MOBILE with IP_HDRINCL and sends three short crafted packets to 127.0.0.1 after setting up gre0 in mobile mode. Each variant is shorter than the mobile header requires, so each independently triggers the underflow.

Reproduction

Baseline (6.5-DEVELOPMENT #0, shipped if_gre.ko): - panic: vm_fault: fault on stack guard - memmove() at memmove+0x24f - encap4_input() at encap4_input+0x20b - ssh dies, vm.sh status => down

Patched (rebuilt if_gre.ko with fix.diff): - trigger exits 0 cleanly, x3 consecutive runs - guest stays up - no panic in boot.log

Impact

DoS / kernel panic. No uid=0 escalation: the over-write that would land in the mbuf cluster faults on the next unmapped page (the kernel stack guard) before any attacker-controlled write reaches a victim object, so there is no corruption primitive to convert.

Realistic threat: any remote attacker who can deliver a packet with the matching outer IP src/dst to a host that has a gre-mobile tunnel configured can panic the kernel with a single short datagram.

Fix

fix.diff adds a one-line guard before the bcopy:

if (m->m_len < (ip->ip_hl << 2) + msiz) {
    m_freem(m);
    return(IPPROTO_DONE);
}

This matches the finding markdown's recommended fix.

Build single-fix module

gre is NOT in the static X86_64_GENERIC kernel, so the correct single-fix build is the module, not a full nativekernel:

cd /usr/src/sys/net/gre
make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
cp if_gre.ko /boot/kernel/if_gre.ko
sync; reboot

A naive make nativekernel does NOT rebuild if_gre.ko (it reuses the warm obj), so the first fix-validation pass appears to fail until the module is rebuilt directly.

Files

file purpose
trigger.c userspace raw-socket injector (3 short IPPROTO_MOBILE packets)
build.sh cc -O2 -Wall -o trigger trigger.c
run.sh setup gre-mobile tunnel + run trigger
build.log baseline trigger build output
panic.txt baseline panic signature from boot.log
run.log.baseline_panic.txt full interleaved baseline panic
fix.diff standalone git-apply-able fix (one-line guard)
fix_build.log single-fix module build output (and full nativekernel log)
fix_run.log patched-kernel trigger output (exit 0, no panic)
env.txt guest uname / cc / sysctls
VERDICT.md full narrative
manifest.json machine-readable catalog
VERDICT.md verdict full narrative: trigger -> primitive -> effect -> fix
↓ download raw

DF-0741 β€” gre_mobile_input bcopy size underflow

Finding

sys/netinet/ip_gre.c:gre_mobile_input() (the IP-in-IP mobile-IP decapsulation handler, registered via in_mobile_protosw for IPPROTO_MOBILE = 55 in sys/net/gre/if_gre.c:140-154) computes a bcopy length as

bcopy((caddr_t)(ip) + (ip->ip_hl << 2) + msiz,
      (caddr_t)(ip) + (ip->ip_hl << 2),
      m->m_len - msiz - (ip->ip_hl << 2));          /* ip_gre.c:237-238 */

without validating that m->m_len >= msiz + ip->ip_hl*4. All three operands (m->m_len, msiz, (ip->ip_hl << 2)) are int, so the third argument is evaluated as a signed int. When a short / truncated / chained-mbuf packet makes the value negative, the implicit widening to size_t for bcopy() produces a near-2^64 byte length (memmove() underneath). The copy faults on the next unmapped page immediately -> kernel panic.

Reachability path (confirmed live)

A gre interface configured for mobile encapsulation (ifconfig greN -link0 -> g_proto = IPPROTO_MOBILE, see sys/net/gre/if_gre.c:461-470) and brought up with a matching src/dst pair causes encap_attach() (sys/net/gre/if_gre.c:563-566) to register in_mobile_protosw (whose .pr_input = gre_mobile_input) for IPPROTO_MOBILE.

When a crafted IP packet with ip_p = 55 arrives whose outer src/dst matches the tunnel:

ip_input
  -> ip_protox[IPPROTO_MOBILE]            /* in_proto.c:201-215 */
     -> encap4_input                       /* ip_encap.c:131 */
        -> mask_match finds the gre-mobile entry
           -> (*psw->pr_input)(mp,offp,proto)
              = gre_mobile_input           /* ip_gre.c:205 */

gre_mobile_input does NOT call m_pullup() and does NOT check the mbuf length before computing the bcopy size β€” so any packet with m_len < ip_hl*4 + msiz (i.e. shorter than 28 bytes with no S-bit, or 32 bytes with S-bit) trips the underflow.

This matches the panic signature observed on the baseline kernel: panic: vm_fault: fault on stack guard from memmove+0x24f called via encap4_input+0x20b (i.e. the (*psw->pr_input)() call site at ip_encap.c:223).

PoC (live path, not a harness)

trigger.c opens a raw socket socket(AF_INET, SOCK_RAW, IPPROTO_MOBILE) with IP_HDRINCL and sends three short crafted packets to the loopback address (127.0.0.1), each shorter than the 8-byte (no S-bit) / 12-byte (S-bit) mobile header requires:

Variant A: 24-byte packet, S-bit clear, msiz=8 -> bcopy len = -4 Variant B: 24-byte packet, S-bit set, msiz=12 -> bcopy len = -8 Variant C: 26-byte packet, S-bit clear, msiz=8 -> bcopy len = -2

Each one independently triggers the underflow.

Setup (root, see run.sh): ifconfig gre create ifconfig gre0 tunnel 127.0.0.1 127.0.0.1 ifconfig gre0 -link0 # g_proto = IPPROTO_MOBILE ifconfig gre0 up

Realistic threat model: gre-mobile tunnels are normally configured by an administrator (root). The reachable attacker is remote: anyone who can route a packet with the matching outer IP src/dst to the host. Once the tunnel exists, no further privilege is required to trip the panic β€” sending a single short IP packet is enough.

Reproduction

BASELINE (unpatched, with-src #0, if_gre.ko as shipped): - run trigger -> guest dead within ~1s - boot.log: panic: vm_fault: fault on stack guard, memmove() at memmove+0x24f, encap4_input() at encap4_input+0x20b - ssh no longer answers; vm.sh status => down

Impact: kernel panic / reliable DoS. No write-primitive conversion attempted because the underflow is a bcopy with an absurd size that faults on the next unmapped page before any controlled write lands β€” the over-write is into the mbuf cluster and trips a stack guard before reaching an attacker-useful victim object. This is a DoS-class bug, not a corruption primitive.

Fix

One-line guard added at ip_gre.c (just after the existing gre_in_cksum drop, before the bcopy):

if (m->m_len < (ip->ip_hl << 2) + msiz) {
    m_freem(m);
    return(IPPROTO_DONE);
}

fix.diff is a standalone git apply-able unified diff.

Fix build

Because gre is a loadable module (NOT compiled into the X86_64_GENERIC static kernel β€” kldstat shows if_gre.ko is loaded on ifconfig gre create), the correct single-fix build is the module, not a full nativekernel:

cd /usr/src/sys/net/gre
make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
cp if_gre.ko /boot/kernel/if_gre.ko
sync; reboot

A naive make nativekernel did NOT rebuild if_gre.ko (the warm obj was reused), so the first fix-validation pass appeared to fail until the module was rebuilt directly. See fix_build.log.

Fix validation

PATCHED (same #0 static kernel; freshly built if_gre.ko, sha256 9f0e7e3f0b919b9c269987ef4d153f9d6a18b536b4dfbd21c1bdebe80f2c20a0): - kldload if_gre (load OK) - ifconfig gre create / tunnel / -link0 / up (gre0 up) - run trigger x3 -> all three runs exit 0 cleanly, guest still answers ssh, boot.log has NO panic / memmove / encap4_input entry after the run.

Before: panic, guest dead. After: trigger exits 0, guest up, no panic, no log noise. => fix closes the bug.

Disassembly of the patched gre_mobile_input confirms the new guard: cmp %ecx,%edx (m_len vs (ip_hl<<2)+msiz) followed by jl drop just before the bcopy/memmove call site (offsets 0x1732-0x1734 in the rebuilt module).

Verdict: REPRODUCED on baseline #0 (panic), FIXED on single-fix module (no panic). Impact class: DoS / kernel panic from a short crafted packet once a gre-mobile tunnel is configured. No escalation (write-primitive would-be over-write faults before any controlled landing).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED. Baseline #0 kernel with the shipped if_gre.ko panics ('vm_fault: fault on stack guard' from memmove/encap4_input) on the same trigger. After installing the if_gre.ko built with fix.diff (rebuilt directly via cd /usr/src/sys/net/gre && make KERNBUILDDIR=...; nativekernel alone rebuilds the module but does not install it), three consecutive trigger runs all exited 0 cleanly with no panic and the guest stayed up. The fix closes the bug. (Caveat documented in notes: the static X86_64_GENERIC kernel does not contain ip_gre.c -- gre is a module -- so the proper single-fix artifact is if_gre.ko, not kernel.stripped.)

BEFORE (baseline #0 + shipped if_gre.ko): trigger -> guest dead in ~1s. boot.log shows 'panic: vm_fault: fault on stack guard, addr: 0xfffff80117618000' / 'memmove() at memmove+0x24f' / 'encap4_input() at encap4_input+0x20b' / 'db>'. AFTER (same #0 kernel + patched if_gre.ko): trigger stdout '[DF-0741] variant A sent 24 bytes' / '[DF-0741] variant B sent 24 bytes' / '[DF-0741] variant C sent 26 bytes' / 'TRIG_RC=0'; vm.sh status => up; grep panic|memmove|encap4 boot.log => empty (no panic). x3 consecutive runs identical.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (static kernel unchanged) + freshly-built if_gre.ko module (sha256 9f0e7e3f0b919b9c269987ef4d153f9d6a18b536b4dfbd21c1bdebe80f2c20a0, BuildID[sha1]=137f0d743ac6c71015fb02e4dbb63f9cc5bd58b8) carrying the fix.diff patch

Confirmed kernel references

Detail

Exploit chain

none -- over-read/over-write only, no escalation. The bcopy over-write would land in the mbuf cluster but the size is so large (~2^64) that memmove faults on the kernel stack guard page immediately, before any attacker-controlled write reaches a victim object. There is no corruption primitive to convert into a uid0 chain. Impact ceiling is reliable kernel panic / DoS (CVSS AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H).

Evidence (decisive lines)

BASELINE (unpatched #0, shipped if_gre.ko sha256 unknown, 323544 bytes): run trigger -> panic within 1s. boot.log: 'panic: vm_fault: fault on stack guard, addr: 0xfffff80117618000' / 'memmove() at memmove+0x24f 0xffffffff80bcab4f' / 'encap4_input() at encap4_input+0x20b 0xffffffff807aa07b' / 'Debugger("panic")' / 'Stopped at Debugger+0x7c: movb $0,0xbdaf09(%rip)' / 'db>'. vm.sh status => down. Trigger sent three short variants: 24-byte (msiz=8, len would be -4), 24-byte S-bit (msiz=12, len -8), 26-byte (msiz=8, len -2); any one trips the underflow.

PoC changes

Wrote findings/poc/DF-0741/ from scratch (no prior PoC existed): trigger.c (raw-socket injector, 3 short IPPROTO_MOBILE packets), build.sh, run.sh, README.md, VERDICT.md, manifest.json, fix.diff, env.txt, full untrimmed build/run/panic logs. The finding markdown was never created by the orchestrator; I derived everything from the DB summary row.

Verified recommended fix

Add a one-line length guard immediately before the bcopy in gre_mobile_input (sys/netinet/ip_gre.c, just after the existing gre_in_cksum drop at line 235): if (m->m_len < (ip->ip_hl << 2) + msiz) { m_freem(m); return(IPPROTO_DONE); }. This validates that the head mbuf carries the full IP header plus the variable-length mobile header before the bcopy, eliminating the signed-int underflow that widens to ~2^64. Matches the finding proposal in the DB summary (m_freem + IPPROTO_DONE return). Full git-apply-able diff in findings/poc/DF-0741/fix.diff.

Verdict

REPRODUCED on baseline 6.5-DEVELOPMENT #0 (with-src). gre_mobile_input at sys/netinet/ip_gre.c:237-238 computes the bcopy length as the signed-int expression m->m_len - msiz - (ip->ip_hl << 2) and then implicitly widens it to size_t for bcopy(). For a short packet with m_len < ip_hl4 + msiz the value goes negative and becomes ~2^64, so memmove (which bcopy lowers to) faults on the next unmapped page. Live path confirmed: created gre0 in mobile mode (ifconfig gre0 -link0 sets g_proto=IPPROTO_MOBILE via sys/net/gre/if_gre.c:466-469), brought it up with tunnel 127.0.0.1 -> 127.0.0.1, and injected a 24-byte IPPROTO_MOBILE packet via raw socket with IP_HDRINCL. Guest panicked within ~1s with 'panic: vm_fault: fault on stack guard, addr 0xfffff80117618000' from memmove+0x24f called via encap4_input+0x20b (i.e. (psw->pr_input)() at ip_encap.c:223 = gre_mobile_input). ssh died; vm.sh status => down. Reachable remotely by any attacker who can deliver a packet matching the tunnel's outer src/dst to a host with a gre-mobile tunnel configured; no further privilege required. The original DB summary cited sys/net/gre/if_gre.c but the vulnerable code is in sys/netinet/ip_gre.c (the mobile-input handler); both files compile into the if_gre.ko module via sys/net/gre/Makefile.