]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ovpn: fix peer refcount leak in TCP error paths
authorPavitra Jha <jhapavitra98@gmail.com>
Sat, 23 May 2026 09:02:43 +0000 (05:02 -0400)
committerAntonio Quartulli <antonio@openvpn.net>
Mon, 20 Jul 2026 13:49:55 +0000 (15:49 +0200)
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@vger.kernel.org
Signed-off-by: Pavitra Jha <jhapavitra98@gmail.com>
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
drivers/net/ovpn/tcp.c

index 433bd07a4f1be9ab39ac8d82ca5ae58e71464857..0af14055c39aad80d561485edb719f550c2bba1c 100644 (file)
@@ -151,7 +151,8 @@ err:
        /* take reference for deferred peer deletion. should never fail */
        if (WARN_ON(!ovpn_peer_hold(peer)))
                goto err_nopeer;
-       schedule_work(&peer->tcp.defer_del_work);
+       if (!schedule_work(&peer->tcp.defer_del_work))
+               ovpn_peer_put(peer);
        ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
 err_nopeer:
        kfree_skb(skb);
@@ -283,7 +284,8 @@ static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk)
                         * stream therefore we abort the connection
                         */
                        ovpn_peer_hold(peer);
-                       schedule_work(&peer->tcp.defer_del_work);
+                       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