mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v4] ipv6: fix dead half of the MTU guard in __ip6_append_data()
@ 2026-11-02  1:57 bo cao
  2026-11-02  2:03 ` netdev-bot+sinfo
  0 siblings, 1 reply; 3+ messages in thread
From: bo cao @ 2026-11-02  1:57 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, pabeni, dsahern, edumazet, idosch, horms,
	willemdebruijn.kernel, stable, linux-kernel

The second half of the MTU guard in __ip6_append_data() has been dead
code since the initial git import:

	if (mtu <= fragheaderlen ||
	    ((mtu - fragheaderlen) & ~7) + fragheaderlen <= sizeof(struct frag_hdr))
		goto emsgsize;

(x & ~7) is unsigned and >= 0, and fragheaderlen is at least
sizeof(struct ipv6hdr) = 40 > sizeof(struct frag_hdr) = 8, so the
second condition can never be true.

For mtu - fragheaderlen in [1,7], (x & ~7) == 0, so

	maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen - 8

underflows below fragheaderlen.  Every later bound computed as
maxfraglen - fragheaderlen then wraps to 0xfffffff8, in particular the
datalen clamp inside the fragment loop, which never fires and lets
fraglen = datalen + fragheaderlen escape the MTU constraint.

With MSG_MORE on an egress device without NETIF_F_SG, alloclen = mtu
while skb_put() is called with the unclamped fraglen, so a single
sendmsg() on a raw IPv6 socket with a 1240-byte HOPOPTS cmsg and
IPV6_MTU = fragheaderlen + 1 triggers:

  skbuff: skb_over_panic: len:61280 put:61280 ...
  kernel BUG at net/core/skbuff.c
  skb_put __ip6_append_data ip6_append_data rawv6_sendmsg

The same re-lowering exists with a transport-mode xfrm bundle: ESP/AH
bytes counted in rt->dst.header_len are not part of fragheaderlen
(ESP lacks XFRM_TYPE_NON_FRAGMENT), and ip6_append_data_mtu()
subtracts dst.header_len from mtu for the first fragment, so after
the entry guard passes, the recomputed maxfraglen can be pushed below
fragheaderlen the same way.

Remove the dead expression and, when the datagram actually has to be
fragmented, require room for at least one 8-octet payload block after
the fragment header, matching the minimum ip6_fragment() already
enforces once skb->len > mtu.  The check is placed after the dontfrag
and maxnonfragsize handling so that unfragmented datagrams that fit
within mtu, and the dontfrag PMTU notification path, keep their
current behaviour.

reserved_headerlen accounts for the first fragment's xfrm header
space regardless of whether this append is the first one on the cork:
a subsequent append that pushes an initially unfragmented datagram
past mtu still fragments, and ip6_append_data_mtu() subtracts
dst.header_len from mtu for the first fragment.  The check runs
before any zerocopy uarg is allocated, so the emsgsize path needs no
cleanup.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Suggested-by: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
Link: https://lore.kernel.org/netdev/willemdebruijn.kernel.22a3131380a8c@gmail.com/
Signed-off-by: bo cao <treebacker@foxmail.com>
---
Changes in v2: move the check into ip6_append_data_mtu() (review of v1).
Changes in v3: collapse the fix into a single entry check, leaving
ip6_append_data_mtu() unchanged (review of v2).
Changes in v4: gate the check on fragmentation actually being needed
and place it after the dontfrag / maxnonfragsize handling, so valid
unfragmented datagrams and the dontfrag PMTU notification path are not
affected; include dst.header_len in reserved_headerlen regardless of
!skb, so a subsequent corked append that fragments still accounts for
the first fragment's xfrm header space (review of v3).
---
 net/ipv6/ip6_output.c | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 25fe0ba98b1a..4353b154f216 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -1477,7 +1477,7 @@ static int __ip6_append_data(struct sock *sk,
 	bool paged, hold_tskey = false, extra_uref = false;
 	struct ipv6_txoptions *opt = v6_cork->opt;
 	int csummode = CHECKSUM_NONE;
-	unsigned int maxnonfragsize, headersize;
+	unsigned int maxnonfragsize, headersize, reserved_headerlen;
 	unsigned int wmem_alloc_delta = 0;
 
 	skb = skb_peek_tail(queue);
@@ -1499,8 +1499,7 @@ static int __ip6_append_data(struct sock *sk,
 		     (opt ? opt->opt_flen + opt->opt_nflen : 0) +
 		     rt->rt6i_nfheader_len;
 
-	if (mtu <= fragheaderlen ||
-	    ((mtu - fragheaderlen) & ~7) + fragheaderlen <= sizeof(struct frag_hdr))
+	if (mtu <= fragheaderlen)
 		goto emsgsize;
 
 	maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen -
@@ -1533,6 +1532,29 @@ static int __ip6_append_data(struct sock *sk,
 		return -EMSGSIZE;
 	}
 
+	/*
+	 * If the datagram has to be fragmented, each non-final fragment
+	 * must leave room for at least 8 bytes of payload after the
+	 * fragment header, matching the minimum ip6_fragment() enforces
+	 * once skb->len > mtu.  This rejects the MTUs that make maxfraglen
+	 * underflow below fragheaderlen and defeat the datalen clamp,
+	 * which leads to skb_over_panic() from skb_put().
+	 *
+	 * reserved_headerlen accounts for the first fragment's xfrm header
+	 * space (with a transport-mode xfrm bundle, ESP/AH bytes counted
+	 * in dst.header_len are not part of fragheaderlen), regardless of
+	 * whether this append is the first one on the cork: a subsequent
+	 * append that pushes an initially unfragmented datagram past mtu
+	 * still fragments, and ip6_append_data_mtu() subtracts
+	 * dst.header_len from mtu for the first fragment.
+	 */
+	reserved_headerlen = fragheaderlen;
+	if (!(rt->dst.flags & DST_XFRM_TUNNEL))
+		reserved_headerlen += rt->dst.header_len;
+	if (reserved_headerlen + cork->length + length > mtu &&
+	    mtu < reserved_headerlen + sizeof(struct frag_hdr) + 8)
+		goto emsgsize;
+
 	/* CHECKSUM_PARTIAL only with no extension headers and when
 	 * we are not going to fragment
 	 */
-- 
2.39.5 (Apple Git-154)


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-02  2:18 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-02  1:57 [PATCH net v4] ipv6: fix dead half of the MTU guard in __ip6_append_data() bo cao
2026-11-02  2:03 ` netdev-bot+sinfo
2026-11-02  2:18   ` bo cao

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®