From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
To: netdev@vger.kernel.org
Cc: davem@davemloft.net, kuba@kernel.org, edumazet@google.com,
pabeni@redhat.com, horms@kernel.org, andrew+netdev@lunn.ch,
Willem de Bruijn <willemb@google.com>,
Katherine Leaver <kleaver@janestreet.com>,
Bjoern Doebel <doebel@amazon.de>,
stable@vger.kernel.org
Subject: [PATCH net 2/2] packet: use ubuf_info completion for TX_RING packets
Date: Mon, 14 Sep 2026 17:37:43 -0400 [thread overview]
Message-ID: <20260914214229.1674102-3-willemdebruijn.kernel@gmail.com> (raw)
In-Reply-To: <20260914214229.1674102-1-willemdebruijn.kernel@gmail.com>
From: Willem de Bruijn <willemb@google.com>
tpacket_snd sends skbs with frags pointing into its ring slots. Slots
are released when skb->destructor is called.
A call to skb_orphan calls skb->destructor before the skb is freed.
This can cause the slot to be reused while still linked into the skb.
Switch to standard zerocopy completion (ubuf_info) so the slot is only
released once all references to the payload are freed or copied.
Restore skb->destructor to standard sock_wfree.
To prevent userspace from aliasing in-flight state on shared ring
slots, allocate tpacket_uarg per packet, rather than per slot. This
adds a small allocation to the transmit path. Use standard kmalloc to
allow backporting to stable kernels.
The uarg holds an sk_wmem_alloc reference, rather than an sk_refcnt
reference. packet_free_tx_ring waits on sk_wmem_alloc before freeing
the ring pages.
As a result a slot is released when its payload is copied, which can
be before transmission (e.g., in skb_orphan_frags_rx). Any slot
timestamp then reflects the time of copy, rather than of transmit
(or skb_orphan).
Revert the now unused previous skb_zcopy_.._nouarg infra.
Reported-by: Katherine Leaver <kleaver@janestreet.com>
Reported-by: Bjoern Doebel <doebel@amazon.de>
Closes: https://lore.kernel.org/netdev/20260909085542.3370986-1-doebel@amazon.de/
Fixes: 5cd8d46ea156 ("packet: copy user buffers before orphan or clone")
Cc: stable@vger.kernel.org
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
This is a complex patch for stable. I have tried a variety of
alternatives for stable, including a two-step with deferral of this
full fix to net-next. But all have worse caveats or side effects:
- disable tpacket_snd zerocopy:
performance regression also on paths that do not call skb_orphan
- detect tpacket skb in skb_orphan and set po->tx_copy that
disables tpacket_snd zerocopy:
- does not fix the first skb/slot
- hot path function now needs to check skb_shinfo(skb) field
- insert skb_orphan_frags in skb_orphan:
- hot path function now needs to check skb_shinfo(skb) field
- needs extra code for virtio-net and cxgb4, which link the frags
into descriptors before calling skb_orphan.
- insert skb_orphan_frags at all relevant callers of skb_orphan:
10+ sites across drivers and qdiscs
---
include/linux/skbuff.h | 19 +---------
net/packet/af_packet.c | 82 ++++++++++++++++++++++++++++--------------
2 files changed, 56 insertions(+), 45 deletions(-)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 421f6fc45451..b14d6be7370b 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -1834,22 +1834,6 @@ static inline void skb_zcopy_set(struct sk_buff *skb, struct ubuf_info *uarg,
}
}
-static inline void skb_zcopy_set_nouarg(struct sk_buff *skb, void *val)
-{
- skb_shinfo(skb)->destructor_arg = (void *)((uintptr_t) val | 0x1UL);
- skb_shinfo(skb)->flags |= SKBFL_ZEROCOPY_FRAG;
-}
-
-static inline bool skb_zcopy_is_nouarg(struct sk_buff *skb)
-{
- return (uintptr_t) skb_shinfo(skb)->destructor_arg & 0x1UL;
-}
-
-static inline void *skb_zcopy_get_nouarg(struct sk_buff *skb)
-{
- return (void *)((uintptr_t) skb_shinfo(skb)->destructor_arg & ~0x1UL);
-}
-
static inline void net_zcopy_put(struct ubuf_info *uarg)
{
if (uarg)
@@ -1872,8 +1856,7 @@ static inline void skb_zcopy_clear(struct sk_buff *skb, bool zerocopy_success)
struct ubuf_info *uarg = skb_zcopy(skb);
if (uarg) {
- if (!skb_zcopy_is_nouarg(skb))
- uarg->ops->complete(skb, uarg, zerocopy_success);
+ uarg->ops->complete(skb, uarg, zerocopy_success);
skb_shinfo(skb)->flags &= ~SKBFL_ALL_ZEROCOPY;
}
diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index 76bde7906d49..41fc053075da 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -2528,26 +2528,6 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
goto drop_n_restore;
}
-static void tpacket_destruct_skb(struct sk_buff *skb)
-{
- struct packet_sock *po = pkt_sk(skb->sk);
-
- if (likely(po->tx_ring.pg_vec)) {
- void *ph;
- __u32 ts;
-
- ph = skb_zcopy_get_nouarg(skb);
-
- ts = __packet_set_timestamp(po, ph, skb);
- __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts);
-
- packet_dec_pending(&po->tx_ring);
- complete(&po->skb_completion);
- }
-
- sock_wfree(skb);
-}
-
static int __packet_snd_vnet_parse(struct virtio_net_hdr *vnet_hdr, size_t len)
{
if ((vnet_hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) &&
@@ -2587,27 +2567,57 @@ static int packet_snd_vnet_parse(struct msghdr *msg, size_t *len,
return 0;
}
+struct tpacket_uarg {
+ struct ubuf_info ubuf;
+ struct packet_sock *po;
+ void *ph;
+};
+
+static void tpacket_ubuf_complete(struct sk_buff *skb, struct ubuf_info *uarg,
+ bool success)
+{
+ struct tpacket_uarg *tu = container_of(uarg, struct tpacket_uarg, ubuf);
+ struct packet_sock *po = tu->po;
+ void *ph = tu->ph;
+ __u32 ts = 0;
+
+ if (!refcount_dec_and_test(&uarg->refcnt))
+ return;
+
+ if (likely(READ_ONCE(po->tx_ring.pg_vec))) {
+ if (likely(skb))
+ ts = __packet_set_timestamp(po, ph, skb);
+ __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts);
+
+ packet_dec_pending(&po->tx_ring);
+ complete(&po->skb_completion);
+ }
+
+ kfree(tu);
+ sk_free(&po->sk);
+}
+
+static const struct ubuf_info_ops tpacket_ubuf_ops = {
+ .complete = tpacket_ubuf_complete,
+};
+
static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb,
- void *frame, struct net_device *dev, void *data, int tp_len,
+ struct net_device *dev, void *data, int tp_len,
__be16 proto, unsigned char *addr, int hlen, int copylen,
int hard_header_len,
const struct sockcm_cookie *sockc)
{
- union tpacket_uhdr ph;
int to_write, offset, len, nr_frags, len_max;
struct socket *sock = po->sk.sk_socket;
struct page *page;
int err;
- ph.raw = frame;
-
skb->protocol = proto;
skb->dev = dev;
skb->priority = sockc->priority;
skb->mark = sockc->mark;
skb_set_delivery_type_by_clockid(skb, sockc->transmit_time, po->sk.sk_clockid);
skb_setup_tx_timestamp(skb, sockc);
- skb_zcopy_set_nouarg(skb, ph.raw);
skb_reserve(skb, hlen);
skb_reset_network_header(skb);
@@ -2747,6 +2757,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
struct virtio_net_hdr vnet_hdr;
bool has_vnet_hdr = false;
struct sockcm_cookie sockc;
+ struct tpacket_uarg *uarg;
__be16 proto;
int err, reserve = 0;
void *ph;
@@ -2874,7 +2885,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
err = len_sum;
goto out_status;
}
- tp_len = tpacket_fill_skb(po, skb, ph, dev, data, tp_len, proto,
+ tp_len = tpacket_fill_skb(po, skb, dev, data, tp_len, proto,
addr, hlen, copylen, hard_header_len,
&sockc);
if (likely(tp_len >= 0) &&
@@ -2906,7 +2917,24 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
virtio_net_hdr_set_proto(skb, &vnet_hdr);
}
- skb->destructor = tpacket_destruct_skb;
+ uarg = kmalloc(sizeof(*uarg), GFP_KERNEL);
+ if (unlikely(!uarg)) {
+ if (likely(len_sum > 0))
+ err = len_sum;
+ else
+ err = -ENOMEM;
+ goto out_status;
+ }
+ uarg->po = po;
+ uarg->ph = ph;
+ uarg->ubuf.ops = &tpacket_ubuf_ops;
+ uarg->ubuf.flags = SKBFL_ZEROCOPY_FRAG;
+ refcount_set(&uarg->ubuf.refcnt, 1);
+
+ /* Hold a sk_wmem_alloc reference until completion */
+ refcount_inc(&po->sk.sk_wmem_alloc);
+ skb_zcopy_init(skb, &uarg->ubuf);
+
__packet_set_status(po, ph, TP_STATUS_SENDING);
packet_inc_pending(&po->tx_ring);
--
2.55.0.1032.g73a4cd73de-goog
next prev parent reply other threads:[~2026-09-14 21:42 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 21:37 [PATCH net 0/2] packet: fix PACKET_TX_RING data corruption on skb_orphan Willem de Bruijn
2026-09-14 21:37 ` [PATCH net 1/2] virtio_net: copy zerocopy frags in start_xmit without NAPI Willem de Bruijn
2026-09-16 0:37 ` netdev-bot+sashiko
2026-09-14 21:37 ` Willem de Bruijn [this message]
2026-09-16 0:37 ` [PATCH net 2/2] packet: use ubuf_info completion for TX_RING packets netdev-bot+sashiko
2026-09-17 9:16 ` Paolo Abeni
2026-09-17 13:28 ` Willem de Bruijn
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=20260914214229.1674102-3-willemdebruijn.kernel@gmail.com \
--to=willemdebruijn.kernel@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=doebel@amazon.de \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kleaver@janestreet.com \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
--cc=willemb@google.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