Thread (14 messages) 14 messages, 2 authors, 4d ago

Re: [PATCH net-next v4 9/9] ibmveth: check headers in the RX large send and checksum helpers

flat view

From: mingming cao <hidden>
Date: 2026-10-13 21:00:52
Also in: linuxppc-dev, lkml

On 10/12/26 10:51 PM, netdev-bot+sashiko@kernel.org wrote:
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 0 (1) · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] ibmveth_rx_mss_helper() (drivers/net/ethernet/ibm/ibmveth.c) now
   sets gso_segs = DIV_ROUND_UP(ip_len - hdr_len, gso_size).
- [Low] The kernel-doc of ibmveth_rx_mss_helper_test() says the helper
   takes "gso_segs from the IP length" and refuses "Frames whose headers…

Pre-existing issues:
- [Medium] ibmveth_rx_csum_helper()
   (drivers/net/ethernet/ibm/ibmveth.c:1530) seeds the TCP pseudo-header…

--

ibmveth: check headers in the RX large send and checksum helpers

This adds bounds checks on the IP and TCP headers in
ibmveth_rx_mss_helper() and ibmveth_rx_csum_helper(). The checks include a
minimum IPv4 ihl and TCP doff, and IPv4 TCP fragments are refused. The large
send helper now computes gso_segs from the IP length and clamps gso_size,
and KUnit cases are added for both helpers.
quoted
diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c
index 85e176d0ed321..1c2ac88c28ca3 100644
--- a/drivers/net/ethernet/ibm/ibmveth.c
+++ b/drivers/net/ethernet/ibm/ibmveth.c
[ ... ]
quoted
@@ -1406,28 +1410,41 @@ static void ibmveth_rx_mss_helper(struct sk_buff *skb, u16 mss, int lrg_pkt)
  	if (skb->protocol == htons(ETH_P_IP)) {
  		struct iphdr *iph = (struct iphdr *)skb->data;
  
+		if (skb_headlen(skb) < sizeof(*iph) || iph->ihl < 5)
+			return false;
  		if (iph->protocol == IPPROTO_TCP) {
+			/* only a whole datagram has a TCP header to segment */
+			if (ip_is_fragment(iph))
+				return false;
  			offset = iph->ihl * 4;
-			skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
+			ip_len = ntohs(iph->tot_len);
+			gso_type = SKB_GSO_TCPV4;
[ ... ]
quoted
  	tcph = (struct tcphdr *)(skb->data + offset);
+	if (skb_headlen(skb) < offset + sizeof(*tcph) || tcph->doff < 5 ||
+	    ip_len > skb_headlen(skb) || ip_len <= offset + tcph->doff * 4)
+		return false;
+	skb_shinfo(skb)->gso_type = gso_type;
  	if (lrg_pkt) {
  		skb_shinfo(skb)->gso_size = mss;
  	} else if (offset) {
@@ -1436,11 +1453,18 @@ static void ibmveth_rx_mss_helper(struct sk_buff *skb, u16 mss, int lrg_pkt)
  	}
  
  	if (skb_shinfo(skb)->gso_size) {
+		/* chosen by the sending partition: at least the floor
+		 * virtio_net_hdr_to_skb() uses, and never GSO_BY_FRAGS
+		 */
+		skb_shinfo(skb)->gso_size =
+			clamp_t(u16, skb_shinfo(skb)->gso_size,
+				TCP_MIN_GSO_SIZE, GSO_BY_FRAGS - 1);
  		hdr_len = offset + tcph->doff * 4;
  		skb_shinfo(skb)->gso_segs =
-				DIV_ROUND_UP(skb->len - hdr_len,
+				DIV_ROUND_UP(ip_len - hdr_len,
  					     skb_shinfo(skb)->gso_size);
  	}
[Severity: High]

Can gso_segs here undercount the number of segments that actually get
produced?
|Thanks for catching this. Agreed. |
The only check of ip_len against the frame is ip_len > skb_headlen(skb).
That means a frame much longer than its IP length is accepted, and nothing
trims the skb to ip_len before napi_gro_receive().

The commit message says the skb is not marked SKB_GSO_DODGY because
gso_segs is computed here. That holds once ip_rcv_core() trims the skb to
tot_len, on local delivery or routing. On the Trunk VEA forwarding paths
(a bridge without br_netfilter, or OVS without conntrack), the skb is not
trimmed.

For example, a sending partition could:

   - set iph->check = 0xffff
   - put an MSS of 8 in tcph->check
   - set tot_len = 100
   - send a frame of about 9000 bytes

The new checks pass (100 <= headlen, 100 > 40), so gso_segs becomes 8.
tcp_gso_segment() or hardware TSO works on skb->len instead, which gives
about 1120 segments.

On egress, gso_features_check() compares the trusted gso_segs against the
device limit:

net/core/dev.c:gso_features_check() {
     ...
	if (gso_segs > READ_ONCE(dev->gso_max_segs))
		return features & ~NETIF_F_GSO_MASK;
     ...
}

Also, qdisc_pkt_len_segs_init() only recomputes gso_segs from skb->len for
SKB_GSO_DODGY skbs.

Would this let a TSO skb past the egress NIC's gso_max_segs? Would it also
make the qdisc and BQL accounting too low?

Before this patch, gso_segs was derived from skb->len, which matches what
gets segmented. Any of these would avoid the mismatch:

   - trim the skb to ip_len, for example with pskb_trim_rcsum()
   - refuse large sends where skb_headlen(skb) != ip_len
   - compute gso_segs from skb->len

[ ... ]
quoted
@@ -1493,6 +1523,8 @@ static void ibmveth_rx_csum_helper(struct sk_buff *skb,
  	if (iph_proto == IPPROTO_TCP) {
  		struct tcphdr *tcph = (struct tcphdr *)(skb->data + iphlen);
  
+		if (skb_headlen(skb) < iphlen + sizeof(*tcph))
+			goto bad_hdr;
  		if (tcph->check == 0x0000) {
  			/* Recompute TCP pseudo header checksum  */
  			tcphdrlen = skb->len - iphlen;
[Severity: Medium]

This isn't a bug introduced by this patch, but should the pseudo-header
length in ibmveth_rx_csum_helper() come from the IP header rather than
skb->len?

The frame can have bytes past the end of the IP datagram, either Ethernet
padding or trailing bytes that ibmveth_rx_mss_helper() still accepts when
ip_len < skb_headlen(skb). If tcph->check is also 0, the seed is computed
over the wrong length, and skb_partial_csum_set() then marks the skb
CHECKSUM_PARTIAL.

ip_rcv_core() and ip6_rcv_core() later trim the skb with pskb_trim_rcsum().
For CHECKSUM_PARTIAL, that only checks where the checksum field is:

net/core/skbuff.c:pskb_trim_rcsum_slow() {
     ...
	} else if (skb->ip_summed == CHECKSUM_PARTIAL) {
		int hdlen = (len > skb_headlen(skb)) ? skb_headlen(skb) : len;
		int offset = skb_checksum_start_offset(skb) + skb->csum_offset;

		if (offset + sizeof(__sum16) > hdlen)
			return -EINVAL;
	}
     ...
}

The seed is left as it was.

Could a forwarded packet then go out with a TCP checksum over the trimmed
data but a pseudo-header for the untrimmed length? The final receiver
would drop it.

This patch moved the large send helper to the IP length. Would
tot_len - ihl * 4, or the IPv6 payload_len, make sense here too?

[ ... ]
|Yes, that makes sense. Since that behavior predates this series (from commit 66aa0678efc2), I'd prefer not to expand Patch 9 further and will handle that as a separate follow-up. |


quoted
@@ -2508,10 +2549,214 @@ static void ibmveth_rxq_harvest_buffer_test(struct kunit *test)
[ ... ]
quoted
+	} else {
+		struct iphdr *iph = (struct iphdr *)skb->data;
+
+		skb->protocol = htons(ETH_P_IP);
+		iph->version = 4;
+		iph->ihl = 5;
+		iph->protocol = IPPROTO_TCP;
+		iph->tot_len = htons(len);
+	}
[ ... ]
quoted
+/**
+ * ibmveth_rx_mss_helper_test - unit test for ibmveth_rx_mss_helper
+ * @test: pointer to kunit structure
+ *
+ * Large send frames get gso_size from the descriptor or the TCP checksum
+ * field, clamped, and gso_segs from the IP length. Frames whose headers
+ * do not fit, with no TCP payload within the IP length, or that are
+ * IPv4 fragments, are refused and left without GSO.
+ *
+ * Return: void
+ */
[Severity: Low]

This isn't a bug, but do any of the cases in ibmveth_rx_mss_helper_test()
test "gso_segs from the IP length" or the "headers do not fit" refusal?

ibmveth_test_tcp_skb() always sets tot_len (or 40 + payload_len) equal to
skb->len. So in every accepted case, DIV_ROUND_UP(ip_len - hdr_len, ...)
gives the same result as the old DIV_ROUND_UP(skb->len - hdr_len, ...).
The only case with ip_len < skb->len is the padding case, and it is refused
before gso_segs is computed.

No mss helper case builds an skb shorter than sizeof(struct iphdr),
sizeof(struct ipv6hdr), or offset + sizeof(struct tcphdr). So if gso_segs
went back to skb->len, or those skb_headlen() checks were dropped from
ibmveth_rx_mss_helper(), every expectation would still pass.

Could cases be added for a tot_len smaller than the frame and for truncated
headers? Otherwise, could the kernel-doc be narrowed to what is tested?

|We added the tot_len < frame length KUnit test case in v5. Truncated headers are already safely caught by the strict IP length / payload checks. v5 is posted here: https://lore.kernel.org/netdev/20261013182926.30417-1-mmc@linux.ibm.com/ (local) Thanks, Mingming pw-bot: cr|

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help