From: Usman Malik <usmalik90@gmail.com>
To: netdev-bot+sashiko@kernel.org
Cc: steffen.klassert@secunet.com, herbert@gondor.apana.org.au,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, cratiu@nvidia.com, leon@kernel.org,
stable@vger.kernel.org
Subject: Re: [PATCH ipsec] xfrm: use software GSO for inner tunnels without crypto offload
Date: Thu, 12 Nov 2026 11:05:38 +0100 [thread overview]
Message-ID: <33CCA7F8-9B0F-42F8-999B-32E9AF5826D1@gmail.com> (raw)
In-Reply-To: <179354432643.3131.245171497251536441@kernel.org>
On Sun, 1 Nov 2026 14:45:27 +0000, Sashiko AI review wrote:
> [Severity: Critical]
> Can this cause AES-GCM IV reuse for the transport-mode software SA
> traffic that this change sends to xfrm_output_gso()?
Thanks for the review.
The concern is right, and it is not new. Packets with
skb->inner_protocol set, such as vxlan, geneve or ip6tnl, over a
tunnel-mode SA already go through xfrm_output_gso() when an ESP GSO
handler is registered, and with software crypto they hit it today.
Jeremy Jean posted the fix as patch 4/7 of "xfrm: fix ESP IV generation
and ESN authentication".
https://lore.kernel.org/netdev/20260930144523.435271-6-Jeremy.Jean@oss.cyber.gouv.fr/
It calls secpath_set() on each segment before xfrm_output2(), so every
segment keeps its own xo->seq until esp_xmit() or esp6_xmit() reads it.
This patch moves the same packets over a transport-mode SA without an
offload device onto that path, so it must not be applied before that
fix.
Teaching xfrm{4,6}_transport_gso_segment() an inner IP tunnel would add
new functionality. Before 0aae2867aa60 these packets never reached the
ESP GSO handlers with software crypto, and the transport handlers do not
produce a valid ESP packet for them. Restoring the software GSO for SAs
without an offload device is the smaller change, and the one stable can
take.
I tested this on v7.3 with a tbf qdisc on the egress veth, so the
segments wait in the queue before validate_xmit_skb(). The traffic was
TCP through an ip6tnl tunnel over an rfc4106(gcm(aes)) SA, with
esp6_offload loaded, 3 MB per run and 3 runs per case. All kernels carry
patch 1/7 of the series, so the esp6 IV off-by-one does not mix into the
counts. The sender capture was decrypted with the SA keys.
kernel SA mode IV values reused per run
v7.3 + 1/7 tunnel 50, 50, 51
v7.3 + 1/7 + this patch tunnel 92, 51, 51
v7.3 + 1/7 + this patch transport 50, 51, 50
v7.3 + 1/7 + 4/7 + this tunnel 0, 0, 0
v7.3 + 1/7 + 4/7 + this transport 0, 0, 0
There is no transport-mode row without this patch, because those packets
take the SKB_GSO_ESP path and arrive malformed, which is the bug this
patch fixes. Each reused IV value is shared by up to 7 segments of one
GSO packet, and for one pair under each value ct1 XOR ct2 equals pt1 XOR
pt2, so the keystream is shared. Without the qdisc every case is 0.
Plain TCP over either mode goes through esp6_gso_segment() and is 0 in
every case. v2 will state the dependency on that fix and carry this
test. I will post it once the series is in the ipsec tree, rebased on it
and tested again on that base.
> [Severity: Medium]
> This isn't a bug introduced by this patch, but transport-mode SAs with
> a crypto offload device (x->xso.dev != NULL) still set SKB_GSO_ESP here
> for packets with skb->inner_protocol set.
Agreed. When the hw_enc_features of the device do not cover the whole
gso_type, validate_xmit_skb() segments the packet through
esp{4,6}_gso_segment() and xfrm{4,6}_transport_gso_segment() with the
same transport header. v2 will name that path in the changelog. The fix
for it belongs in the transport GSO handlers, as a separate change.
> [Severity: Low]
> This is a pre-existing issue, but x->xso.dev is read here with a plain
> load, and xfrm_dev_state_free() can clear it at the same time:
Pre-existing, and the new test only uses the value as a boolean. I would
leave a READ_ONCE() conversion of x->xso.dev to a separate cleanup that
also covers xfrm_dev_offload_ok() and the NETIF_F_HW_ESP_TX_CSUM check.
pw-bot: cr
Usman
> On 1. Nov 2026, at 15:45, netdev-bot+sashiko@kernel.org wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider.
>
> Critical: 1 · High: 0 · Medium: 0 (1) · Low: 0 (1)
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [Critical] The patch sends more traffic into xfrm_output_gso(), and on
> that path AES-GCM IVs can repeat.
>
> Pre-existing issues:
> - [Medium] This problem wasn't introduced by this patch, but transport-
> mode SAs that have a crypto offload device (x->xso.dev != NULL) still…
> - [Low] This problem wasn't introduced by this patch, but xfrm_output()
> reads x->xso.dev with plain, unmarked loads: in xfrm_dev_offload_ok()…
>
> --
>
> xfrm: use software GSO for inner tunnels without crypto offload
>
> This commit changes xfrm_output() to use xfrm_output_gso() for GSO
> packets with skb->inner_protocol set. Before, that only happened in
> tunnel mode. Now it also happens when the SA has no offload device. The
> goal is to stop the SKB_GSO_ESP path from producing malformed ESP packets
> for ipip/ip6tnl traffic over transport-mode software SAs when
> esp4_offload/esp6_offload are loaded.
>
>> diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c
>> index e305ba32e356b..c370d28a83f50 100644
>> --- a/net/xfrm/xfrm_output.c
>> +++ b/net/xfrm/xfrm_output.c
>> @@ -808,7 +808,8 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb)
>> skb->encapsulation = 1;
>>
>> if (skb_is_gso(skb)) {
>> - if (skb->inner_protocol && x->props.mode == XFRM_MODE_TUNNEL)
>> + if (skb->inner_protocol &&
>> + (x->props.mode == XFRM_MODE_TUNNEL || !x->xso.dev))
>> return xfrm_output_gso(net, sk, skb);
>
> [Severity: Critical]
> Can this cause AES-GCM IV reuse for the transport-mode software SA
> traffic that this change sends to xfrm_output_gso()?
>
> Before this point, xfrm_output() has attached a sec_path with
> sp->olen = 1, so the skb carries an xfrm_offload. xfrm_output_gso() then
> calls skb_gso_segment(). In skb_segment()->__copy_skb_header(),
> __skb_ext_copy() only takes a reference on the extension:
>
> include/linux/skbuff.h:__skb_ext_copy() {
> ...
> refcount_inc(&ext->refcnt);
> dst->extensions = ext;
> ...
> }
>
> So all segments seem to share one struct xfrm_offload.
>
> For each segment, xfrm_output2()->...->xfrm_replay_overflow_offload()
> writes to that shared xo:
>
> net/xfrm/xfrm_replay.c:xfrm_replay_overflow_offload() {
> ...
> if (!skb_is_gso(skb)) {
> XFRM_SKB_CB(skb)->seq.output.low = ++oseq;
> xo->seq.low = oseq;
> ...
> }
>
> The XFRM_SKB_CB() copy belongs to each skb, but xo->seq.low is in the
> shared extension.
>
> Encryption happens later, at qdisc dequeue time, in
> validate_xmit_skb()->validate_xmit_xfrm()->esp_xmit(). That code reads
> the shared value:
>
> net/ipv4/esp4_offload.c:esp_xmit() {
> ...
> seq = xo->seq.low;
> ...
> esp.seqno = cpu_to_be64(seq + ((u64)xo->seq.hi << 32));
> ...
> }
>
> XFRM_GSO_SEGMENT is not set on this path, so nothing advances
> xo->seq.low per segment. esp_output_tail() then copies esp->seqno into
> the AEAD IV. esp6_xmit() does the same.
>
> Segments can wait in the qdisc while the xfrm_output_gso() loop keeps
> running. For example, dequeue_skb() returns nothing while a
> TCQ_F_ONETXQUEUE txq is stopped by BQL or a full ring, or another CPU
> may already be running the qdisc. In that case, won't later segments
> overwrite xo->seq.low before the earlier ones are encrypted?
>
> Each packet would still have a distinct esph->seq_no on the wire, but
> the explicit IVs would be the same. That is GCM nonce reuse under one
> key. The receiver takes the IV from the packet, so decryption still
> succeeds and nothing reports the problem.
>
> veth is noqueue, so in the test setup validate_xmit_skb() runs before
> the next segment's xfrm_output_one(). That would hide this.
>
> Tunnel-mode software SAs already use this mechanism today. In the base
> tree, however, these transport-mode packets, including the vxlan and
> geneve traffic that worked, went down the SKB_GSO_ESP path. There,
> xo->seq.low advances per segment within one validate_xmit_xfrm() call,
> on an extension owned by a single GSO skb.
>
> Could the sec_path be unshared per segment, or the IV be taken from the
> per-skb XFRM_SKB_CB() sequence? Another option would be to fix
> xfrm{4,6}_transport_gso_segment() instead of rerouting these packets.
>
> [Severity: Low]
> This is a pre-existing issue, but x->xso.dev is read here with a plain
> load, and xfrm_dev_state_free() can clear it at the same time:
>
> net/xfrm/xfrm_state.c:xfrm_dev_state_free() {
> ...
> WRITE_ONCE(xso->dev, NULL);
> xso->type = XFRM_DEV_OFFLOAD_UNSPECIFIED;
> netdev_put(dev, &xso->dev_tracker);
> ...
> }
>
> The new !x->xso.dev test is only used as a boolean, and either value
> leads to a valid path. At worst it is a data race that KCSAN can see,
> like the one already in xfrm_dev_offload_ok():
>
> struct net_device *dev = x->xso.dev;
>
> Further down in xfrm_output(), the check-then-dereference also uses two
> plain loads. This patch does not touch it:
>
> if (x->xso.dev && x->xso.dev->features & NETIF_F_HW_ESP_TX_CSUM)
>
> Would it make sense to read x->xso.dev once with READ_ONCE() into a
> local variable and use that for these checks?
>
>>
>> skb_shinfo(skb)->gso_type |= SKB_GSO_ESP;
>
> [Severity: Medium]
> This isn't a bug introduced by this patch, but transport-mode SAs with
> a crypto offload device (x->xso.dev != NULL) still set SKB_GSO_ESP here
> for packets with skb->inner_protocol set.
>
> The commit message says:
>
> "An SA with crypto offload that falls back to software
> segmentation in validate_xmit_xfrm() takes the same path."
>
> Is the reroute/overflow fallback in validate_xmit_xfrm() the only way to
> get there? validate_xmit_skb() also does this:
>
> net/core/dev.c:validate_xmit_skb() {
> ...
> if (netif_needs_gso(skb, features)) {
> struct sk_buff *segs;
>
> segs = skb_gso_segment(skb, features);
> ...
> }
>
> That runs whenever the device's hw_enc_features don't cover the whole
> gso_type, for example SKB_GSO_ESP | SKB_GSO_IPXIP4. The call chain would
> then be:
>
> skb_gso_segment()
> esp4_gso_segment()
> xfrm4_transport_gso_segment()
> inet_gso_segment() /* inner IPIP, transport header at inner L4 */
>
> followed by validate_xmit_xfrm()->__xfrm_transport_prep()->esp_xmit().
> That writes the ESP header 4 bytes into the inner IP header, which is
> the same malformed layout the commit message describes.
> xfrm6_transport_gso_segment() has the same limitation.
>
> Could the commit message mention this fallback as well?
>
> --
> Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261028-xfrm-inner-gso-v1-1-1908f0eb7313%40engineer.com
prev parent reply other threads:[~2026-11-12 10:05 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 14:10 Usman Malik via B4 Relay
2026-11-01 14:45 ` netdev-bot+sashiko
2026-11-12 10:05 ` Usman Malik [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=33CCA7F8-9B0F-42F8-999B-32E9AF5826D1@gmail.com \
--to=usmalik90@gmail.com \
--cc=cratiu@nvidia.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=herbert@gondor.apana.org.au \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=leon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev-bot+sashiko@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
--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
all inboxes | Powered by JetHome®