[PATCH v2] ovpn: fix peer refcount leak in TCP error paths
From: Pavitra Jha
Date: Fri May 22 2026 - 05:24:39 EST
When either the TCP RX or TX error path calls ovpn_peer_hold() followed
by schedule_work(&peer->tcp.defer_del_work), and the work item is already
pending from the other path, schedule_work() returns false and the work
runs only once. Since ovpn_tcp_peer_del_work() calls ovpn_peer_put()
exactly once, the extra reference taken by the losing path is never
dropped, leaking the peer object.
The race window:
CPU0 (strparser/RX error): CPU1 (tcp_tx_work/TX error):
ovpn_peer_hold() <- refcnt+1 ovpn_peer_hold() <- refcnt+2
schedule_work() <- queued schedule_work() <- NO-OP
(work already pending)
ovpn_tcp_peer_del_work runs:
ovpn_peer_del()
ovpn_peer_put() <- refcnt+1
<- peer never freed
Fix by checking the return value of schedule_work() in both paths and
calling ovpn_peer_put() to drop the extra reference if the work was
already pending. ovpn_peer_hold() is kept unconditional in the TX path
as it cannot fail at that point.
Fixes: a6a5e87b3ee4 ("ovpn: avoid sleep in atomic context in TCP RX error path")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Pavitra Jha <jhapavitra98@xxxxxxxxx>
---
Changes since v1:
- TX path: keep ovpn_peer_hold() unconditional per Antonio Quartulli's
review; only check schedule_work() return value
- Link: https://lore.kernel.org/netdev/20260521083739.65061-1-jhapavitra98@xxxxxxxxx/
---
drivers/net/ovpn/tcp.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
index d651ce85c..2c7d830e7 100644
--- a/drivers/net/ovpn/tcp.c
+++ b/drivers/net/ovpn/tcp.c
@@ -283,9 +283,9 @@ static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk)
/* in case of TCP error we can't recover the VPN
* stream therefore we abort the connection
*/
- if (ovpn_peer_hold(peer))
- if (!schedule_work(&peer->tcp.defer_del_work))
- ovpn_peer_put(peer);
+ ovpn_peer_hold(peer);
+ if (!schedule_work(&peer->tcp.defer_del_work))
+ ovpn_peer_put(peer);
/* we bail out immediately and keep tx_in_progress set
* to true. This way we prevent more TX attempts
--
2.53.0