From nobody Mon May 25 06:41:10 2026 Received: from mail-qk1-f175.google.com (mail-qk1-f175.google.com [209.85.222.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 498062857CC for ; Sun, 17 May 2026 15:56:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779033419; cv=none; b=Ixsx36yfgFPRlWII/hZ78amGH1j4jGmKKYFUnK2M2ZZETDsNyae3khIpV+01EbjwDw11H3esV9gHZrtUjkn73xl+86iJhJYXAAXjyEm9kn6igZpMTkS9uGGWjgu07TNTEzd7irnuLnfSzCsxVsL30hOJ5S5rtOi/7nt2Qb47KDo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779033419; c=relaxed/simple; bh=15mWIMAHjv4cgViWKIAWOSSow/WUs8mL4EFX0Z3XoNo=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=NsLV5TE1vuc6I29l5KT9uEU+pvzjgx6i9vp/S18zPmq3hyZFBQtO1qkzctnlTbMOjrIEiPaO44WbWsLH34hngqWCC+TiMZQHplupg3XvHGwtBL23Vpqho7Y6G0Wa/Yi2ZFsgwDYx6akUYYI3dBN2Jug3ihZ4MEiMFVOB/bP54mo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MzVrkJT5; arc=none smtp.client-ip=209.85.222.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MzVrkJT5" Received: by mail-qk1-f175.google.com with SMTP id af79cd13be357-90d2acb9936so169342585a.0 for ; Sun, 17 May 2026 08:56:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779033417; x=1779638217; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=8CSBACOO5uZiUL3Bifh+26rI7UtfDamwviXzCgkFmj0=; b=MzVrkJT5tSQMBAfNL/W7XlswDCVgW/qfWWjCrMkH+Ek5WTGt+KyrNdydI+LzcCoA85 j/A9BF+E1RRNtFZY6NcyizFcGjfBA/9dDSU4JPwRfVuFp+aodvCiTCWG0n76vrs2sYOk POk6culATJyL6Cb32io0GxKbXYqtjoYdK0ewMppmcrAwZuxTGoMP7bPwPq+ilChyLrQ5 71CeLsf5nihx9SKIjDIybWz4ujIzoH4eExbbvXO5QhkXjKll1qHfMr7xDQJ9oiC4bEIm UuyQnGDKB7R8v6CKHEs+cEz+RkCY/pAz50jl3TLXuSZ/Kl8FJT2H8PSS3vMUv0njlMH0 Fhsg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779033417; x=1779638217; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=8CSBACOO5uZiUL3Bifh+26rI7UtfDamwviXzCgkFmj0=; b=oIDNS6iduezoZoLCNIjX+U+fQ2HXDDqcKXvbS61artf/d5rLCB75qTxULnVDMs+uYN ptTjLkEs5tJajXykz0OTuPIknBQ+e4L6BYCNKsdtyJgaKxR5WcT4ib4urQIYiJSTqg80 fEg/8ZFM//91JtYmH4nSmYNDAGEF86JR/5ab5H26yJRxJjXwRBCE0ONIc1tKCog6dTIa wybRdByWaUPtpIAQcma/iJsU4+hl2yGPyfC6PXZLUWx7tWzPqvHHCXnfnMc7U7FsDM1m IXBldOtyYtNEApdr5DTF9sX1nCAm0eekULB6JB4n54Fnx8RfRMPDmShRqmEd3qU0r4Nf otFQ== X-Forwarded-Encrypted: i=1; AFNElJ901e/xElXxRSUsblPrx1ra+LPzTOPkmwFeBilfGLmPq/yHCFTLiB3RlwelmuB+rn2X33DTuDQzuitW+fU=@vger.kernel.org X-Gm-Message-State: AOJu0Yy4SRsTZSf0KPqrKETiSHkwqwHzjRgjonlWFtjUrqAQXKwhZM3W PsZfwBklsP6oyq/HfCMTgeAattSy281O23MTGWUgCna58wk3g1GrYr/u X-Gm-Gg: Acq92OFHpaQdcZ3rsbx6N4ZBSK0ftEFrPxL2qopi1S4QMB9FAG/w9jpT/ygP0TbYAIp VB2KztlUtX0CYfx9IgOsD45w7OY09QQ6Uwy36TWMIF/yEzmfNH5dlwL/8aOMsGALAMKd1pzM+GG Xpdbvfrj12SwP9/ZbaFl9u9K1G0PIWcpktEus58gk1cGUvm+rBbghV+HgZFE0qbQPHsxnC+zV7h qHWHa1Fh7YEbJfdNkfTIkiyD0PcFKG2h78mhzO0WZVeeZs/NZPDXUEvVKhhuM1KiCwwpxtQmIpA Y70O8kpBwZnJr3NXVMc8TtT8XejcQ4siBojrDW6TvEDUeT79DQgs1VxtV1MAULevmEiasFbyO57 NpdVxwtkV2Z6AYT3CjlbgGMdDHwl6ZtbVSsq9ItkfwMBKHt0D071V2zUVLNzYb24WojpZx4KGpH y8wroHp6Vof58uag0hst0EZvCULyHGwPY4fqKRjdeWrDDHUalAsB3j+vk8gPc7ulFCgMnmlYeJN w8CxKhuLQT00EsgT2TGJsR1KndlMw/Qa7gznnJWE3w= X-Received: by 2002:a05:620a:179f:b0:90f:786c:895a with SMTP id af79cd13be357-911ce71c487mr1796840285a.32.1779033416927; Sun, 17 May 2026 08:56:56 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id af79cd13be357-910ba463814sm1202618085a.5.2026.05.17.08.56.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 17 May 2026 08:56:56 -0700 (PDT) From: Michael Bommarito To: Antonio Quartulli Cc: Sabrina Dubroca , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH net] net: ovpn: refuse TCP socket layering with an active ULP Date: Sun, 17 May 2026 11:56:45 -0400 Message-ID: <20260517155645.3882533-1-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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=3Dtls), 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 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 #include #include +#include #include #include #include @@ -210,14 +211,29 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_= sock) { struct ovpn_peer *peer =3D ovpn_sock->peer; struct sock *sk =3D ovpn_sock->sk; + bool has_ulp; =20 strp_stop(&peer->tcp.strp); skb_queue_purge(&peer->tcp.user_queue); =20 - /* restore CBs that were saved in ovpn_sock_set_tcp_cb() */ - sk->sk_data_ready =3D peer->tcp.sk_cb.sk_data_ready; - sk->sk_write_space =3D peer->tcp.sk_cb.sk_write_space; - sk->sk_prot =3D 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 =3D inet_csk_has_ulp(sk); + if (!has_ulp) { + /* restore CBs that were saved in ovpn_sock_set_tcp_cb() */ + sk->sk_data_ready =3D peer->tcp.sk_cb.sk_data_ready; + sk->sk_write_space =3D peer->tcp.sk_cb.sk_write_space; + sk->sk_prot =3D peer->tcp.sk_cb.prot; + } =20 /* 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_so= ck) * 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 =3D peer->tcp.sk_cb.ops; write_unlock_bh(&sk->sk_callback_lock); =20 @@ -518,6 +534,17 @@ int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_so= ck, /* 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); =20 /* only a fully connected socket is expected. Connection should be @@ -583,6 +610,26 @@ static void ovpn_tcp_close(struct sock *sk, long timeo= ut) sock =3D 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 =3D=3D AF_INET6) + tcpv6_prot.close(sk, timeout); + else +#endif + tcp_prot.close(sk, timeout); return; } peer =3D sock->peer; base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731 --=20 2.53.0