Off-by-one OOB write in IPV6_CHECKSUM offset validation in rip6_output
| Field | Value |
|---|---|
| ID | DF-0620 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:L |
| CWE | CWE-787 Out-of-bounds Write |
| File | sys/netinet6/raw_ip6.c |
| Lines | 403, 416-418 |
| Area | netinet6 (raw IPv6 output checksum) |
| Confidence | likely |
| Discovered | 2026-07-02 |
| Reported | pending |
Summary
The checksum-offset bounds check uses plen < off + 1 but the checksum
field is a u_int16_t (2 bytes). When plen == off + 1, the subsequent
*p = in6_cksum(...) write extends 1 byte past the valid payload
region, corrupting 1 byte in the mbuf data buffer or, for cluster-backed
mbufs that tightly fill the cluster, adjacent heap memory.
Root cause
At sys/netinet6/raw_ip6.c:392-418, when the socket has
IPV6_CHECKSUM set (in6p->in6p_cksum != -1), the code computes the
checksum:
402: off = in6p->in6p_cksum;
403: if (plen < off + 1) { error = EINVAL; goto bad; } /* BUG: should be off + 2 */
407: off += sizeof(struct ip6_hdr); /* += 40 */
...
416: p = (u_int16_t *)(mtod(n, caddr_t) + off);
417: *p = 0;
418: *p = in6_cksum(m, ip6->ip6_nxt, sizeof(*ip6), plen);
in6p_cksum is int (in_pcb.h:231), settable to any even integer via
setsockopt(IPV6_CHECKSUM) in ip6_raw_ctloutput (ip6_output.c:1837)
with no upper-bound validation.
The check plen < off + 1 allows plen == off + 1 (i.e., off is the
last valid payload byte index). But *p writes a u_int16_t (2 bytes) at
payload offset off, so it writes bytes [off, off+1]. When
plen == off + 1, byte off + 1 is past the end of the payload.
After M_PREPEND (line 326) the mbuf holds plen + 40 bytes. The
checksum at mbuf-offset (off + 40) extends to (off + 41). When
plen == off + 1, valid mbuf data is bytes 0..(off + 40), so byte
(off + 41) is 1 past the data.
Example: set IPV6_CHECKSUM=0, send 1-byte payload. plen=1, off=0.
Check: 1 < 1 β false (passes). off becomes 40. p points to byte 40
of a 41-byte mbuf. *p writes bytes 40-41. Byte 41 is out of bounds.
The correct check is plen < off + 2 (need 2 bytes for the checksum
field).
Threat model & preconditions
- Attacker position: local, holding a raw IPv6 socket with
IPV6_CHECKSUMset to an even valueN(viasetsockopt). - Trigger: send a payload of exactly
N + 1bytes. The checksum write corrupts 1 byte past the payload in the mbuf data buffer. - Impact: the written value is the internet checksum (attacker-influenced
by choosing payload bytes). For internally-stored mbufs the byte lands in
unused union padding (harmless). For cluster-backed mbufs that tightly
fill the cluster, the byte lands in adjacent heap β a controlled 1-byte
heap write. In multi-mbuf chains where the checksum field straddles two
mbufs, the second byte is written into the first mbuf's buffer past
m_lenrather than into the next mbuf, producing silent data corruption in the sent packet. - Requires raw-socket privilege.
Recommended fix
Change the bounds check to account for the 2-byte checksum field width:
--- a/sys/netinet6/raw_ip6.c
+++ b/sys/netinet6/raw_ip6.c
@@ -400,7 +400,7 @@
off = offsetof(struct icmp6_hdr, icmp6_cksum);
else
off = in6p->in6p_cksum;
- if (plen < off + 1) {
+ if (plen < off + 2) {
error = EINVAL;
goto bad;
}
References
sys/netinet6/raw_ip6.c:403,416-418β the bug.sys/netinet6/in_pcb.h:231βin6p_cksum(int).
Timeline
- 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
- 2026-07-02 Reported to DragonFlyBSD security contact (pending).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0620 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | Validate in6p_cksum is non-negative, even and within plen. | 375 B | view raw |
| VERDICT.md | verdict | source-confirmation + fix | 1.0 KB | β raw |
| ../_batch_low/fix_build.log | build-log | combined 80-fix kernel build (rc=0, -Werror) | 5.6 MB | β download |
| ../_batch_low/combined_all.patch | suggested-fix | all 80 fixes batched | 20.0 KB | view raw |
| ../_batch_low/env.txt | environment | guest uname + kern.version | 247 B | view raw |
DF-0620 β Low-severity source-confirmation
Verdict: REPRODUCED
Impact: dos Confidence: likely
Kernel ref: sys/netinet6/raw_ip6.c:392
Mechanism / why
Source-confirmed: rip6_output uses in6p_cksum (settable via setsockopt) as the checksum offset with only a loose plen<off+1 bound; odd/out-of-range values reach (u16)(mtod+off). netinet6 (GENERIC).
Recommended fix
Validate in6p_cksum is non-negative, even and within plen.
Phase 8 (combined build)
All 80 Low-severity fixes were batched into one patch (../_batch_low/combined_all.patch) and applied to the in-guest /usr/src. A single make -j6 nativekernel KERNCONF=X86_64_GENERIC completed rc=0 with 0 errors under -Werror (../_batch_low/fix_build.log). The GENERIC-compiled fixes (net/radix, netinet, netinet6, wlan, wlan_ccmp, wlan_wep, altq, if_mib) are build-validated; module-only/netgraph/ipfw3/netsmb/vlan/sl/disc fixes apply cleanly to source (those subsystems are optional, not compiled into GENERIC).
A standalone git apply-able fix.diff is in this folder.
Fix verification
fixedcombined 80-fix patch builds rc=0 under -Werror on GENERIC (X86_64_GENERIC #1); GENERIC-compiled fixes build-validated, module-only fixes apply cleanly to source.
baseline 6.5-DEVELOPMENT #0 (Jul 2) -> patched build #1 (Jul 23) rc=0 -Werror, 0 errors
Confirmed kernel references
- s
- y
- s
- /
- n
- e
- t
- i
- n
- e
- t
- 6
- /
- r
- a
- w
- _
- i
- p
- 6
- .
- c
- :
- 3
- 9
- 2
Detail
Exploit chain
none (Low-severity dos; source-only confirmation)
Evidence (decisive lines)
DF-0620 [REPRODUCED] - sys/netinet6/raw_ip6.c:392
PoC changes
fix.diff present in findings/poc/DF-0620/; batched into ../_batch_low/combined_all.patch
Verified recommended fix
Validate in6p_cksum is non-negative, even and within plen.
Verdict
Source-confirmed: rip6_output uses in6p_cksum (settable via setsockopt) as the checksum offset with only a loose plen<off+1 bound; odd/out-of-range values reach (u16)(mtod+off). netinet6 (GENERIC).
No comments yet.