From: "Jérémy Jean" <jeremy.jean@oss.cyber.gouv.fr>
To: Sabrina Dubroca <sd@queasysnail.net>
Cc: Steffen Klassert <steffen.klassert@secunet.com>,
Herbert Xu <herbert@gondor.apana.org.au>,
"David S. Miller" <davem@davemloft.net>,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] xfrm: esp6: fix off-by-one IV counter causing AES-GCM nonce reuse
Date: Tue, 29 Sep 2026 15:13:44 +0200 [thread overview]
Message-ID: <a0d39c52b6169ce5e00869abbe2162dd@oss.cyber.gouv.fr> (raw)
In-Reply-To: <aru2Y-gyrgcZsyUD@krikkit>
On 2026-09-29 15:00, Sabrina Dubroca wrote:
> 2026-09-29, 11:47:47 +0200, Jérémy Jean wrote:
>> Hello Sabrina,
>>
>> On 2026-09-29 01:48, Sabrina Dubroca wrote:
>> > 2026-09-28, 21:33:41 +0200, Jérémy Jean wrote:
>> > > On 2026-09-28 18:19, Sabrina Dubroca wrote:
>> > > > 2026-09-25, 09:51:06 +0000, Jérémy Jean wrote:
>> > > > > An off-by-one error in esp6_xmit() advances the IV counter before
>> > > > > encrypting each software-GSO segment. For N segments with sequence
>> > > > > numbers X through X+N-1, the IV counters are therefore X+1 through
>> > > > > X+N.
>> > > > > The following non-GSO packet uses X+N for both its sequence number and
>> > > > > IV counter, repeating the last segment's AES-GCM nonce under the same
>> > > > > key.
>> > > >
>> > > > I find this description very unclear. All I'm managing to understand
>> > > > from this is "there's some situation where a packet isn't getting the
>> > > > seqno it should". I don't know where the "+1" comes from since for GSO
>> > > > the function does +N (xo->seq.low += skb_shinfo(skb)->gso_segs).
>> > >
>> > > I tried to be as explicit as possible, but I apologize if it was not
>> > > good enough. My understanding on the full GSO processing isn't as
>> > > deep as yours, so here is another try at explaining.
>> > >
>> > > The bug happens after software segmentation in GSO. When a large
>> > > amount of data needs to span across several packets, software
>> > > segmentation splits it into N smaller skb. After this split, each
>> > > smaller skb holding the individual packets has skb_is_gso(skb)
>> > > returning false, yet each skb keeps the flag XFRM_GSO_SEGMENT stating
>> > > that this skb resulted from a segmentation. Consequently, the skb
>> > > goes through the increment below in esp6_xmit():
>> > >
>> > > net/ipv6/esp6_offload.c:
>> > > 355 if (xo->flags & XFRM_GSO_SEGMENT) {
>> > > 356 esp.esph->seq_no = htonl(seq);
>> > > 357
>> > > 358 if (!skb_is_gso(skb))
>> > > 359 xo->seq.low++; // <<< increment here
>> > > 360 else
>> > > 361 xo->seq.low += skb_shinfo(skb)->gso_segs;
>> > > 362 }
>> > >
>> > > There are N calls to esp6_xmit() for all the smaller packets, and for
>> > > each of them, the current sequence number is first written into the
>> > > header, and then the shared counter for the next packet is
>> > > incremented. However, the value esp.seqno used to construct the IV is
>> > > derived from the counter value _after_ the increment.
>> >
>> > Ok, I see now. One call to validate_xmit_xfrm() that calls
>> > skb_gso_segment() and feeds those N non-GSO skbs to ->xmit one by one.
>>
>> Yes, precisely.
>>
>> > In that case, the seqno used in the IV wouldn't match the one that the
>> > peer will reconstruct using the bottom 32b of seqno present in the
>> > header, and it would never manage to decrypt anything we sent.
>>
>> I don't think the peer uses its internal counter to reconstruct the
>> IV?
>
> For some reason when looking at this last night I thought it was
> rebuilding it from the seqno in the esp header.
>
>> The IV is part of the GCM ciphertext and the peer uses that value it
>> received to decrypt the payload. AFAICT, the decryption is ultimately
>> done
>> in seqiv_aead_decrypt(), where one can see the IV copy (121), and the
>> actual decryption call (123).
>>
>> crypto/seqiv.c:
>> 99 static int seqiv_aead_decrypt(struct aead_request *req)
>> 100 {
>> // ...
>> 116 aead_request_set_callback(subreq, req->base.flags, compl,
>> data);
>> 117 aead_request_set_crypt(subreq, req->src, req->dst,
>> 118 req->cryptlen - ivsize, req->iv);
>> 119 aead_request_set_ad(subreq, req->assoclen + ivsize);
>> 120
>> 121 scatterwalk_map_and_copy(req->iv, req->src, req->assoclen,
>> ivsize,
>> 0);
>> 122
>> 123 return crypto_aead_decrypt(subreq);
>> 124 }
>>
>> > But luckily, it seems commenting out the memcpy(iv, seqno) line has no
>> > effect (I think that's because all algorithms rely on either seqiv or
>> > echainiv).
>>
>> I may misunderstand, but which memcpy() do you refer to ?
>> If you do not memcpy, then IV is always null, and the nonce reuse is
>> even
>> worse, no?
>
> Yeah right. I thought there was something dodgy there.
>
>> > > For example, if
>> > > the last packet produced by segmentation has sequence number 100, the
>> > > IV is constructed using counter value 101. Then, a subsequent
>> > > ordinary packet not going through segmentation is allocated sequence
>> > > number 101, yet since it does not have the flag XFRM_GSO_SEGMENT,
>> > > there is no increment, and its value is constructed from value 101 as
>> > > well. Hence the nonce repetition.
>> >
>> > I don't think that happens? The other packet will go through ->xmit
>> > too and use the wrong seqno too.
>>
>> Which "wrong seqno"? The other packet will indeed, go through ->xmit,
>> but its lack of XFRM_GSO_SEGMENT will make it skip the increment and
>> reuse the previous counter value.
>
> Eh, ok. I thought you were saying one goes through ->xmit (with the
> wrong seqno because it has been incremented) and the other through
> ->output.
>
> Then I guess this makes sense.
Good that we agree then, thanks :)
> Could you:
>
> 1. fix both bugs you found so that the code looks similar, probably
> bundled as a small series (this stuff is not ipv*-specific, so
> there's no reason for it to be implemented differently)
>
> 2. rewrite the commit messages based on this thread to be much more
> precise and also less verbose
Yes, I will try do this shortly.
> I'd also reduce the amount of crypto detail about GCM, I don't think
> it's super relevant. Or move it to a cover letter.
I purposedly emphasized the crypto part about GCM to make it clear to
non-crypto people that the nonce reuse impact is catastrophic (all zero
or
even a single reuse). I will move it to the cover letter then.
Regards,
Jérémy
prev parent reply other threads:[~2026-09-29 13:13 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-25 9:51 [PATCH] xfrm: esp6: fix off-by-one IV counter causing AES-GCM nonce reuse Jérémy Jean
2026-09-28 16:19 ` Sabrina Dubroca
2026-09-28 19:33 ` Jérémy Jean
2026-09-28 23:48 ` Sabrina Dubroca
2026-09-29 9:47 ` Jérémy Jean
2026-09-29 13:00 ` Sabrina Dubroca
2026-09-29 13:13 ` Jérémy Jean [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=a0d39c52b6169ce5e00869abbe2162dd@oss.cyber.gouv.fr \
--to=jeremy.jean@oss.cyber.gouv.fr \
--cc=davem@davemloft.net \
--cc=herbert@gondor.apana.org.au \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=sd@queasysnail.net \
--cc=steffen.klassert@secunet.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox