Netdev List
 help / color / mirror / Atom feed
* [PATCH net] net: ovpn: refuse TCP socket layering with an active ULP
@ 2026-05-17 15:56 Michael Bommarito
  2026-05-19 14:32 ` Sabrina Dubroca
  0 siblings, 1 reply; 6+ messages in thread
From: Michael Bommarito @ 2026-05-17 15:56 UTC (permalink / raw)
  To: Antonio Quartulli
  Cc: Sabrina Dubroca, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, netdev, linux-kernel

ovpn's TCP transport replaces sk_prot, sk_data_ready, sk_write_space
and sk_socket->ops by direct field writes when a peer is attached, and
restores them by direct field writes when the peer is detached. That
layering scheme is not compatible with a TCP ULP (e.g. kTLS) being
installed on the same socket: ULP setup also captures the current
sk_prot as its lower layer and replaces sk_prot with its own. The two
schemes are not aware of each other and can be combined in either
order on the same fd.

In the order "ovpn attach, then setsockopt(TCP_ULP=tls), then
OVPN_CMD_PEER_DEL, then close", ovpn_tcp_socket_detach() restores the
saved pre-ovpn sk_prot directly. That overwrites the ULP-installed
sk_prot with the base TCP proto, so the ULP's close hook
(tls_sk_proto_close() in the kTLS case) is never invoked when
userspace closes the fd. The ULP's TX delayed work is never cancelled
and still carries the raw struct sock *, the per-ULP context is never
freed, and TlsCurrTxSw / TlsCurrRxSw are not decremented. After the
TCP socket is freed by the normal RCU path, the still-scheduled TX
delayed work fires and dereferences the freed sock:

  BUG: KASAN: slab-use-after-free in tx_work_handler+0x3a/0x146
  Workqueue: events tx_work_handler
  Read of size 8 at addr ... by task kworker/...
  Allocated by task ...:
    sk_alloc
    inet_create
    __sock_create
  Freed by task 0:
    __sk_destruct
    rcu_core

The same trigger run as a loop also produces an unbounded leak of the
per-iteration ULP context: TlsCurrTxSw and TlsCurrRxSw grow
monotonically and SUnreclaim grows in proportion, because
tls_sk_proto_close() is the only path that frees them and it is
skipped.

Fix this in three related places, all in ovpn, since ovpn is the
layer that bypasses the in-tree ULP close contract by restoring
sk_prot directly:

  - ovpn_tcp_socket_attach(): if the socket already carries a TCP ULP
    (inet_csk_has_ulp() true), return -EBUSY. ovpn does not own the
    sk_prot it would be saving, and would silently displace the ULP's
    callback chain.

  - ovpn_tcp_socket_detach(): if a ULP was layered on top of ovpn
    between attach and detach, do NOT restore the saved sk_prot /
    sk_data_ready / sk_write_space / sk_socket->ops. The ULP recorded
    ovpn_tcp_prot as its lower layer; the correct contract on detach
    is to leave the ULP's callbacks in place so that close() reaches
    the ULP's close hook and the ULP's own teardown path can run.

  - ovpn_tcp_close(): after detach has cleared sk_user_data, close
    dispatches that still land on ovpn_tcp_prot (the ULP close hook
    chains via the captured ctx->sk_proto, which equals
    &ovpn_tcp_prot; the detach-vs-close race window can also leave
    sk_prot pointing at ovpn_tcp_prot momentarily) used to early-
    return without invoking the base TCP close, leaving the TCP
    socket to be freed without a graceful FIN handshake. Chain to
    tcp_prot.close / tcpv6_prot.close in that case so the TCP layer
    still completes its normal shutdown.

OVPN_CMD_PEER_NEW and OVPN_CMD_PEER_DEL are gated by
GENL_ADMIN_PERM, and the ovpn netdev itself is created via
rtnetlink RTM_NEWLINK which also requires CAP_NET_ADMIN; both
gates resolve to CAP_NET_ADMIN in init_user_ns.  A uid 65534
process in a new user+net namespace cannot reach this code path.
The trigger therefore requires an attacker with init-ns
CAP_NET_ADMIN (host network-management service, privileged
container, or rootful container deliberately granted host
CAP_NET_ADMIN).

Fixes: 11851cbd60ea ("ovpn: implement TCP transport")
Cc: stable@vger.kernel.org
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-7
---
 drivers/net/ovpn/tcp.c | 57 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 52 insertions(+), 5 deletions(-)

diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
index 65054cc84be55..469f968010b74 100644
--- a/drivers/net/ovpn/tcp.c
+++ b/drivers/net/ovpn/tcp.c
@@ -9,6 +9,7 @@
 #include <linux/skbuff.h>
 #include <net/hotdata.h>
 #include <net/inet_common.h>
+#include <net/inet_connection_sock.h>
 #include <net/ipv6.h>
 #include <net/tcp.h>
 #include <net/transp_v6.h>
@@ -210,14 +211,29 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock)
 {
 	struct ovpn_peer *peer = ovpn_sock->peer;
 	struct sock *sk = ovpn_sock->sk;
+	bool has_ulp;
 
 	strp_stop(&peer->tcp.strp);
 	skb_queue_purge(&peer->tcp.user_queue);
 
-	/* restore CBs that were saved in ovpn_sock_set_tcp_cb() */
-	sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
-	sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
-	sk->sk_prot = peer->tcp.sk_cb.prot;
+	/* If a TCP ULP (e.g. kTLS) was installed on top of our protocol
+	 * callbacks after ovpn_tcp_socket_attach(), the ULP recorded
+	 * ovpn_tcp_prot as its lower layer and replaced sk_prot with its
+	 * own. Directly restoring the pre-attach sk_prot here would
+	 * overwrite the ULP's sk_prot with the base TCP proto, bypassing
+	 * the ULP close path entirely on subsequent close() (which would
+	 * leak the ULP context and leave any deferred ULP work pointing
+	 * at the freed sock). Leave the callbacks alone in that case;
+	 * the ULP's close path is responsible for both its own teardown
+	 * and for chaining into the saved lower-layer close.
+	 */
+	has_ulp = inet_csk_has_ulp(sk);
+	if (!has_ulp) {
+		/* restore CBs that were saved in ovpn_sock_set_tcp_cb() */
+		sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
+		sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
+		sk->sk_prot = peer->tcp.sk_cb.prot;
+	}
 
 	/* tcp_close() may race this function and could set
 	 * sk->sk_socket to NULL. It does so by invoking
@@ -228,7 +244,7 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock)
 	 * sk_socket to disappear under our feet
 	 */
 	write_lock_bh(&sk->sk_callback_lock);
-	if (sk->sk_socket)
+	if (sk->sk_socket && !has_ulp)
 		sk->sk_socket->ops = peer->tcp.sk_cb.ops;
 	write_unlock_bh(&sk->sk_callback_lock);
 
@@ -518,6 +534,17 @@ int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_sock,
 	/* make sure no pre-existing encapsulation handler exists */
 	if (ovpn_sock->sk->sk_user_data)
 		return -EBUSY;
+
+	/* refuse to layer on top of an already-attached TCP ULP (e.g.
+	 * kTLS). ovpn replaces sk_prot/sk_data_ready/sk_write_space by
+	 * direct field writes; layering it under an existing ULP would
+	 * confuse the ULP's saved-proto chain. The inverse case (a ULP
+	 * layered on top of ovpn after attach) is handled in
+	 * ovpn_tcp_socket_detach().
+	 */
+	if (inet_csk_has_ulp(ovpn_sock->sk))
+		return -EBUSY;
+
 	rcu_assign_sk_user_data(ovpn_sock->sk, ovpn_sock);
 
 	/* only a fully connected socket is expected. Connection should be
@@ -583,6 +610,26 @@ static void ovpn_tcp_close(struct sock *sk, long timeout)
 	sock = rcu_dereference_sk_user_data(sk);
 	if (!sock || !sock->peer || !ovpn_peer_hold(sock->peer)) {
 		rcu_read_unlock();
+		/* No (or no-longer-attached) ovpn state on this socket.
+		 * That happens when ovpn_tcp_socket_detach() already ran
+		 * and cleared sk_user_data, but the caller chain still
+		 * dispatches ->close() on ovpn_tcp_prot. The two cases
+		 * that exhibit this are:
+		 *   - a TCP ULP layered on top of ovpn whose close hook
+		 *     chains via the captured ctx->sk_proto (which is
+		 *     &ovpn_tcp_prot) after detach has run;
+		 *   - the close-vs-detach race window where sk_user_data
+		 *     has been cleared but sk_prot has not yet been
+		 *     restored.
+		 * In both cases the TCP layer still owes the peer a
+		 * graceful close, so chain to the base TCP close.
+		 */
+#if IS_ENABLED(CONFIG_IPV6)
+		if (sk->sk_family == AF_INET6)
+			tcpv6_prot.close(sk, timeout);
+		else
+#endif
+			tcp_prot.close(sk, timeout);
 		return;
 	}
 	peer = sock->peer;

base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-05-20  9:17 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-17 15:56 [PATCH net] net: ovpn: refuse TCP socket layering with an active ULP Michael Bommarito
2026-05-19 14:32 ` Sabrina Dubroca
2026-05-19 14:37   ` Antonio Quartulli
2026-05-19 15:02     ` Michael Bommarito
2026-05-20  7:22   ` Antonio Quartulli
2026-05-20  9:16     ` Sabrina Dubroca

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox