* [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
@ 2026-06-03 6:46 Petr Wozniak
2026-06-04 11:29 ` Sabrina Dubroca
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Petr Wozniak @ 2026-06-03 6:46 UTC (permalink / raw)
To: netdev; +Cc: sd, steffen.klassert, pabeni, edumazet, Petr Wozniak
validate_xmit_xfrm() returns NULL both when a packet is dropped and
when it is stolen by async crypto (-EINPROGRESS from ->xmit()).
Callers cannot distinguish the two cases.
f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
changed the semantics of a NULL return from "dropped" to "stolen or
dropped", but __dev_queue_xmit() was not updated. On virtual/bridge
interfaces (noqueue qdisc) __dev_queue_xmit() initialises rc=-ENOMEM
and jumps to out: when skb is NULL, returning -ENOMEM to the caller
even though the packet will be delivered correctly via xfrm_dev_resume().
Return ERR_PTR(-EINPROGRESS) from validate_xmit_xfrm() for the async
case so callers can tell it apart from a real drop. Update
__dev_queue_xmit() to handle ERR_PTR(-EINPROGRESS) from
validate_xmit_skb() correctly. Update validate_xmit_skb_list() to
use IS_ERR_OR_NULL() so that ERR_PTR(-EINPROGRESS) is not mistakenly
added to the transmitted list.
Fixes: f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
Suggested-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Petr Wozniak <petr.wozniak@gmail.com>
---
Changes in v4:
- Drop bool stolen tracking in validate_xmit_skb_list() and ERR_PTR
return from that function; use IS_ERR_OR_NULL() in the loop so
stolen skbs are silently skipped (same as the pre-patch NULL behaviour)
(Sabrina Dubroca)
- Drop ERR_PTR(-EINPROGRESS) handling in __dev_direct_xmit(); callers
cannot reach the -EINPROGRESS path of validate_xmit_xfrm()
(Sabrina Dubroca)
- Move validate_xmit_skb() return-value comment above the function
instead of inline in __dev_queue_xmit() (Sabrina Dubroca)
Changes in v3:
- Fix validate_xmit_skb_list(): set stolen=true only for -EINPROGRESS,
not for all IS_ERR() cases (Sabrina Dubroca)
- Update __dev_direct_xmit() to handle ERR_PTR(-EINPROGRESS) from
validate_xmit_skb_list() before kfree_skb_list() which would crash
on an ERR_PTR (Sabrina Dubroca / sashiko)
Changes in v2:
- Only reset rc to NET_XMIT_SUCCESS when PTR_ERR(skb) == -EINPROGRESS,
not for any IS_ERR() result (Sabrina Dubroca)
- Add comment explaining why rc is reset and the async delivery path
- Fix validate_xmit_skb_list(): track stolen skbs and return
ERR_PTR(-EINPROGRESS) when all skbs in the list were stolen by
async crypto, not NULL (Sabrina Dubroca)
---
net/core/dev.c | 11 ++++++++---
net/xfrm/xfrm_device.c | 2 +-
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index 0c6c270d9..18e92bef7 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4016,6 +4016,9 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
return NULL;
}
+/* Returns the skb on success, NULL if dropped, or ERR_PTR(-EINPROGRESS)
+ * if stolen by async xfrm crypto (delivered via xfrm_dev_resume()).
+ */
static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)
{
netdev_features_t features;
@@ -4078,7 +4081,6 @@ static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device
struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *dev, bool *again)
{
struct sk_buff *next, *head = NULL, *tail;
-
for (; skb != NULL; skb = next) {
next = skb->next;
skb_mark_not_on_list(skb);
@@ -4087,7 +4089,7 @@ struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *d
skb->prev = skb;
skb = validate_xmit_skb(skb, dev, again);
- if (!skb)
+ if (IS_ERR_OR_NULL(skb))
continue;
if (!head)
@@ -4858,8 +4860,11 @@ int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)
goto recursion_alert;
skb = validate_xmit_skb(skb, dev, &again);
- if (!skb)
+ if (IS_ERR_OR_NULL(skb)) {
+ if (PTR_ERR(skb) == -EINPROGRESS)
+ rc = NET_XMIT_SUCCESS;
goto out;
+ }
HARD_TX_LOCK(dev, txq, cpu);
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 550457e4c..bb0c0fafa 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -182,7 +182,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
err = x->type_offload->xmit(x, skb, esp_features);
if (err) {
if (err == -EINPROGRESS)
- return NULL;
+ return ERR_PTR(-EINPROGRESS);
XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR);
kfree_skb(skb);
--
2.51.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
2026-06-03 6:46 [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
@ 2026-06-04 11:29 ` Sabrina Dubroca
2026-06-21 10:03 ` [PATCH net v5 0/2] xfrm: fix async crypto (-EINPROGRESS) handling in validate_xmit_xfrm() Petr Wozniak
2026-06-21 10:06 ` [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
2 siblings, 0 replies; 6+ messages in thread
From: Sabrina Dubroca @ 2026-06-04 11:29 UTC (permalink / raw)
To: Petr Wozniak; +Cc: netdev, steffen.klassert, pabeni, edumazet
2026-06-03, 08:46:59 +0200, Petr Wozniak wrote:
> +/* Returns the skb on success, NULL if dropped, or ERR_PTR(-EINPROGRESS)
> + * if stolen by async xfrm crypto (delivered via xfrm_dev_resume()).
> + */
> static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)
> {
> netdev_features_t features;
> @@ -4078,7 +4081,6 @@ static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device
> struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *dev, bool *again)
> {
> struct sk_buff *next, *head = NULL, *tail;
> -
nit: no, that blank line needs to stay
> for (; skb != NULL; skb = next) {
> next = skb->next;
> skb_mark_not_on_list(skb);
> @@ -4087,7 +4089,7 @@ struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *d
> skb->prev = skb;
>
> skb = validate_xmit_skb(skb, dev, again);
> - if (!skb)
> + if (IS_ERR_OR_NULL(skb))
> continue;
>
> if (!head)
[...]
> diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
> index 550457e4c..bb0c0fafa 100644
> --- a/net/xfrm/xfrm_device.c
> +++ b/net/xfrm/xfrm_device.c
> @@ -182,7 +182,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
> err = x->type_offload->xmit(x, skb, esp_features);
> if (err) {
> if (err == -EINPROGRESS)
> - return NULL;
> + return ERR_PTR(-EINPROGRESS);
>
> XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR);
> kfree_skb(skb);
Could you please explain why you're ignoring my comment about the
2nd x->type_offload->xmit() call in this function?
Thanks,
--
Sabrina
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net v5 0/2] xfrm: fix async crypto (-EINPROGRESS) handling in validate_xmit_xfrm()
2026-06-03 6:46 [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
2026-06-04 11:29 ` Sabrina Dubroca
@ 2026-06-21 10:03 ` Petr Wozniak
2026-06-21 10:03 ` [PATCH net v5 1/2] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
2026-06-21 10:03 ` [PATCH net v5 2/2] xfrm: fix stale skb->prev after async crypto steals a GSO segment Petr Wozniak
2026-06-21 10:06 ` [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
2 siblings, 2 replies; 6+ messages in thread
From: Petr Wozniak @ 2026-06-21 10:03 UTC (permalink / raw)
To: netdev
Cc: sd, steffen.klassert, herbert, kuba, horms, pabeni, edumazet,
davem, Petr Wozniak
This series fixes how the async crypto path (-EINPROGRESS from ->xmit())
is handled in validate_xmit_xfrm() and its callers.
Patch 1 (previously sent on its own, v1-v4) makes validate_xmit_xfrm()
return ERR_PTR(-EINPROGRESS) instead of NULL when a packet is stolen by
async crypto, so __dev_queue_xmit() can tell it apart from a real drop
and stop reporting -ENOMEM on noqueue/bridge interfaces. v5 also covers
the GSO segment loop, as Sabrina pointed out.
Patch 2 fixes a use-after-free found while looking at that GSO loop:
validate_xmit_xfrm() unlinks async-stolen segments but never updates the
list head ->prev, which validate_xmit_skb_list() later dereferences.
Changes in v5:
- 1/2: also propagate ERR_PTR(-EINPROGRESS) from the GSO segment loop
(the 2nd ->xmit() call); v4 only handled the single-skb path. Restore
the blank line in validate_xmit_skb_list(). Add the missing
maintainers to Cc. (Sabrina Dubroca)
- 2/2: new patch -- fix the stale skb->prev use-after-free (also flagged
by Sashiko)
Changes in v4:
- Drop bool stolen tracking and the ERR_PTR return in
validate_xmit_skb_list(); use IS_ERR_OR_NULL() so stolen skbs are
silently skipped (Sabrina Dubroca)
- Drop ERR_PTR(-EINPROGRESS) handling in __dev_direct_xmit() (Sabrina Dubroca)
- Move validate_xmit_skb() return-value comment above the function
(Sabrina Dubroca)
Changes in v3:
- validate_xmit_skb_list(): set stolen=true only for -EINPROGRESS
(Sabrina Dubroca)
Changes in v2:
- Reset rc to NET_XMIT_SUCCESS only when PTR_ERR(skb) == -EINPROGRESS
(Sabrina Dubroca)
Petr Wozniak (2):
xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
xfrm: fix stale skb->prev after async crypto steals a GSO segment
net/core/dev.c | 10 ++++++++--
net/xfrm/xfrm_device.c | 12 ++++++++++--
2 files changed, 18 insertions(+), 4 deletions(-)
--
2.51.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net v5 1/2] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
2026-06-21 10:03 ` [PATCH net v5 0/2] xfrm: fix async crypto (-EINPROGRESS) handling in validate_xmit_xfrm() Petr Wozniak
@ 2026-06-21 10:03 ` Petr Wozniak
2026-06-21 10:03 ` [PATCH net v5 2/2] xfrm: fix stale skb->prev after async crypto steals a GSO segment Petr Wozniak
1 sibling, 0 replies; 6+ messages in thread
From: Petr Wozniak @ 2026-06-21 10:03 UTC (permalink / raw)
To: netdev
Cc: sd, steffen.klassert, herbert, kuba, horms, pabeni, edumazet,
davem, Petr Wozniak
validate_xmit_xfrm() returns NULL both when a packet is dropped and
when it is stolen by async crypto (-EINPROGRESS from ->xmit()).
Callers cannot distinguish the two cases.
f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
changed the semantics of a NULL return from "dropped" to "stolen or
dropped", but __dev_queue_xmit() was not updated. On virtual/bridge
interfaces (noqueue qdisc) __dev_queue_xmit() initialises rc=-ENOMEM
and jumps to out: when skb is NULL, returning -ENOMEM to the caller
even though the packet will be delivered correctly via xfrm_dev_resume().
Return ERR_PTR(-EINPROGRESS) from validate_xmit_xfrm() for the async
case so callers can tell it apart from a real drop. Update
__dev_queue_xmit() to handle ERR_PTR(-EINPROGRESS) from
validate_xmit_skb() correctly. Update validate_xmit_skb_list() to
use IS_ERR_OR_NULL() so that ERR_PTR(-EINPROGRESS) is not mistakenly
added to the transmitted list.
Fixes: f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
Suggested-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Petr Wozniak <petr.wozniak@gmail.com>
---
net/core/dev.c | 10 ++++++++--
net/xfrm/xfrm_device.c | 4 ++--
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index 5c01dfaa6c44..f7ffc4d29597 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4018,6 +4018,9 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
return NULL;
}
+/* Returns the skb on success, NULL if dropped, or ERR_PTR(-EINPROGRESS)
+ * if stolen by async xfrm crypto (delivered via xfrm_dev_resume()).
+ */
static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)
{
netdev_features_t features;
@@ -4089,7 +4092,7 @@ struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *d
skb->prev = skb;
skb = validate_xmit_skb(skb, dev, again);
- if (!skb)
+ if (IS_ERR_OR_NULL(skb))
continue;
if (!head)
@@ -4860,8 +4863,11 @@ int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)
goto recursion_alert;
skb = validate_xmit_skb(skb, dev, &again);
- if (!skb)
+ if (IS_ERR_OR_NULL(skb)) {
+ if (PTR_ERR(skb) == -EINPROGRESS)
+ rc = NET_XMIT_SUCCESS;
goto out;
+ }
HARD_TX_LOCK(dev, txq, cpu);
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 630f3dd31cc5..19c77f09acc9 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -182,7 +182,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
err = x->type_offload->xmit(x, skb, esp_features);
if (err) {
if (err == -EINPROGRESS)
- return NULL;
+ return ERR_PTR(-EINPROGRESS);
XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR);
kfree_skb(skb);
@@ -224,7 +224,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
pskb = skb2;
}
- return skb;
+ return skb ? skb : ERR_PTR(-EINPROGRESS);
}
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
--
2.51.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH net v5 2/2] xfrm: fix stale skb->prev after async crypto steals a GSO segment
2026-06-21 10:03 ` [PATCH net v5 0/2] xfrm: fix async crypto (-EINPROGRESS) handling in validate_xmit_xfrm() Petr Wozniak
2026-06-21 10:03 ` [PATCH net v5 1/2] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
@ 2026-06-21 10:03 ` Petr Wozniak
1 sibling, 0 replies; 6+ messages in thread
From: Petr Wozniak @ 2026-06-21 10:03 UTC (permalink / raw)
To: netdev
Cc: sd, steffen.klassert, herbert, kuba, horms, pabeni, edumazet,
davem, Petr Wozniak
skb_gso_segment() leaves the segment list head with ->prev pointing at
the last segment, an invariant validate_xmit_skb_list() relies on when
it sets its tail pointer (tail = skb->prev).
When validate_xmit_xfrm() walks a GSO list and some segments are stolen
by async crypto (->xmit() returns -EINPROGRESS), those segments are
unlinked from the list but the head ->prev is never updated. If the
last segment is the one stolen, the returned head still has ->prev
pointing at it, even though it is now owned by the crypto engine and may
be freed. validate_xmit_skb_list() later does tail->next = skb, writing
through that stale pointer -- a use-after-free.
Repoint skb->prev at the last retained segment before returning.
Fixes: f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
Signed-off-by: Petr Wozniak <petr.wozniak@gmail.com>
---
net/xfrm/xfrm_device.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 19c77f09acc9..aec1e1184a71 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -224,6 +224,14 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
pskb = skb2;
}
+ /* skb_gso_segment() set skb->prev to the last segment, but async
+ * crypto may have stolen it above without updating ->prev. Repoint
+ * it at the last retained segment so validate_xmit_skb_list() does
+ * not chain onto a segment now owned by the crypto engine.
+ */
+ if (skb)
+ skb->prev = pskb;
+
return skb ? skb : ERR_PTR(-EINPROGRESS);
}
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
--
2.51.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
2026-06-03 6:46 [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
2026-06-04 11:29 ` Sabrina Dubroca
2026-06-21 10:03 ` [PATCH net v5 0/2] xfrm: fix async crypto (-EINPROGRESS) handling in validate_xmit_xfrm() Petr Wozniak
@ 2026-06-21 10:06 ` Petr Wozniak
2 siblings, 0 replies; 6+ messages in thread
From: Petr Wozniak @ 2026-06-21 10:06 UTC (permalink / raw)
To: netdev, sd
Cc: steffen.klassert, herbert, kuba, horms, pabeni, edumazet, davem
Reposting on the list, as you asked.
Apologies for missing your comment about the 2nd x->type_offload->xmit()
call across several versions -- entirely my fault.
You're right: in the skb_list_walk_safe() loop, if all GSO segments
return -EINPROGRESS, skb is advanced to NULL and the function returns
NULL instead of ERR_PTR(-EINPROGRESS). v5 1/2 fixes it:
- return skb;
+ return skb ? skb : ERR_PTR(-EINPROGRESS);
At that point NULL can only mean all segments were stolen -- the error
path (err != -EINPROGRESS) returns NULL directly from inside the loop.
v5 1/2 also restores the blank line in validate_xmit_skb_list() and adds
the missing maintainers to Cc.
For the use-after-free I mentioned: I confirmed it. validate_xmit_xfrm()
unlinks async-stolen segments but never updates the list head ->prev, so
when the last segment is stolen, validate_xmit_skb_list() chains onto it
via tail->next. v5 2/2 fixes it by repointing skb->prev at the last
retained segment. As you suggested, the two fixes go as a series.
I could not confirm the head-list leak on a closer look, so I left it
out; I'll send a separate patch if I find it.
The v5 series has been sent.
Thanks,
Petr
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-21 10:06 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-03 6:46 [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
2026-06-04 11:29 ` Sabrina Dubroca
2026-06-21 10:03 ` [PATCH net v5 0/2] xfrm: fix async crypto (-EINPROGRESS) handling in validate_xmit_xfrm() Petr Wozniak
2026-06-21 10:03 ` [PATCH net v5 1/2] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
2026-06-21 10:03 ` [PATCH net v5 2/2] xfrm: fix stale skb->prev after async crypto steals a GSO segment Petr Wozniak
2026-06-21 10:06 ` [PATCH net v4] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() Petr Wozniak
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox