When a guest transmits a short Ethernet frame, iov_size() returns the wire length including any Ethernet padding bytes added to reach the minimum 60-byte frame size. net_tx_pkt_rebuild_payload() used that inflated size directly as payload_len. net_tx_pkt_update_ip_hdr_ checksum() then rewrites the IP Total Length (IPv4) or Payload Length (IPv6) field using this value, producing a malformed packet on the wire: the receiver interprets Ethernet padding bytes as IP payload.
In practice this breaks protocol stacks that validate IP lengths strictly. A nested ESXi host running on OpenStack using the e1000e emulation sends short TCP ACK packets in response to a Windows guest's virtio TLS client hello, which get padded to 60 bytes; the inflated IP Total Length corrupts the IP header seen by Windows, causing TLS handshakes and TCP connections to fail. Fix net_tx_pkt_rebuild_payload() to read the IP header's declared length and clamp payload_len accordingly: IPv4: ip_total_len - l3_hdr_len IPv6: ip6_plen adjusted for parsed extension headers Guard both paths against malformed guest packets where the header- declared length is shorter than the IP header itself; without the guard the unsigned subtraction would underflow, producing a huge payload_len. Fall back to the raw iov_size() value in that case. Signed-off-by: Sanjeeva Yerrapureddy <[email protected]> --- hw/net/net_tx_pkt.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/hw/net/net_tx_pkt.c b/hw/net/net_tx_pkt.c index 903238dca2..d3549a4378 100644 --- a/hw/net/net_tx_pkt.c +++ b/hw/net/net_tx_pkt.c @@ -274,7 +274,60 @@ static bool net_tx_pkt_parse_headers(struct NetTxPkt *pkt) static void net_tx_pkt_rebuild_payload(struct NetTxPkt *pkt) { - pkt->payload_len = iov_size(pkt->raw, pkt->raw_frags) - pkt->hdr_len; + size_t raw_payload_len = iov_size(pkt->raw, pkt->raw_frags) - pkt->hdr_len; + struct iovec *l2hdr = &pkt->vec[NET_TX_PKT_L2HDR_FRAG]; + uint16_t l3_proto = eth_get_l3_proto(l2hdr, 1, l2hdr->iov_len); + + /* + * Clamp payload_len to what the IP header says, not what iov_size() + * returns. When a short frame is padded to the Ethernet minimum of 60 + * bytes, iov_size() includes the padding. Using that inflated size as + * payload_len causes net_tx_pkt_update_ip_hdr_checksum() to rewrite the + * IP total/payload length to include Ethernet padding, making the packet + * malformed (receiver may treat padding as payload or miscalculate + * checksums). Clamp to the guest-provided IP length instead. + * + * IPv4: ip_len is the total length including the IP header. + * IPv6: ip6_plen is the payload length after the base 40-byte header + * (extension headers are included in ip6_plen but also in + * l3_hdr_len, so subtract them out). + */ + if (l3_proto == ETH_P_IP) { + uint16_t ip_total_len = be16_to_cpu(pkt->l3_hdr.ip.ip_len); + size_t l3_hdr_len = pkt->vec[NET_TX_PKT_L3HDR_FRAG].iov_len; + /* + * Guard against malformed packets where ip_total_len is zero or + * smaller than the IP header itself. Without this check, the + * subtraction (ip_total_len - l3_hdr_len) would underflow (both + * types are unsigned), producing a huge payload_len and corrupting + * the packet. Fall back to the raw iov_size value in that case. + */ + if (ip_total_len > l3_hdr_len) { + pkt->payload_len = MIN(raw_payload_len, ip_total_len - l3_hdr_len); + } else { + pkt->payload_len = raw_payload_len; + } + } else if (l3_proto == ETH_P_IPV6) { + uint16_t ip6_payload_len = be16_to_cpu(pkt->l3_hdr.ip6.ip6_plen); + size_t l3_hdr_len = pkt->vec[NET_TX_PKT_L3HDR_FRAG].iov_len; + size_t ext_hdr_len = l3_hdr_len - sizeof(struct ip6_header); + /* + * Guard against malformed packets where ip6_plen is zero or does + * not cover the extension headers that were already parsed. Without + * this check, the subtraction (ip6_payload_len - ext_hdr_len) would + * underflow (unsigned), producing a huge payload_len. Fall back to + * the raw iov_size value in that case. + */ + if (ip6_payload_len > ext_hdr_len) { + pkt->payload_len = MIN(raw_payload_len, + ip6_payload_len - ext_hdr_len); + } else { + pkt->payload_len = raw_payload_len; + } + } else { + pkt->payload_len = raw_payload_len; + } + pkt->payload_frags = iov_copy(&pkt->vec[NET_TX_PKT_PL_START_FRAG], pkt->max_payload_frags, pkt->raw, pkt->raw_frags, --- base-commit: b83371668192a705b878e909c5ae9c1233cbd5fb change-id: 20260624-net-tx-pkt-ip-length-padding-5a8f358933fc Best regards, -- Sanjeeva Yerrapureddy <[email protected]>
