__ip_append_data() allocates space for dst.header_len in the first skb,
but includes that space in skb->len until __ip_make_skb() pulls it.
Fragment accounting uses an MTU that already excludes the XFRM headers.
As a result, the next skb can have a fraggap larger than its capped
datalen, and skb_copy_and_csum_bits() can overwrite its shared info.
For example, five AH tunnel states can reserve 500 bytes while leaving
an effective MTU of 348. With 40 bytes of IPv4 options, the second skb
has room for 288 bytes of fragment data but receives a 500-byte gap
copy. The overwritten shared info is subsequently consumed by the skb
release paths.
Reserve the XFRM headers together with the link-layer headroom instead.
This keeps skb->len, the gap-copy offsets and the trim lengths relative
to the IPv4 packet throughout corking. The network and transport header
addresses and the headroom available to XFRM at output are unchanged.
Both paged and linear allocations, including subsequent corked appends,
use the corrected accounting.
Signed-off-by: Kyle Zeng <redacted>
---
I have a reproducer for this issue and can share via DM
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 3396f177a18d..d42d776af273 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -1150,16 +1150,16 @@ static int __ip_append_data(struct sock *sk,
*/
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(
base-commit: d89e1d186ec9271bf78e62771779e9fc03bf0fc3
--
2.56.0