From patchwork Fri Aug 28 13:00:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5290 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:2a5c:b0:87d:ab56:3700 with SMTP id l28csp1142040maz; Fri, 28 Aug 2026 06:01:15 -0700 (PDT) X-Forwarded-Encrypted: i=2; AHgh+RrbLK5lxwXd9pbe3uLYTdAMRdxAUuoCSechf0CkQ6IZVH0IfPeJOt9GEVZdHrSVtVj02qZjMxEoqQk=@openvpn.net X-Received: by 2002:a05:6870:c1d0:b0:455:9a0a:f40f with SMTP id 586e51a60fabf-46836af1e30mr7034365fac.12.1787922074791; Fri, 28 Aug 2026 06:01:14 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1787922074; cv=none; d=google.com; s=arc-20260327; b=JGZu4tY1arP0kVCAJsKc1QwGvrsSaCRcuuprXgbGVM4xUfBwFy9cApKTiof/d+c78Z p5PMe/3lg0nAszj7wtHUbwwnKITqLASGIBgmwj+n5so4mX+4lP9tx1fqfOY7Vsstoj4m ZxgZSPavoZYOzXFLS6VPmmTECT35YoxzSW45g2l9gGs5p3HqFIHqDDLFm83hZICTp7KF kbB8Q6Uhhn5nzVVV2ro2mzwRDaAU2Tv+LFggodFawB4k+KIIQ8O9/lGFjKUf5OC/m0ff VR+QY2PHb2EUP3yndUgkd3G4aoKhuhpwuJwV+BkxAhfrjawm9+NmRUSHKqoY5QU94UDs a6Qw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=yvMTJ++0LDskAUkMKnffCKtqHDawJMKC1+FPfB30cUk=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=XMCZoDNDOFQguVVN1X5QYgksJTvAQgf7Uh/9nrEjq0GdEJVqjCcMM00mhA4mFC6l/y 1pGL/wu16p0CAd1tHcJB/RfKnFQDWu5g/816CRMSAdTIuIWyE7oKAbvWA38RgWP7Zeva Qs6R4vsXaPO5YB57uNqc3wx3ynbPDubK9Y+pdq77Nx4i2YejaMLtzf9yF8Mu96NZ6xfi JijYGvw7aI0zJZG1PZ/b/T4SRHKdAp2IFjLzASult+j6AjxOhTBtwwz64ItRfPzY+PVX pS/nMozw04KlFZnDNcHa3ri9OMeGcUmmNA4FxNZ931JieicTHHmD8XGmbU4PjL2zD80Y Pn7Q==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=Vg0aEMed; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=HGYU3rs8; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=mxjlTvj5; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=HSz0i8XL; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-468a69c78c4si1761037fac.361.2026.08.28.06.01.14 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 28 Aug 2026 06:01:14 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=Vg0aEMed; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=HGYU3rs8; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=mxjlTvj5; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=HSz0i8XL; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=yvMTJ++0LDskAUkMKnffCKtqHDawJMKC1+FPfB30cUk=; b=Vg0aEMednGFIUPhFaNOfiRVP97 mBF8bRjEQ5TfQI1mn/XGqsG27XgQhPNr5bGTAh9ixDPuUh+bW9XZTyqGz9R/7mZM7fn615mIJQ4Q4 qqhFv+3EzCe+qn6H43gio4jXMlQwFiSgG5rcLa99arnSq9SMyMFOfyafLl7jAiXDq9ro=; Received: from [127.0.0.1] (helo=sfs-ml-3.v29.lw.sourceforge.com) by sfs-ml-3.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wzwCj-0001bD-He; Fri, 28 Aug 2026 13:01:10 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-3.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wzwCM-0001aG-JE for openvpn-devel@lists.sourceforge.net; Fri, 28 Aug 2026 13:00:48 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=KtdulZmEFhCpglK28MwKNKSoL519haptelaHqlRGy7M=; b=HGYU3rs8OhusgMINLQ2uTkZuxF 3OPaICQ8Hz7sR2GOOKU8uPgkPCfXaYmu/gemRBCD80QRL6ACCXwyikwOglQ4HnUlU2YC9VYhrXaSD p5I1akLJg6Jgs5ulsJbVLAayXlZfRPBChvLYxPRV8yvRSzT1V+4NMPtuXzyguRhjF8F4=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=KtdulZmEFhCpglK28MwKNKSoL519haptelaHqlRGy7M=; b=mxjlTvj5dYU92mxo8NXffo5+DY GQF/xej3zd9RFR50Ypl+SQc5MH2Cm2b0gSUDVQ5vd4EP5PEXTTvfWMV4G/ogps91WQj3cC2170ze2 SFdNzfiY+lE+RBSFjuxM4feXV97K/BJdvvfhycse+RKcjARuYjW5l1Xn+fAk3x4nQIGM=; Received: from mout-b-206.mailbox.org ([195.10.208.51]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wzwCB-0005bL-Ge for openvpn-devel@lists.sourceforge.net; Fri, 28 Aug 2026 13:00:37 +0000 Received: from smtp102.mailbox.org (smtp102.mailbox.org [IPv6:2001:67c:2050:b231:465::102]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-206.mailbox.org (Postfix) with ESMTPS id 4hWdlv0YtyzHj; Fri, 28 Aug 2026 15:00:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1787922027; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=KtdulZmEFhCpglK28MwKNKSoL519haptelaHqlRGy7M=; b=HSz0i8XLNuFCq+zt0BXSfyjLANxfcLsOZUmlco5F8EPUGx/cvXDNha+LNX3HalivW2mMym ec5JhDh45inv42rcd4WHPoFnq9YxUqJETq9cPoBFeI+gEm5ch4VF9+MdNeWmZhh8XJMvDE Na37oH9Oaydbpj+9idSjq0/+St/k6EkaybQVAuniM7Um/gf9rfPnzO/hlXkfW83DsG8+Jq fmXqDuUHft12i26Pc6mNK07yW619j3fatSUgG2/SeIqUNiGai8JNTcrhtsGLGir3soBM4X P1KeIqqOHNdklf5Hxa4KwojwYRUZZgB6llxjCgI3wiVmkE1zsrcKbMYw77W/PQ== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::102 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Fri, 28 Aug 2026 15:00:07 +0200 Message-ID: <3da9a7b4d938b88f7c5cad9127bdffa5c79fcfbb.1787919082.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4hWdlv0YtyzHj X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: In MP mode, ovpn uses the peer VPN addresses as lookup keys for selecting the peer that should receive an outgoing tunnel packet. However, the netlink peer configuration path does not currently reject [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [195.10.208.51 listed in wl.mailspike.net] -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid X-Headers-End: 1wzwCB-0005bL-Ge Subject: [Openvpn-devel] [PATCH ovpn net v2 2/5] ovpn: reject duplicate peer VPN addresses X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1874772177512789791 X-GMAIL-MSGID: 1874772177512789791 In MP mode, ovpn uses the peer VPN addresses as lookup keys for selecting the peer that should receive an outgoing tunnel packet. However, the netlink peer configuration path does not currently reject duplicate VPN addresses. If two peers are configured with the same VPN address, both can be inserted in the VPN address hash table and lookups return whichever peer is found first. This makes peer selection ambiguous and dependent on hash insertion order. Reject peer creation or update when the resulting VPN address is already assigned to another peer. Ignore unspecified addresses because those are not inserted in the VPN address hash tables. This changes such configurations from being accepted to being rejected, but they have never worked reliably because peer selection is ambiguous. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici --- Changes since v1 https://lore.kernel.org/openvpn-devel/872f401e31dae10388fe65b45463c5c55b96ac86.1785338921.git.ralf@mandelbit.com/ - Explicitly stated that the policy introduced by this commit is not breaking userspace. (Sabrina) - Split ovpn_peer_vpn_addr_conflict into two helpers, one for v4 and one for v6. (Sabrina) drivers/net/ovpn/netlink.c | 37 ++++++++++++++++----- drivers/net/ovpn/peer.c | 67 +++++++++++++++++++++++++++++++++++++- drivers/net/ovpn/peer.h | 6 ++++ 3 files changed, 101 insertions(+), 9 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 4dad85294198..a444930234b6 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -474,8 +474,10 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; struct ovpn_priv *ovpn = info->user_ptr[0]; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct in6_addr vpn_addr6; + struct in_addr vpn_addr4; struct ovpn_socket *sock; struct ovpn_peer *peer; u32 peer_id; @@ -522,28 +524,47 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) rcu_read_unlock(); spin_lock_bh(&ovpn->lock); - ret = ovpn_nl_peer_modify(peer, info, attrs); - if (ret < 0) { - spin_unlock_bh(&ovpn->lock); - ovpn_peer_put(peer); - return ret; + + /* reject peer with conflicting VPN address */ + if (attrs[OVPN_A_PEER_VPN_IPV4]) { + vpn_addr4.s_addr = nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); + if (ovpn_peer_vpn_addr_conflict4(ovpn, peer, &vpn_addr4)) + goto addr_conflict; } + if (attrs[OVPN_A_PEER_VPN_IPV6]) { + vpn_addr6 = nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); + if (ovpn_peer_vpn_addr_conflict6(ovpn, peer, &vpn_addr6)) + goto addr_conflict; + } + + ret = ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) + goto unlock; /* ret == 1 means that VPN IPv4/6 has been modified and rehashing * is required */ - if (ret > 0) + if (ret > 0) { ovpn_peer_hash_vpn_ip(peer); + ret = 0; + } /* if the remote endpoint was updated, the by_transp_addr hash bucket * also needs to be refreshed, otherwise incoming packets from the new * remote address would fail the lockless lookup */ if (attrs[OVPN_A_PEER_REMOTE_IPV4] || attrs[OVPN_A_PEER_REMOTE_IPV6]) ovpn_peer_hash_transp_addr(peer); + +unlock: spin_unlock_bh(&ovpn->lock); ovpn_peer_put(peer); - return 0; + return ret; +addr_conflict: + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "VPN IP is already assigned to another peer"); + ret = -EADDRINUSE; + goto unlock; } static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info, diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 68a2b05689ef..da456981bc67 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -484,7 +484,7 @@ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr4(struct ovpn_priv *ovpn, * Return: the peer if found or NULL otherwise */ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_priv *ovpn, - struct in6_addr *addr) + const struct in6_addr *addr) { struct hlist_nulls_head *nhead; struct hlist_nulls_node *ntmp; @@ -509,6 +509,64 @@ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_priv *ovpn, return NULL; } +/** + * ovpn_peer_vpn_addr_conflict4 - check if the VPN v4 address is already in use + * @ovpn: the openvpn instance to search + * @peer: peer being added or updated, or NULL + * @addr: VPN IPv4 address to check + * + * Check whether @addr is already assigned to another peer. @peer is ignored + * when found, allowing peer updates that keep an existing address. + * Unspecified addresses are ignored. + * + * Note: the caller must hold @ovpn->lock. + * + * Return: true on conflict, false otherwise. + */ +bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in_addr *addr) +{ + struct ovpn_peer *tmp = NULL; + + lockdep_assert_held(&ovpn->lock); + + /* we don't hash INADDR_ANY, no conflict in that case */ + if (addr->s_addr != htonl(INADDR_ANY)) + tmp = ovpn_peer_get_by_vpn_addr4(ovpn, addr->s_addr); + + return tmp && tmp != peer; +} + +/** + * ovpn_peer_vpn_addr_conflict6 - check if the VPN v6 address is already in use + * @ovpn: the openvpn instance to search + * @peer: peer being added or updated, or NULL + * @addr: VPN IPv6 address to check + * + * Check whether @addr is already assigned to another peer. @peer is ignored + * when found, allowing peer updates that keep an existing address. + * Unspecified addresses are ignored. + * + * Note: the caller must hold @ovpn->lock. + * + * Return: true on conflict, false otherwise. + */ +bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in6_addr *addr) +{ + struct ovpn_peer *tmp = NULL; + + lockdep_assert_held(&ovpn->lock); + + /* we don't hash ::, no conflict in that case */ + if (!ipv6_addr_any(addr)) + tmp = ovpn_peer_get_by_vpn_addr6(ovpn, addr); + + return tmp && tmp != peer; +} + /** * ovpn_peer_transp_match - check if sockaddr and peer binding match * @peer: the peer to get the binding from @@ -1036,6 +1094,13 @@ static int ovpn_peer_add_mp(struct ovpn_priv *ovpn, struct ovpn_peer *peer) goto out; } + /* reject peer with conflicting VPN address */ + if (ovpn_peer_vpn_addr_conflict4(ovpn, NULL, &peer->vpn_addrs.ipv4) || + ovpn_peer_vpn_addr_conflict6(ovpn, NULL, &peer->vpn_addrs.ipv6)) { + ret = -EADDRINUSE; + goto out; + } + bind = rcu_dereference_protected(peer->bind, true); /* peers connected via TCP have bind == NULL */ if (bind) { diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index dfa5c0037e02..a1cbd4013349 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -149,6 +149,12 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb); +bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in_addr *addr); +bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in6_addr *addr); void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer); void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer); bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb,