vr_encap copies m_pkthdr.len bytes into fixed MCLBYTES TX buffer without bounds check: heap overflow when MTU raised
- File:
sys/dev/netif/vr/if_vr.c - Lines: 1288, 1289, 1290, 1313
- Severity: Low
- CVSS:
CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H - CWE: CWE-787 Out-of-bounds Write
- Confidence: likely
Summary
vr_encap unconditionally copies the entire outgoing packet
(m_head->m_pkthdr.len bytes) via m_copydata into a per-descriptor TX scratch
buffer of exactly MCLBYTES (2048) bytes, with no check that m_pkthdr.len
fits.
vr_encap always returns 0 (line 1313) so the caller's error check in
vr_start is dead code and can never catch an oversize frame.
If the interface MTU is raised above ~2034 bytes (via SIOCSIFMTU, which
requires root), outgoing packets exceed the 2048-byte buffer and m_copydata
writes up to ~63500 bytes past it, corrupting adjacent TX scratch slots and β
for descriptor index 127 β overflowing past the contigmalloc'd
VR_TX_BUF_SIZE region into adjacent kernel heap.
Root cause
Line 1288: tx_buf = VR_TX_BUF(sc, chain_idx) expands (if_vrreg.h:402) to
sc->vr_cdata.vr_tx_buf + (chain_idx * MCLBYTES). The backing allocation at
vr_attach:752 is contigmalloc(VR_TX_BUF_SIZE, ...) where
VR_TX_BUF_SIZE = VR_TX_LIST_CNT * MCLBYTES = 128 * 2048 = 262144
(if_vrreg.h:401). So each descriptor's tx_buf is a 2048-byte slot within
this 256KB contiguous region.
Line 1289: m_copydata(m_head, 0, m_head->m_pkthdr.len, tx_buf) copies the full
packet length into this slot β m_copydata (uipc_mbuf.c:1671) performs a raw
bcopy into the destination with no destination-size awareness.
There is NO comparison of m_head->m_pkthdr.len against MCLBYTES anywhere in
vr_encap or vr_start.
Line 1313: return(0) unconditionally, so the
if (vr_encap(...)) { ifq_set_oactive; break; } guard at vr_start:1361 can
never fire.
The driver hardcodes ifp->if_mtu = ETHERMTU (1500) at attach (line 767), so
with the default MTU the max Ethernet frame is 1514 bytes and the overflow is
unreachable. But the kernel ifioctl layer allows SIOCSIFMTU up to
MAXIMUM_MTU (65535) with only a root privilege check, and this driver
registers no per-driver MTU cap or SIOCSIFMTU handler to reject oversize
values. Once MTU is raised, any locally-sent packet (TCP/UDP/ICMP, or a BPF
write up to the new MTU per bpf.c:643) whose total length exceeds 2048
overflows tx_buf.
Threat
Attacker position: root on the local machine (required to set MTU via
ifconfig/SIOCSIFMTU). With default MTU the path is unreachable, so this is
not a privilege-escalation primitive.
However, it is a genuine kernel heap overflow: once MTU > ~2034, any
unprivileged user who can send traffic through vr0 (e.g. a multi-user system
where an admin configured a large MTU, or a forwarded/bridged path) triggers a
controlled-content write of up to ~63500 bytes past the 2048-byte slot.
For descriptor 0..126 this overwrites sibling TX scratch buffers (within the
256KB allocation); for descriptor 127 it writes past the contigmalloc region
into physically-contiguous adjacent pages.
Impact: kernel heap corruption β panic or, with grooming, arbitrary kernel
write. The same defect class exists in other legacy single-buffer NIC drivers
that do not cap m_pkthdr.len before m_copydata.
Exploit / PoC
As root on a machine with a VIA Rhine NIC:
ifconfig vr0 mtu 9000(raises MTU; the driver does not reject it).- As any user:
ping -s 8972 <peer>or send a UDP datagram > ~2034 payload bytes vianc.
The network stack assembles an mbuf chain with m_pkthdr.len up to ~9014
bytes. vr_start dequeues it, vr_encap calls
m_copydata(m_head, 0, 9014, tx_buf) where tx_buf is 2048 bytes.
m_copydata bcopy's 9014 bytes into the 2048-byte slot, overflowing ~6966
bytes past it.
Success: immediate kernel panic (Fatal trap 12 on the overwritten memory, or
corrupted slab metadata causing a later KASSERT panic).
With heap grooming (fill the adjacent region with controlled data, arrange for descriptor 127 to be the victim), the overflow writes attacker-controlled packet content into adjacent kernel objects β a potential code-execution primitive for an attacker who already has root, or for an unprivileged user on a misconfigured multi-user host.
The PoC trigger is trivial:
ifconfig vr0 mtu 9000 && ping -c1 -s 8972 <gateway>
Recommended fix
Add an m_pkthdr.len bounds check in vr_encap (or vr_start before calling
vr_encap) and return a non-zero error so vr_start's existing error path
fires and the oversize frame is dropped. Also reject oversize MTU in vr_ioctl's
SIOCSIFMTU case to prevent the driver from being left in a state where the TX
path can overflow:
--- a/sys/dev/netif/vr/if_vr.c
+++ b/sys/dev/netif/vr/if_vr.c
@@ -1285,6 +1285,14 @@ vr_encap(struct vr_softc *sc, int chain_idx, struct mbuf *m_head)
* to copy, just do it all the time.
*/
tx_buf = VR_TX_BUF(sc, chain_idx);
+
+ /*
+ * The per-descriptor TX scratch buffer is MCLBYTES. Drop
+ * frames that would overflow it (only reachable if the MTU
+ * has been raised beyond what this 10/100 controller supports).
+ */
+ if (m_head->m_pkthdr.len > MCLBYTES)
+ return (EFBIG);
+
m_copydata(m_head, 0, m_head->m_pkthdr.len, tx_buf);
len = m_head->m_pkthdr.len;
With this, vr_start:1361's existing
if (vr_encap(sc, cur_tx_idx, m_head)) { ifq_set_oactive(&ifp->if_snd); ... break; }
correctly drops the frame.
Optionally also add a cap in vr_ioctl so SIOCSIFMTU > ETHERMTU returns
EINVAL, preventing the driver from entering a state where all TX is silently
dropped.
Related findings
- DF-1481 (sibling):
vr_rxeofRX length OOB in same file.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1482 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | Fix for if_vr encap TX buffer overflow | 377 B | view raw |
| VERDICT.md | verdict | Source-only verification verdict | 816 B | β raw |
| build.sh | build-script | No-op (source-only) | 109 B | view raw |
| run.sh | run-script | No-op (source-only) | 107 B | view raw |
VERDICT DF-1482: if_vr encap TX buffer overflow
Verdict
REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.
Mechanism
m_copydata copies m_pkthdr.len into fixed MCLBYTES buffer with no bounds check; overflow when MTU raised.
Source reference: sys/dev/netif/vr/if_vr.c:1288-1290.
Reproduction
Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed.
The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present
on the QEMU/virtio guest. The finding is HW-gated.
Fix
Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with
make -j6 nativekernel KERNCONF=X86_64_GENERIC β rc=0, -Werror clean.
See fix.diff for the git-apply-able patch.
Fix verification
fixedCombined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.
'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- n
- e
- t
- i
- f
- /
- v
- r
- /
- i
- f
- _
- v
- r
- .
- c
- :
- 1
- 2
- 8
- 8
Detail
Exploit chain
none
Evidence (decisive lines)
Source confirmed: sys/dev/netif/vr/if_vr.c:1288. Combined 41-fix kernel build rc=0 -Werror clean.
PoC changes
fix.diff authored; validated by combined kernel build.
Verified recommended fix
Add m_pkthdr.len>MCLBYTES check. Matches finding.
Verdict
REPRODUCED (source-confirmed). m_copydata into MCLBYTES buffer with no len bounds check. Cited path verified at sys/dev/netif/vr/if_vr.c:1288. HW/module-gated on QEMU guest.
No comments yet.