[PATCH] ovpn: fix peer refcount leak in TCP error paths

Pavitra Jha posted 1 patch 3 days, 11 hours ago
There is a newer version of this series
drivers/net/ovpn/tcp.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
[PATCH] ovpn: fix peer refcount leak in TCP error paths
Posted by Pavitra Jha 3 days, 11 hours ago
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.

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>
---
 drivers/net/ovpn/tcp.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
index 5499c1572..d651ce85c 100644
--- a/drivers/net/ovpn/tcp.c
+++ b/drivers/net/ovpn/tcp.c
@@ -151,7 +151,8 @@ static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
 	/* 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);
 	dev_dstats_rx_dropped(peer->ovpn->dev);
 err_nopeer:
 	kfree_skb(skb);
@@ -282,8 +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
 			 */
-			ovpn_peer_hold(peer);
-			schedule_work(&peer->tcp.defer_del_work);
+			if (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
Re: [PATCH] ovpn: fix peer refcount leak in TCP error paths
Posted by Antonio Quartulli 2 days, 12 hours ago
Hi,

Thanks for your patch!

On 21/05/2026 10:37, Pavitra Jha wrote:
> 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.
> 
> 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>
> ---
>   drivers/net/ovpn/tcp.c | 8 +++++---
>   1 file changed, 5 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
> index 5499c1572..d651ce85c 100644
> --- a/drivers/net/ovpn/tcp.c
> +++ b/drivers/net/ovpn/tcp.c
> @@ -151,7 +151,8 @@ static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
>   	/* 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);
>   	dev_dstats_rx_dropped(peer->ovpn->dev);
>   err_nopeer:
>   	kfree_skb(skb);
> @@ -282,8 +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
>   			 */
> -			ovpn_peer_hold(peer);
> -			schedule_work(&peer->tcp.defer_del_work);
> +			if (ovpn_peer_hold(peer))

why introducing this new if check?
It seems unrelated to the current fix.

At this point in the flow the hold() cannot fail, otherwise `peer` would 
already be a stale/bogus pointer and we'd be in bigger troubles.


Regards,

> +				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

-- 
Antonio Quartulli
OpenVPN Inc.