* [PATCH net v2 1/2] net: gro: do not undo remote checksum offload on merged packets
2026-10-30 12:59 [PATCH net v2 0/2] net: gro: fix frag0 and remcsum handling Eric Dumazet
@ 2026-10-30 12:59 ` Eric Dumazet
2026-10-30 12:59 ` [PATCH net v2 2/2] net: gro: invalidate frag0 after a pull Eric Dumazet
2026-10-30 13:04 ` [PATCH net v2 0/2] net: gro: fix frag0 and remcsum handling netdev-bot+sinfo
2 siblings, 0 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-10-30 12:59 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, edumazet, Willem de Bruijn,
Steffen Klassert, Tom Herbert, Cen Zhang, AutonomousCodeSecurity,
stable
skb_gro_flush_final_remcsum() calls skb_gro_remcsum_cleanup() to undo
the checksum adjustment done by skb_gro_remcsum_process() when the
packet was not consumed by GRO.
When the inner handler merged the packet (NAPI_GRO_CB(skb)->same_flow
is set), grc->offset can no longer be trusted:
- skb_gro_receive() either gives the frags or the head of the skb
to the held packet, or uses __skb_pull(skb, offset) before adding
the skb to the frag_list of the held packet.
- skb_gro_receive_list() uses skb_pull(skb, skb_gro_offset(skb)).
grc->offset is relative to the original skb->data, so the 2-byte
read-modify-write done by skb_gro_remcsum_cleanup() can land in
the payload of a packet which now belongs to the GRO packet.
pskb_may_pull() can also be called on this frag_list member.
With fraglist GRO, this can also be a use-after-free:
pskb_may_pull(skb, skb_gro_offset(skb)) in skb_gro_receive_list()
or udp_gro_receive_segment() releases the first page fragment if it
only contains headers. NAPI_GRO_CB(skb)->frag0 and frag0_len are not
updated, so skb_gro_remcsum_cleanup() takes the fast path and writes
through frag0 into a page the skb no longer holds a reference on.
The non merged case, where frag0 is used after this pull, is handled
in the following patch.
Skipping the cleanup for a merged skb is correct:
- With skb_gro_receive(), the skb is freed, or its headers are
rebuilt from the head skb in skb_segment().
- skb_segment_list() only copies the link-layer and tunnel headers
from the head skb, so fraglist members keep their own inner headers.
grc->delta is only set with VXLAN_F_REMCSUM_NOPARTIAL, where
skb_remcsum_process() writes the same adjusted checksum in the non
GRO path. Keeping it is the intended result.
This affects VXLAN and GUE with remote checksum offload enabled.
Fixes: 26c4f7da3e41 ("net: Fix remcsum in GRO path to not change packet")
Cc: stable@vger.kernel.org
Tested-by: Cen Zhang <cenzhang@linux.microsoft.com>
Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
include/net/gro.h | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/include/net/gro.h b/include/net/gro.h
index 2300b6da05b2..00b49b6b9e74 100644
--- a/include/net/gro.h
+++ b/include/net/gro.h
@@ -351,7 +351,13 @@ static inline void skb_gro_remcsum_cleanup(struct sk_buff *skb,
void *ptr;
size_t plen = grc->offset + sizeof(u16);
- if (!grc->delta)
+ /* Once merged, grc->offset can not be trusted: the frags or head
+ * of this skb now belong to the GRO packet, or skb->data was pulled
+ * past the headers before adding it to a frag_list.
+ * Fraglist members keep their adjusted inner checksum, which is the
+ * value skb_remcsum_process() would have written.
+ */
+ if (!grc->delta || NAPI_GRO_CB(skb)->same_flow)
return;
ptr = skb_gro_header(skb, plen, grc->offset);
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH net v2 2/2] net: gro: invalidate frag0 after a pull
2026-10-30 12:59 [PATCH net v2 0/2] net: gro: fix frag0 and remcsum handling Eric Dumazet
2026-10-30 12:59 ` [PATCH net v2 1/2] net: gro: do not undo remote checksum offload on merged packets Eric Dumazet
@ 2026-10-30 12:59 ` Eric Dumazet
2026-10-30 13:04 ` [PATCH net v2 0/2] net: gro: fix frag0 and remcsum handling netdev-bot+sinfo
2 siblings, 0 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-10-30 12:59 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, edumazet, Willem de Bruijn,
Steffen Klassert, Tom Herbert, Cen Zhang, AutonomousCodeSecurity,
stable, Taesoo Kim
Since commit bd56a29c7a4e ("net: gro: change skb_gro_network_header()"),
skb_gro_header_slow() no longer invalidates NAPI_GRO_CB(skb)->frag0
after pskb_may_pull(). skb_gro_may_pull() keeps comparing against the
frag0_len value set in skb_gro_reset_offset(), assuming that after a
slow pull, all later accesses are beyond frag0_len.
This is not true:
- udp4_gro_receive() and udp6_gro_receive() read the network header
with skb_gro_network_header() after udp_gro_udphdr() pulled the
UDP header.
- vxlan_gro_receive() calls skb_gro_remcsum_cleanup() at a lower
offset after the inner handlers pulled their headers.
- skb_gro_remcsum_process() can pull beyond the inner Ethernet header
before eth_gro_receive() reads it.
- napi_frags_skb() calls __skb_pull(skb, ETH_HLEN) after a slow pull,
so frag0 still points to the Ethernet header.
If the pull consumed the first page fragment, __skb_pull_tail() released
it. If the head had to grow, pskb_expand_head() freed the old one.
These reads, and the 2-byte write in skb_gro_remcsum_cleanup(), then
access memory the skb no longer holds a reference on.
esp4_gro_receive(), esp6_gro_receive(), skb_gro_receive_list() and
udp_gro_receive_segment() also pull directly and never invalidated
frag0.
Add skb_gro_frag0_invalidate(), which only clears frag0_len, and call
it before each of these pulls. This is a single store in the slow path,
and frag0 stays non NULL, so skb_gro_network_header() does not need
a NULL test.
frag0 must be invalidated even if the pull fails: __pskb_pull_tail()
can return NULL after pskb_expand_head() freed the old head, for
instance if a frag_list member has to be cloned or pulled.
Fixes: bd56a29c7a4e ("net: gro: change skb_gro_network_header()")
Fixes: 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.")
Cc: stable@vger.kernel.org
Reported-by: Taesoo Kim <tsgatesv@gmail.com>
Reported-by: Cen Zhang <cenzhang@linux.microsoft.com>
Tested-by: Cen Zhang <cenzhang@linux.microsoft.com>
Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
include/net/gro.h | 10 ++++++++++
net/core/gro.c | 1 +
net/ipv4/esp4_offload.c | 1 +
net/ipv4/udp_offload.c | 1 +
net/ipv6/esp6_offload.c | 1 +
5 files changed, 14 insertions(+)
diff --git a/include/net/gro.h b/include/net/gro.h
index 00b49b6b9e74..6f6affc8ab91 100644
--- a/include/net/gro.h
+++ b/include/net/gro.h
@@ -160,9 +160,19 @@ static inline bool skb_gro_may_pull(const struct sk_buff *skb,
return likely(hlen <= NAPI_GRO_CB(skb)->frag0_len);
}
+/* A pull can release the page fragment frag0 points to, or reallocate
+ * skb->head, even if it fails. Call this before the pull, to make sure later
+ * skb_gro_header() and skb_gro_network_header() calls no longer use frag0.
+ */
+static inline void skb_gro_frag0_invalidate(struct sk_buff *skb)
+{
+ NAPI_GRO_CB(skb)->frag0_len = 0;
+}
+
static inline void *skb_gro_header_slow(struct sk_buff *skb, unsigned int hlen,
unsigned int offset)
{
+ skb_gro_frag0_invalidate(skb);
if (!pskb_may_pull(skb, hlen))
return NULL;
diff --git a/net/core/gro.c b/net/core/gro.c
index 1b033855a20d..bc88652e353a 100644
--- a/net/core/gro.c
+++ b/net/core/gro.c
@@ -238,6 +238,7 @@ int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb)
NAPI_GRO_CB(skb)->flush))
return -E2BIG;
+ skb_gro_frag0_invalidate(skb);
if (!pskb_may_pull(skb, skb_gro_offset(skb))) {
NAPI_GRO_CB(skb)->flush = 1;
return -ENOMEM;
diff --git a/net/ipv4/esp4_offload.c b/net/ipv4/esp4_offload.c
index abd77162f5e7..2309468cfc3d 100644
--- a/net/ipv4/esp4_offload.c
+++ b/net/ipv4/esp4_offload.c
@@ -37,6 +37,7 @@ static struct sk_buff *esp4_gro_receive(struct list_head *head,
__be32 seq;
__be32 spi;
+ skb_gro_frag0_invalidate(skb);
if (!pskb_pull(skb, offset))
return NULL;
diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index cf07c3c6611a..cd9d7426b227 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -743,6 +743,7 @@ static struct sk_buff *udp_gro_receive_segment(struct list_head *head,
pp = p;
} else {
if (NAPI_GRO_CB(skb)->is_flist) {
+ skb_gro_frag0_invalidate(skb);
if (!pskb_may_pull(skb, skb_gro_offset(skb))) {
NAPI_GRO_CB(skb)->flush = 1;
return NULL;
diff --git a/net/ipv6/esp6_offload.c b/net/ipv6/esp6_offload.c
index 22895521a57d..ca947cb94fb7 100644
--- a/net/ipv6/esp6_offload.c
+++ b/net/ipv6/esp6_offload.c
@@ -64,6 +64,7 @@ static struct sk_buff *esp6_gro_receive(struct list_head *head,
if (NAPI_GRO_CB(skb)->proto == IPPROTO_UDP)
encap_type = UDP_ENCAP_ESPINUDP;
+ skb_gro_frag0_invalidate(skb);
if (!pskb_pull(skb, offset))
return NULL;
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH net v2 0/2] net: gro: fix frag0 and remcsum handling
2026-10-30 12:59 [PATCH net v2 0/2] net: gro: fix frag0 and remcsum handling Eric Dumazet
2026-10-30 12:59 ` [PATCH net v2 1/2] net: gro: do not undo remote checksum offload on merged packets Eric Dumazet
2026-10-30 12:59 ` [PATCH net v2 2/2] net: gro: invalidate frag0 after a pull Eric Dumazet
@ 2026-10-30 13:04 ` netdev-bot+sinfo
2 siblings, 0 replies; 4+ messages in thread
From: netdev-bot+sinfo @ 2026-10-30 13:04 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
netdev, Willem de Bruijn, Steffen Klassert, Tom Herbert,
Cen Zhang, AutonomousCodeSecurity
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 4+ messages in thread