]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ovpn: respect peer refcount in CMD_NEW_PEER error path
authorDavid Carlier <devnexen@gmail.com>
Wed, 13 May 2026 10:55:21 +0000 (11:55 +0100)
committerAntonio Quartulli <antonio@openvpn.net>
Thu, 14 May 2026 14:24:45 +0000 (16:24 +0200)
ovpn_nl_peer_new_doit()'s error path calls ovpn_peer_release() directly
rather than ovpn_peer_put(), bypassing the kref. The accompanying
comment ("peer was not yet hashed, thus it is not used in any context")
holds for UDP but not for TCP.

For UDP, the ovpn_socket union uses the .ovpn arm and never points back
at a peer; UDP encap_recv looks up peers via the not-yet-populated
hashtables, so the new peer is unreachable until ovpn_peer_add()
publishes it.

For TCP, ovpn_socket_new() sets ovpn_sock->peer and
ovpn_tcp_socket_attach() publishes ovpn_sock via rcu_assign_sk_user_data().
From that moment until ovpn_socket_release() detaches in the error path,
the TCP fd is fully wired: userspace recvmsg / sendmsg / close / poll
on the fd, as well as the strparser-driven ovpn_tcp_rcv() path, can
reach the peer through sk_user_data -> ovpn_sock->peer and bump its
refcount via ovpn_peer_hold().

ovpn_tcp_socket_wait_finish() (called inside ovpn_socket_release())
drains strparser and the tx work, but does not synchronize with
userspace syscall callers that already hold a peer reference. If
ovpn_nl_peer_modify() or ovpn_peer_add() returns an error while such
a caller is in flight - notably an ovpn_tcp_recvmsg() blocked in
__skb_recv_datagram() on peer->tcp.user_queue - the direct
ovpn_peer_release() destroys the peer while the caller still holds
the reference, and the eventual ovpn_peer_put() from that caller
operates on freed memory.

Replace the direct destructor call with ovpn_peer_put() so the kref
correctly defers destruction until the last reference is dropped.
In the common case where no concurrent user is present, behaviour is
unchanged: the kref hits zero immediately and ovpn_peer_release_kref()
runs the same destructor.

With this conversion ovpn_peer_release() has no callers outside peer.c
- ovpn_peer_release_kref() in the same translation unit is the only
remaining user - so make it static and drop its declaration from
peer.h.

Fixes: 11851cbd60ea ("ovpn: implement TCP transport")
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: David Carlier <devnexen@gmail.com>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
drivers/net/ovpn/netlink.c
drivers/net/ovpn/peer.c
drivers/net/ovpn/peer.h

index 291e2e5bb450dbc20df04659c21b29ac231e7e4c..4c66c1ec497ec2b824a6ee42fc899560a171c2b7 100644 (file)
@@ -462,10 +462,12 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info)
 sock_release:
        ovpn_socket_release(peer);
 peer_release:
-       /* release right away because peer was not yet hashed, thus it is not
-        * used in any context
+       /* For UDP, the peer is unreachable until added to the hashtables, so
+        * dropping the initial reference is enough. For TCP, the peer may be
+        * concurrently reachable via sk_user_data->peer until
+        * ovpn_socket_release() detaches; rely on the refcount.
         */
-       ovpn_peer_release(peer);
+       ovpn_peer_put(peer);
 
        return ret;
 }
index c02dfab51a6e6380b4ff2d1095ec1e3c55548858..fb10d1fea940600d2dcceef31dc28b14403bd010 100644 (file)
@@ -354,7 +354,7 @@ static void ovpn_peer_release_rcu(struct rcu_head *head)
  * ovpn_peer_release - release peer private members
  * @peer: the peer to release
  */
-void ovpn_peer_release(struct ovpn_peer *peer)
+static void ovpn_peer_release(struct ovpn_peer *peer)
 {
        ovpn_crypto_state_release(&peer->crypto);
        spin_lock_bh(&peer->lock);
index 328401570cba838631058cfec3129927816ef4ca..86c8cffada6d42e10c17811de9e061b7faffd96d 100644 (file)
@@ -127,7 +127,6 @@ static inline bool ovpn_peer_hold(struct ovpn_peer *peer)
        return kref_get_unless_zero(&peer->refcount);
 }
 
-void ovpn_peer_release(struct ovpn_peer *peer);
 void ovpn_peer_release_kref(struct kref *kref);
 
 /**