oce_tso_setup reads ether/IP/TCP headers (incl. th->th_off at offset up to ~74) before its m_pullup β out-of-contract mbuf reads on split-header TSO frames (DF-1373 class, different function)
Summary
TSO is enabled by default so oce_tso_setup runs for large TCP sends; it dereferences eh/ip/ip6 and th->th_off with no m_len check, and only then m_pullup(total_len). If the TCP header sits in a later mbuf, th->th_off reads stale bytes past m_len (inside the cluster) -> bogus total_len -> m_pullup fails -> silent ENXIO drop. Same class as DF-1373; distinct function. Pullup-first fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4081 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 1.7 KB | β raw | |
| verdict.json | β | 1.9 KB | view raw | |
| manifest.json | β | 609 B | view raw |
DF-4081 β oce_tso_setup parses IP/TCP headers before m_pullup (sibling of
DF-1373, different function): reads past m_len of the first mbuf
File: sys/dev/netif/oce/oce_if.c (pass 2) Class: parsing of attacker-influenced data (out-of-contract mbuf reads)
Root cause
oce_tso_setup() (oce_if.c:1021-1087) β reached from oce_tx() at oce_if.c:845
whenever m->m_pkthdr.csum_flags & CSUM_TSO β dereferences protocol headers
before the m_pullup at line 1080:
- 1046:
eh = mtod(m, struct ether_vlan_header *);(18-byte read, no length check against m->m_len) - 1047-1052:
eh->evl_encap_proto/eh->evl_protoreads - 1058/1068:
ip/ip6cast atm->m_data + ehdrlenβ field reads - 1061/1071:
th = (struct tcphdr *)((caddr_t)ip + (ip->ip_hl << 2)); - 1063/1073:
th->th_offdereferenced β offset ehdrlen+ihl*4 (up to 74) with no guarantee that many bytes are in the first mbuf (m_len). - only THEN: 1080
m = m_pullup(m, total_len);
CSUM_TSO frames are produced by every TCP connection over oce (TSO is in
if_capabilities and if_capenable is set to capabilities at attach,
oce_if.c:1697-1707), and the IP/TCP header split across mbufs is legal for
locally-generated chains (e.g. unusual socket-buffer write patterns). When
the TCP header is not (fully) in the first mbuf, th->th_off is read past
m_len β stale cluster bytes within the MCLBYTES allocation (not an
allocation-boundary overread; the same class DF-1373 established for
oce_check_ipv6_ext_hdr). Consequence: garbage total_len β m_pullup fails β
packet silently dropped (ENXIO at oce_if.c:849-852), or pullup to a wrong
size. Correctness/robustness bug more than disclosure.
Fix
Pull up sizeof(*eh) + max(sizeof(struct ip6_hdr), sizeof(struct ip)) +
sizeof(struct tcphdr) (or simply ETHER_HDR_LEN+sizeof(struct ip6_hdr)+64)
before reading any header fields, and re-derive pointers afterwards β the
same fix pattern as DF-1373.
Verification status
UNTESTED β hardware-gated (no oce NIC in QEMU, TSO path cannot be exercised).
DF-4081 VERDICT β oce_tso_setup pre-pullup header reads
Status: UNTESTED (hardware-gated; no oce device in QEMU)
Source trace:
- oce_if.c:842-853: CSUM_TSO frames take oce_tso_setup() on every TSO send.
- TSO enabled by default: oce_if.c:1697-1699 adds IFCAP_TSO to capabilities, oce_if.c:1707 sets if_capenable = if_capabilities.
- oce_if.c:1046-1073 dereferences eh / ip / ip6 / th (th->th_off at m_data+14+ip_hl*4, up to byte ~74 of the first mbuf) with no m_len check; the m_pullup that would guarantee contiguity is at oce_if.c:1080, after the reads.
- If the TCP header lives in a later mbuf (legal chain shape from the socket layer), th->th_off reads stale bytes past m_len but inside the cluster β bogus total_len β m_pullup failure β silent ENXIO drop (oce_if.c:849-852), or a wrong-size pullup consumed by the WQE builder.
Distinct from DF-1373 only in function (oce_check_ipv6_ext_hdr vs oce_tso_setup) β same defect class, both live on the TX path.
Fix
m = *mpp;
+
+ m = m_pullup(m, ETHER_HDR_LEN + sizeof(struct ip6_hdr) +
+ sizeof(struct tcphdr));
+ if (m == NULL)
+ return NULL;
+ *mpp = m;
if (M_WRITABLE(m) == 0) {
(re-derive eh/ip6/th after the pullup; existing pointer math then stays valid because m_pullup preserves data offsets.)
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableFix validation not possible: no OneConnect hardware/emulation in QEMU.
Guest pciconf -l: no 0x19a2/0x10df devices.
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md, VERDICT.md. Key lines: oce_if.c:1046-1073 (pre-pullup derefs), 1080 (pullup after), 842-852 (call site + drop), 1697-1707 (TSO on by default).
PoC changes
No runnable PoC: hardware-gated.
Verified recommended fix
m_pullup the ether+IP+TCP header region at the top of oce_tso_setup before any header dereference.
Verdict
oce_tso_setup (oce_if.c:1021-1087) reads ether/IP/TCP header fields β including th->th_off at byte offset up to ~74 β from the first mbuf before the m_pullup at line 1080, with no m_len validation. On TSO frames whose TCP header is not in the first mbuf, th->th_off reads past m_len (stale bytes within the cluster, in-allocation), producing a bogus total_len: m_pullup then fails and the packet is silently dropped. Sibling of pass-1 DF-1373 (oce_check_ipv6_ext_hdr), different function. Untested: no oce NIC in the QEMU guest.
No comments yet.