From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EAD45439F7F; Mon, 17 Aug 2026 13:51:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786974704; cv=none; b=JYLS/+H15rG+0KwmufmaAIVEVYXkj+sC/zZu8W1+I8s+jK0Zb9HjWhDL0UqCuWr6Sz4O3TZjFwI4lcKtO/+bUwkhW+HwjJmcRVH8jbCBQZgNOb73cGTib8/FLDEsHu9VK0CMkRLvnsQ5WnJLAwZjf5b+KjGhHMXcw09ooYARpI8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786974704; c=relaxed/simple; bh=bv+2ufAMCwRLeb28qnwRx1R1LT37zFALtKEZKf4Hiao=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aVJlf8t/U7DFVOx4sZ01+8ltKqUVMY+msFwzEdCUhoa5h5NHbl8AxIPMXpIA8Uhy50NR3QgJDPO3uOpdxfCd4uabFZ+mNC20latPzWSumqAYHpsz5D1zTPasmwkaGCeWZqxMgPA10PGHFMR3DLE6FpzUAYIb+/GLZxBY8/iUEx4= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=fbk9qxx/; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="fbk9qxx/" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D2FB11F00A3D; Mon, 17 Aug 2026 13:51:34 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1786974695; bh=KSJEe7zKczHveDgR7/dzq7TNqtG+AzF9Dhd0sdIcIxw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=fbk9qxx/j/rawmBArFnSIPMuJ1pUaqaJPK5y5hDyImXJBoR5lQfyEHDqtfMSSfw33 qbSWWLYiYo41zjDoRsleOy+JN+OjCf7E4NIsVZCfq0WX/yukUFK/HvEo7z+G2yXtLZ oMK6wU5BqAdj+5aojrj0fDM2jwSwmgb2JUFqXqTI= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Antonio Quartulli , Sasha Levin Subject: [PATCH 6.18 022/250] ovpn: skip rehash for peers already removed from by_id Date: Mon, 17 Aug 2026 15:29:43 +0200 Message-ID: <20260817132537.365010897@linuxfoundation.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817132536.466235697@linuxfoundation.org> References: <20260817132536.466235697@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 6.18-stable review patch. If anyone has any objections, please let me know. ------------------ From: Antonio Quartulli [ Upstream commit 33ec10567fe14456063daf549fdf1a4f53448e4c ] ovpn_nl_peer_set_doit() resolves the target peer via ovpn_peer_get_by_id() before taking ovpn->lock. In the window between the lookup (which only takes a refcount) and the subsequent spin_lock_bh(&ovpn->lock), a concurrent OVPN_CMD_PEER_DEL, keepalive expiry, or socket teardown can take ovpn->lock first, run ovpn_peer_remove() to unhash the peer from all four tables (by_id, by_vpn_addr4/6, by_transp_addr) and release the lock. set_doit then acquires ovpn->lock and calls ovpn_peer_hash_vpn_ip(), which re-inserts the now-removed peer back into the rehashing tables. The same race affects the float path: ovpn_peer_endpoints_update() holds only a refcount and acquires ovpn->lock very late (after async AEAD decrypt and a netlink notification), then rehashes the peer in the by_transp_addr table. The resurrected peer becomes reachable again from the RX lookup (ovpn_peer_get_by_transp_addr) and the TX VPN-IP lookup, even though userspace believes it is gone. Once the data-path refcount drops the peer is freed via call_rcu while the hash entries embedded in it remain linked, opening a UAF window. Bail out of the rehash when hash_entry_id is unhashed, mirroring the sentinel already used by ovpn_peer_remove() to detect the already-removed state. The check is safe under ovpn->lock, which serializes every mutation of hash_entry_id, and is a no-op for the add path because ovpn_peer_add_mp() inserts hash_entry_id before calling ovpn_peer_hash_vpn_ip(). Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Antonio Quartulli Signed-off-by: Sasha Levin --- drivers/net/ovpn/peer.c | 73 ++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 85fa9bd475f45..6b7e368f63f3a 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -291,40 +291,46 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) /* rehashing is required only in MP mode as P2P has one peer * only and thus there is no hashtable */ - if (peer->ovpn->mode == OVPN_MODE_MP) { - spin_lock_bh(&peer->ovpn->lock); - spin_lock_bh(&peer->lock); - bind = rcu_dereference_protected(peer->bind, - lockdep_is_held(&peer->lock)); - if (unlikely(!bind)) { - spin_unlock_bh(&peer->lock); - spin_unlock_bh(&peer->ovpn->lock); - return; - } + if (peer->ovpn->mode != OVPN_MODE_MP) + return; - /* This function may be invoked concurrently, therefore another - * float may have happened in parallel: perform rehashing - * using the peer->bind->remote directly as key - */ + spin_lock_bh(&peer->ovpn->lock); + spin_lock_bh(&peer->lock); + bind = rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + if (unlikely(!bind)) + goto unlock2; - switch (bind->remote.in4.sin_family) { - case AF_INET: - salen = sizeof(*sa); - break; - case AF_INET6: - salen = sizeof(*sa6); - break; - } + /* peer may have been concurrently removed between the caller's + * initial lookup and our acquisition of ovpn->lock; skip the + * rehash so we don't re-insert a removed peer + */ + if (unlikely(hlist_unhashed(&peer->hash_entry_id))) + goto unlock2; - /* remove old hashing */ - hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); - /* re-add with new transport address */ - nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, - &bind->remote, salen); - hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); - spin_unlock_bh(&peer->lock); - spin_unlock_bh(&peer->ovpn->lock); + /* This function may be invoked concurrently, therefore another + * float may have happened in parallel: perform rehashing + * using the peer->bind->remote directly as key + */ + + switch (bind->remote.in4.sin_family) { + case AF_INET: + salen = sizeof(*sa); + break; + case AF_INET6: + salen = sizeof(*sa6); + break; } + + /* remove old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); + /* re-add with new transport address */ + nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, + &bind->remote, salen); + hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); +unlock2: + spin_unlock_bh(&peer->lock); + spin_unlock_bh(&peer->ovpn->lock); return; unlock: spin_unlock_bh(&peer->lock); @@ -901,6 +907,13 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) if (peer->ovpn->mode != OVPN_MODE_MP) return; + /* peer may have been concurrently removed between the caller's + * initial lookup and our acquisition of ovpn->lock; skip the + * rehash so we don't re-insert a removed peer + */ + if (hlist_unhashed(&peer->hash_entry_id)) + return; + if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { /* remove potential old hashing */ hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); -- 2.53.0