[PATCH ipsec v2 3/7] xfrm: esp: use the current sequence number for AAD and offload
From: Jérémy Jean
Date: Sun Nov 08 2026 - 09:28:11 EST
When a GSO packet is split in software, esp_xmit() and esp6_xmit()
advance xo->seq before software encryption or the handoff to the NIC.
If the 64-bit sequence number of the segment is (H, 0xffffffff), the
counter becomes (H+1, 0). Both the software ESN helpers and drivers
such as Chelsio then use H+1 instead of H as part of the associated
data that is authenticated.
However, the ESN 32 high bits are not present in the ESP header: the
receiver reconstructs the 64-bit value from the received 32 low bits
and its own replay state, and uses it to check cryptographic
integrity. It uses H for this packet at the boundary, so the GCM tag
computed by the sender with H+1 fails verification, as it is different
from the one obtained by the receiver using H.
Use the high bits saved in esp->seqno for the software AAD. For
hardware encryption, restore the current sequence after skb_ext_add()
makes the secpath private, leaving the shared counter advanced for
the remaining segments. Restore both halves because drivers can also
use xo->seq to generate the IV.
Fixes: 3dca3f38cfb8 ("xfrm: Separate ESP handling from segmentation for GRO packets.")
Fixes: 4b549ccce941 ("xfrm: replay: Fix ESN wrap around for GSO")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Jérémy Jean <Jeremy.Jean@xxxxxxxxxxxxxxxxx>
---
net/ipv4/esp4.c | 13 ++++---------
net/ipv4/esp4_offload.c | 4 ++++
net/ipv6/esp6.c | 13 ++++---------
net/ipv6/esp6_offload.c | 4 ++++
4 files changed, 16 insertions(+), 18 deletions(-)
diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
index 111bea0bd2601..df4857beca02c 100644
--- a/net/ipv4/esp4.c
+++ b/net/ipv4/esp4.c
@@ -271,20 +271,15 @@ static void esp_output_restore_header(struct sk_buff *skb)
static struct ip_esp_hdr *esp_output_set_extra(struct sk_buff *skb,
struct xfrm_state *x,
struct ip_esp_hdr *esph,
- struct esp_output_extra *extra)
+ struct esp_output_extra *extra,
+ __be64 seqno)
{
/* For ESN we move the header forward by 4 bytes to
* accommodate the high bits. We will move it back after
* encryption.
*/
if ((x->props.flags & XFRM_STATE_ESN)) {
- __u32 seqhi;
- struct xfrm_offload *xo = xfrm_offload(skb);
-
- if (xo)
- seqhi = xo->seq.hi;
- else
- seqhi = XFRM_SKB_CB(skb)->seq.output.hi;
+ __u32 seqhi = upper_32_bits(be64_to_cpu(seqno));
extra->esphoff = (unsigned char *)esph -
skb_transport_header(skb);
@@ -543,7 +538,7 @@ int esp_output_tail(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *
else
dsg = &sg[esp->nfrags];
- esph = esp_output_set_extra(skb, x, esp->esph, extra);
+ esph = esp_output_set_extra(skb, x, esp->esph, extra, esp->seqno);
esp->esph = esph;
sg_init_table(sg, esp->nfrags);
diff --git a/net/ipv4/esp4_offload.c b/net/ipv4/esp4_offload.c
index 3a0aafe991f47..6f6ce9ea5c491 100644
--- a/net/ipv4/esp4_offload.c
+++ b/net/ipv4/esp4_offload.c
@@ -358,6 +358,10 @@ static int esp_xmit(struct xfrm_state *x, struct sk_buff *skb, netdev_features_
if (!xo)
return -EINVAL;
+ /* Keep the current sequence number in the private copy. */
+ xo->seq.low = seq;
+ xo->seq.hi = upper_32_bits(be64_to_cpu(esp.seqno));
+
xo->flags |= XFRM_XMIT;
return 0;
}
diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c
index b287c807b0186..7509fff7f6682 100644
--- a/net/ipv6/esp6.c
+++ b/net/ipv6/esp6.c
@@ -308,20 +308,15 @@ static void esp_output_restore_header(struct sk_buff *skb)
static struct ip_esp_hdr *esp_output_set_esn(struct sk_buff *skb,
struct xfrm_state *x,
struct ip_esp_hdr *esph,
- struct esp_output_extra *extra)
+ struct esp_output_extra *extra,
+ __be64 seqno)
{
/* For ESN we move the header forward by 4 bytes to
* accommodate the high bits. We will move it back after
* encryption.
*/
if ((x->props.flags & XFRM_STATE_ESN)) {
- __u32 seqhi;
- struct xfrm_offload *xo = xfrm_offload(skb);
-
- if (xo)
- seqhi = xo->seq.hi;
- else
- seqhi = XFRM_SKB_CB(skb)->seq.output.hi;
+ __u32 seqhi = upper_32_bits(be64_to_cpu(seqno));
extra->esphoff = (unsigned char *)esph -
skb_transport_header(skb);
@@ -575,7 +570,7 @@ int esp6_output_tail(struct xfrm_state *x, struct sk_buff *skb, struct esp_info
else
dsg = &sg[esp->nfrags];
- esph = esp_output_set_esn(skb, x, esp->esph, extra);
+ esph = esp_output_set_esn(skb, x, esp->esph, extra, esp->seqno);
esp->esph = esph;
sg_init_table(sg, esp->nfrags);
diff --git a/net/ipv6/esp6_offload.c b/net/ipv6/esp6_offload.c
index 05d13cce22e47..682063bfe685f 100644
--- a/net/ipv6/esp6_offload.c
+++ b/net/ipv6/esp6_offload.c
@@ -379,6 +379,10 @@ static int esp6_xmit(struct xfrm_state *x, struct sk_buff *skb, netdev_features
if (!xo)
return -EINVAL;
+ /* Keep the current sequence number in the private copy. */
+ xo->seq.low = seq;
+ xo->seq.hi = upper_32_bits(be64_to_cpu(esp.seqno));
+
xo->flags |= XFRM_XMIT;
return 0;
}
--
2.47.3