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@vger.kernel.org Signed-off-by: Jérémy Jean --- 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