From: netdev-bot+sashiko@kernel.org
To: carges@cloudflare.com
Cc: Jason@zx2c4.com, andrew+netdev@lunn.ch, davem@davemloft.net,
edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
wireguard@lists.zx2c4.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, kernel-team@cloudflare.com
Subject: Re: [PATCH net] wireguard: wait for per-peer crypto during removal
Date: Mon, 07 Sep 2026 06:14:39 +0000 [thread overview]
Message-ID: <178876167936.219967.11338017664970925838@kernel.org> (raw)
In-Reply-To: <20260902-fix-wg-peer-removal-v1-1-7a0190f5cdb1@cloudflare.com>
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 1 · Medium: 0 · Low: 0
- [High] Teardown can strand decrypted packets in `peer->rx_queue`,
permanently leaking the skbs and the peer.
--
wireguard: wait for per-peer crypto during removal
This replaces the two device-wide flush_workqueue(packet_crypt_wq) calls and
the flush_workqueue(handshake_send_wq) call in peer_remove_after_dead() with
a new per-peer packet_crypt_pending counter plus flush_work() on the peer's
own transmit_packet_work and transmit_handshake_work.
> diff --git a/drivers/net/wireguard/peer.c b/drivers/net/wireguard/peer.c
> index 1cb502a932e0..f7a9c437b5b8 100644
> --- a/drivers/net/wireguard/peer.c
> +++ b/drivers/net/wireguard/peer.c
[ ... ]
> @@ -105,28 +108,27 @@ static void peer_remove_after_dead(struct wg_peer *peer)
> */
> wg_timers_stop(peer);
>
> - /* The transition between packet encryption/decryption queues isn't
> - * guarded by is_dead, but each reference's life is strictly bounded by
> - * two generations: once for parallel crypto and once for serial
> - * ingestion, so we can simply flush twice, and be sure that we no
> - * longer have references inside these queues.
> + /* Lookup removal and is_dead prevent new packets from entering the
> + * parallel crypto queues after synchronize_net() waits for pre-existing
> + * submission paths. Drop the initial count and wait for existing packets
> + * to finish scheduling their serial TX work or RX NAPI processing.
> */
> + atomic_dec(&peer->packet_crypt_pending);
> + wait_var_event(&peer->packet_crypt_pending,
> + !atomic_read_acquire(&peer->packet_crypt_pending));
> +
> + flush_work(&peer->transmit_packet_work);
>
> - /* a) For encrypt/decrypt. */
> - flush_workqueue(peer->device->packet_crypt_wq);
> - /* b.1) For send (but not receive, since that's napi). */
> - flush_workqueue(peer->device->packet_crypt_wq);
> - /* b.2.1) For receive (but not send, since that's wq). */
> napi_disable(&peer->napi);
[Severity: High]
Can this leave entries in peer->rx_queue when the peer is torn down, and
leak both the skbs and the wg_peer itself?
The new counter is decremented in wg_queue_enqueue_per_peer_rx() only after
the packet has been put on peer->rx_queue and NAPI has been scheduled:
drivers/net/wireguard/queueing.h:wg_queue_enqueue_per_peer_rx() {
atomic_set_release(&PACKET_CB(skb)->state, state);
napi_schedule(&peer->napi);
if (atomic_dec_and_test(&peer->packet_crypt_pending))
wake_up_var(&peer->packet_crypt_pending);
wg_peer_put(peer);
}
So packet_crypt_pending reaching zero means "handoff done, NAPI scheduled",
not "rx_queue drained". For an rx-only peer flush_work(&peer->transmit_packet_work)
is a no-op, so napi_disable() can now run immediately after the last decrypt
handoff while up to MAX_QUEUED_PACKETS entries are still queued.
If wg_packet_rx_poll() consumes exactly budget entries it breaks out without
calling napi_complete_done():
drivers/net/wireguard/receive.c:wg_packet_rx_poll() {
...
if (++work_done >= budget)
break;
}
if (work_done < budget)
napi_complete_done(napi, work_done);
return work_done;
}
and __napi_poll() then takes the completion path rather than a repoll,
because napi_disable_locked() has already set the disable-pending state:
net/core/dev.c:__napi_poll() {
...
if (unlikely(napi_disable_pending(n))) {
napi_complete(n);
return work;
}
so napi_disable()/netif_napi_del() finish with a non-empty rx_queue, and
is_dead plus the completed counter wait mean nothing schedules the NAPI
again.
Each stranded skb still holds the peer reference taken by
wg_peer_get_maybe_zero() in wg_packet_consume_data() and a keypair
reference, and those are only dropped at the next: label of
wg_packet_rx_poll():
next:
wg_noise_keypair_put(keypair, false);
wg_peer_put(peer);
Is there any other consumer of peer->rx_queue that could drain it? If not,
the peer kref never reaches zero, so kref_release()/rcu_release() never run
and the skbs, the wg_peer, its dst_cache and the staged queue are never
freed. That also means the existing safety net in rcu_release():
WARN_ON(wg_prev_queue_peek(&peer->tx_queue) || wg_prev_queue_peek(&peer->rx_queue));
can never fire to report it.
The pre-patch code did not strictly guarantee the drain either, but the two
flush_workqueue(peer->device->packet_crypt_wq) calls being removed blocked
for as long as the shared crypt workers kept running, which is exactly the
heavy-traffic case where rx_queue gets deep, and in practice gave the NAPI
softirq time to drain it. Would it make sense to have the teardown path
wait for the rx_queue to actually empty (or purge it explicitly) rather
than only for the handoff counter?
> - /* b.2.1) It's now safe to remove the napi struct, which must be done
> + /* It's now safe to remove the napi struct, which must be done
> * here from process context.
> */
> netif_napi_del(&peer->napi);
[ ... ]
> diff --git a/drivers/net/wireguard/queueing.h b/drivers/net/wireguard/queueing.h
> index 79b6d70de236..fdd34f0f15a6 100644
> --- a/drivers/net/wireguard/queueing.h
> +++ b/drivers/net/wireguard/queueing.h
[ ... ]
> @@ -194,6 +198,8 @@ static inline void wg_queue_enqueue_per_peer_rx(struct sk_buff *skb, enum packet
>
> atomic_set_release(&PACKET_CB(skb)->state, state);
> napi_schedule(&peer->napi);
> + if (atomic_dec_and_test(&peer->packet_crypt_pending))
> + wake_up_var(&peer->packet_crypt_pending);
> wg_peer_put(peer);
> }
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902-fix-wg-peer-removal-v1-1-7a0190f5cdb1%40cloudflare.com
prev parent reply other threads:[~2026-09-07 6:14 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-02 15:11 Chris J Arges
2026-09-07 6:14 ` netdev-bot+sashiko [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=178876167936.219967.11338017664970925838@kernel.org \
--to=netdev-bot+sashiko@kernel.org \
--cc=Jason@zx2c4.com \
--cc=andrew+netdev@lunn.ch \
--cc=carges@cloudflare.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=kernel-team@cloudflare.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=wireguard@lists.zx2c4.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®