* [PATCH net 2/2] net/ipv4: fail append when the moved fraggap exceeds the fragment data
[not found] <v2-20261026-cover@tencent.com>
2026-10-26 9:51 ` [PATCH net 1/2] net/ipv4: reserve exthdrlen as headroom instead of counting it in skb->len =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?=
@ 2026-10-26 9:51 ` =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?=
2026-10-26 12:20 ` Eric Dumazet
1 sibling, 1 reply; 4+ messages in thread
From: =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?= @ 2026-10-26 9:51 UTC (permalink / raw)
To: netdev
Cc: David Ahern, Ido Schimmel, Paolo Abeni, Jakub Kicinski,
Willem de Bruijn, Steffen Klassert, Eric Dumazet, security
When the first fragment of a cork is already at maxfraglen, the next
__ip_append_data() iteration relays the previous fragment's tail,
fraggap = skb_prev->len - maxfraglen, into the new skb. The moved
fraggap is charged against datalen by computing
copy = datalen - transhdrlen - fraggap - pagedlen;
but once datalen is capped at maxfraglen - fragheaderlen it can become
smaller than transhdrlen + fraggap. Then copy goes negative: no payload
is copied and the iteration makes no progress. Worse, in the paged
branch (MSG_SPLICE_PAGES / MSG_ZEROCOPY / GSO) pagedlen = datalen -
transhdrlen - fraggap additionally wraps unsigned, breaking the
alloclen + pagedlen == fraglen invariant that commit eca856950f7c
("ipv4: account for fraggap on the paged allocation path") relies
on; the loop then either queues skbs with garbage tail accounting
(silent packet corruption) or, when maxfraglen == fragheaderlen,
never makes progress and grinds on until -ENOBUFS.
Reject such an append before any allocation: if the moved tail
(transhdrlen + fraggap) cannot fit in the fragment's datalen, the
iteration cannot make progress, so fail with -EINVAL. This covers all
three alloclen branches, including the paged one. With the preceding
fix fraggap is bounded by the alignment slop, but the condition stays
reachable with extreme geometries (e.g. an effective mtu too small for
fragheaderlen + transhdrlen), so pin the geometry explicitly, as
__ip6_append_data() has done since commit 232cd35d0804 ("ipv6: fix
out of bound writes in __ip6_append_data()").
This cannot reject a send that would otherwise succeed: the guard only
fires when transhdrlen + fraggap > datalen, which is exactly when copy
would come out negative, or, on the paged branch, when pagedlen would
wrap. No send can get past an iteration with a negative copy -- no
payload is copied, so the loop makes no progress. All callers flush
the cork on append error, and returning -EINVAL in mid-cork state
already happens in this function (MSG_ZEROCOPY ubuf mismatch).
Fixes: 353e5c9abd90 ("ipv4: Fix IPsec slowpath fragmentation problem")
Reported-by: TencentOS Corvus AI <corvus@tencent.com>
Cc: stable@vger.kernel.org
Signed-off-by: Anakin Zhang <tencentos_secure@tencent.com>
---
net/ipv4/ip_output.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
index b84d56c..5db8b06 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -1105,6 +1105,11 @@ alloc_new_skb:
fraglen = datalen + fragheaderlen;
pagedlen = 0;
+ if (unlikely(transhdrlen + fraggap > datalen)) {
+ err = -EINVAL;
+ goto error;
+ }
+
alloc_extra = hh_len + 15;
alloc_extra += exthdrlen;
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH net 1/2] net/ipv4: reserve exthdrlen as headroom instead of counting it in skb->len
[not found] <v2-20261026-cover@tencent.com>
@ 2026-10-26 9:51 ` =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?=
2026-10-27 15:26 ` Willem de Bruijn
2026-10-26 9:51 ` [PATCH net 2/2] net/ipv4: fail append when the moved fraggap exceeds the fragment data =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?=
1 sibling, 1 reply; 4+ messages in thread
From: =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?= @ 2026-10-26 9:51 UTC (permalink / raw)
To: netdev
Cc: David Ahern, Ido Schimmel, Paolo Abeni, Jakub Kicinski,
Willem de Bruijn, Steffen Klassert, Eric Dumazet, security
When sending through an xfrm route (rt->dst.header_len > 0),
__ip_append_data() counts the bundle header space ("exthdrlen")
into skb->len of the first fragment skb: the skb setup does
skb_reserve(skb, hh_len);
data = skb_put(skb, fraglen + exthdrlen - pagedlen);
skb_set_network_header(skb, exthdrlen);
so a fully filled first skb has skb->len == maxfraglen + exthdrlen.
Since commit 353e5c9abd90 ("ipv4: Fix IPsec slowpath fragmentation
problem"), the next iteration's fraggap = skb_prev->len - maxfraglen
therefore inherits the whole bundle header length instead of the
intended alignment slop.
fraggap is relayed into the next skb by skb_copy_and_csum_bits(), but
the MSG_MORE (alloclen = mtu) and linear (alloclen = fraglen) branches
account for fraggap in the allocation only for as long as datalen stays
uncapped. Once datalen is capped at maxfraglen - fragheaderlen, the
bytes copied into the fresh skb exceed the skb head allocation and run
past the end of the buffer, corrupting skb_shared_info. The
paged-branch accounting of
commit eca856950f7c ("ipv4: account for fraggap on the paged
allocation path") relies on datalen accounting for fraggap, which
stops being true exactly when datalen is capped.
Fix the root cause by reserving exthdrlen as headroom, like IPv6 has
done since commit 1f85851e17b6 ("ipv6: fix incorrent ipv6 ipsec
packet fragment") in 2012:
skb_reserve(skb, hh_len + exthdrlen);
data = skb_put(skb, fraglen - pagedlen);
skb_reset_network_header(skb);
With this, every fragment skb satisfies skb->len <= maxfraglen, so
fraggap <= mtu - maxfraglen <= 7 (the alignment slop the relay was
designed for) and the copy destination is provably in bounds.
Allocation sizes, tail positions, tailroom and truesize are unchanged
(alloc_extra already carries exthdrlen), and when dst.header_len == 0
this is a literal no-op.
Fixes: 353e5c9abd90 ("ipv4: Fix IPsec slowpath fragmentation problem")
Reported-by: TencentOS Corvus AI <corvus@tencent.com>
Cc: stable@vger.kernel.org
Signed-off-by: Anakin Zhang <tencentos_secure@tencent.com>
---
net/ipv4/ip_output.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
index eaa6fab..b84d56c 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -1150,16 +1150,16 @@ alloc_new_skb:
*/
skb->ip_summed = csummode;
skb->csum = 0;
- skb_reserve(skb, hh_len);
+ skb_reserve(skb, hh_len + exthdrlen);
/*
* Find where to start putting bytes.
*/
- data = skb_put(skb, fraglen + exthdrlen - pagedlen);
- skb_set_network_header(skb, exthdrlen);
+ data = skb_put(skb, fraglen - pagedlen);
+ skb_reset_network_header(skb);
skb->transport_header = (skb->network_header +
fragheaderlen);
- data += fragheaderlen + exthdrlen;
+ data += fragheaderlen;
if (fraggap) {
skb->csum = skb_copy_and_csum_bits(
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH net 2/2] net/ipv4: fail append when the moved fraggap exceeds the fragment data
2026-10-26 9:51 ` [PATCH net 2/2] net/ipv4: fail append when the moved fraggap exceeds the fragment data =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?=
@ 2026-10-26 12:20 ` Eric Dumazet
0 siblings, 0 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-10-26 12:20 UTC (permalink / raw)
To: tencentos_secure(TencentOS安全公共邮箱)
Cc: netdev, David Ahern, Ido Schimmel, Paolo Abeni, Jakub Kicinski,
Willem de Bruijn, Steffen Klassert, security
Le lun. 26 oct. 2026 à 12:23, tencentos_secure(TencentOS安全公共邮箱)
<tencentos_secure@tencent.com> a écrit :
>
> When the first fragment of a cork is already at maxfraglen, the next
> __ip_append_data() iteration relays the previous fragment's tail,
> fraggap = skb_prev->len - maxfraglen, into the new skb. The moved
> fraggap is charged against datalen by computing
>
> copy = datalen - transhdrlen - fraggap - pagedlen;
>
> but once datalen is capped at maxfraglen - fragheaderlen it can become
> smaller than transhdrlen + fraggap. Then copy goes negative: no payload
> is copied and the iteration makes no progress. Worse, in the paged
> branch (MSG_SPLICE_PAGES / MSG_ZEROCOPY / GSO) pagedlen = datalen -
> transhdrlen - fraggap additionally wraps unsigned, breaking the
> alloclen + pagedlen == fraglen invariant that commit eca856950f7c
> ("ipv4: account for fraggap on the paged allocation path") relies
> on; the loop then either queues skbs with garbage tail accounting
> (silent packet corruption) or, when maxfraglen == fragheaderlen,
> never makes progress and grinds on until -ENOBUFS.
>
> Reject such an append before any allocation: if the moved tail
> (transhdrlen + fraggap) cannot fit in the fragment's datalen, the
> iteration cannot make progress, so fail with -EINVAL. This covers all
> three alloclen branches, including the paged one. With the preceding
> fix fraggap is bounded by the alignment slop, but the condition stays
> reachable with extreme geometries (e.g. an effective mtu too small for
> fragheaderlen + transhdrlen), so pin the geometry explicitly, as
> __ip6_append_data() has done since commit 232cd35d0804 ("ipv6: fix
> out of bound writes in __ip6_append_data()").
>
I do not think this is reachable once patch 1 is applied.
ip_setup_cork() rejects cork->fragsize < 68 (inetdev_valid_mtu()), and
fragheaderlen is at most 60, so a capped datalen is at least 8, and
maxfraglen == fragheaderlen cannot happen.
transhdrlen is non zero only on the first iteration with an empty
queue, where fraggap is 0, and current callers pass at most 8 bytes
(UDP, ICMP, ping). On later iterations transhdrlen is 0 and, with
patch 1, fraggap <= mtu - maxfraglen <= 7.
So transhdrlen + fraggap > datalen can not be true.
Please give a concrete setup that reaches this check after patch 1,
or present the check as defense in depth matching
__ip6_append_data() and drop the Fixes: and Cc: stable tags.
> This cannot reject a send that would otherwise succeed: the guard only
> fires when transhdrlen + fraggap > datalen, which is exactly when copy
> would come out negative, or, on the paged branch, when pagedlen would
> wrap. No send can get past an iteration with a negative copy -- no
> payload is copied, so the loop makes no progress. All callers flush
> the cork on append error, and returning -EINVAL in mid-cork state
> already happens in this function (MSG_ZEROCOPY ubuf mismatch).
>
> Fixes: 353e5c9abd90 ("ipv4: Fix IPsec slowpath fragmentation problem")
> Reported-by: TencentOS Corvus AI <corvus@tencent.com>
> Cc: stable@vger.kernel.org
> Signed-off-by: Anakin Zhang <tencentos_secure@tencent.com>
> ---
> net/ipv4/ip_output.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
> index b84d56c..5db8b06 100644
> --- a/net/ipv4/ip_output.c
> +++ b/net/ipv4/ip_output.c
> @@ -1105,6 +1105,11 @@ alloc_new_skb:
> fraglen = datalen + fragheaderlen;
> pagedlen = 0;
>
> + if (unlikely(transhdrlen + fraggap > datalen)) {
> + err = -EINVAL;
> + goto error;
> + }
> +
> alloc_extra = hh_len + 15;
> alloc_extra += exthdrlen;
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net 1/2] net/ipv4: reserve exthdrlen as headroom instead of counting it in skb->len
2026-10-26 9:51 ` [PATCH net 1/2] net/ipv4: reserve exthdrlen as headroom instead of counting it in skb->len =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?=
@ 2026-10-27 15:26 ` Willem de Bruijn
0 siblings, 0 replies; 4+ messages in thread
From: Willem de Bruijn @ 2026-10-27 15:26 UTC (permalink / raw)
To: tencentos_secure(TencentOS安全公共邮箱),
netdev
Cc: David Ahern, Ido Schimmel, Paolo Abeni, Jakub Kicinski,
Willem de Bruijn, Steffen Klassert, Eric Dumazet, security,
steffen.klassert
tencentos_secure(TencentOS安全公共邮箱) wrote:
> When sending through an xfrm route (rt->dst.header_len > 0),
> __ip_append_data() counts the bundle header space ("exthdrlen")
> into skb->len of the first fragment skb: the skb setup does
>
> skb_reserve(skb, hh_len);
> data = skb_put(skb, fraglen + exthdrlen - pagedlen);
> skb_set_network_header(skb, exthdrlen);
>
> so a fully filled first skb has skb->len == maxfraglen + exthdrlen.
> Since commit 353e5c9abd90 ("ipv4: Fix IPsec slowpath fragmentation
> problem"), the next iteration's fraggap = skb_prev->len - maxfraglen
> therefore inherits the whole bundle header length instead of the
> intended alignment slop.
>
> fraggap is relayed into the next skb by skb_copy_and_csum_bits(), but
> the MSG_MORE (alloclen = mtu) and linear (alloclen = fraglen) branches
> account for fraggap in the allocation only for as long as datalen stays
> uncapped. Once datalen is capped at maxfraglen - fragheaderlen, the
> bytes copied into the fresh skb exceed the skb head allocation and run
> past the end of the buffer, corrupting skb_shared_info. The
> paged-branch accounting of
> commit eca856950f7c ("ipv4: account for fraggap on the paged
> allocation path") relies on datalen accounting for fraggap, which
> stops being true exactly when datalen is capped.
>
> Fix the root cause by reserving exthdrlen as headroom, like IPv6 has
> done since commit 1f85851e17b6 ("ipv6: fix incorrent ipv6 ipsec
> packet fragment") in 2012:
>
> skb_reserve(skb, hh_len + exthdrlen);
> data = skb_put(skb, fraglen - pagedlen);
> skb_reset_network_header(skb);
>
> With this, every fragment skb satisfies skb->len <= maxfraglen, so
> fraggap <= mtu - maxfraglen <= 7 (the alignment slop the relay was
> designed for) and the copy destination is provably in bounds.
>
> Allocation sizes, tail positions, tailroom and truesize are unchanged
> (alloc_extra already carries exthdrlen), and when dst.header_len == 0
> this is a literal no-op.
>
> Fixes: 353e5c9abd90 ("ipv4: Fix IPsec slowpath fragmentation problem")
> Reported-by: TencentOS Corvus AI <corvus@tencent.com>
> Cc: stable@vger.kernel.org
> Signed-off-by: Anakin Zhang <tencentos_secure@tencent.com>
LGTM on the general approach:
The XFRM inserted headers must only be inserted once on the first
fragment.
The same approach is already taken in IPv6. Though there the variable
is dst_exthdrlen, not to be confused with exthdrlen.
My main concern is that this code is notoriously fragile, with the
same variables used in different calculations / overlapping uses.
Writing past skb head is obviously bad. Ideally we could catch that
with a patch that is trivially reviewable for stable. Not sure that
exists. But I'm a little hesitant to send to stable something hard
to establish the correctness of.
I can definitely stare at it for longer to increase confidence.
Also Cc:ing the IPSec maintainer for an extra set of expert eyes.
> ---
> net/ipv4/ip_output.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
> index eaa6fab..b84d56c 100644
> --- a/net/ipv4/ip_output.c
> +++ b/net/ipv4/ip_output.c
> @@ -1150,16 +1150,16 @@ alloc_new_skb:
> */
> skb->ip_summed = csummode;
> skb->csum = 0;
> - skb_reserve(skb, hh_len);
> + skb_reserve(skb, hh_len + exthdrlen);
>
> /*
> * Find where to start putting bytes.
> */
> - data = skb_put(skb, fraglen + exthdrlen - pagedlen);
> - skb_set_network_header(skb, exthdrlen);
> + data = skb_put(skb, fraglen - pagedlen);
> + skb_reset_network_header(skb);
> skb->transport_header = (skb->network_header +
> fragheaderlen);
> - data += fragheaderlen + exthdrlen;
> + data += fragheaderlen;
>
> if (fraggap) {
> skb->csum = skb_copy_and_csum_bits(
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-27 15:26 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <v2-20261026-cover@tencent.com>
2026-10-26 9:51 ` [PATCH net 1/2] net/ipv4: reserve exthdrlen as headroom instead of counting it in skb->len =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?=
2026-10-27 15:26 ` Willem de Bruijn
2026-10-26 9:51 ` [PATCH net 2/2] net/ipv4: fail append when the moved fraggap exceeds the fragment data =?gb18030?B?dGVuY2VudG9zX3NlY3VyZShUZW5jZW50T1Owssiruau5stPKz+Qp?=
2026-10-26 12:20 ` Eric Dumazet
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox