ovpn: validate peer VPN addresses
Subsystems (4)
Ralf Lici
reviewerv2 cover ovpn: validate peer VPN addresses
Hi,
This is a respin of the series that enforces explicit policy rules for VPN addresses, taking into account Sabrina's suggestions.
In MP mode, ovpn uses peer VPN addresses as lookup keys when selecting the peer for an outgoing packet. The peer configuration path should therefore maintain a few basic invariants around those addresses.
This series ensures that cleared addresses are removed from the hash tables, prevents the same address from being assigned to multiple peers, requires every MP peer to have at least one configured VPN address, and rejects addresses that cannot identify a unicast peer.
The final patch extends the existing selftests to exercise these rules through both peer creation and update, including transitions between single-stack and dual-stack configurations.
Regards,
Ralf Lici
Mandelbit Srl
---
Changes since v1 https://lore.kernel.org/openvpn-devel/[email protected]/
- Rebased on net/main.
- Reworded 2/5 and 3/5 to explain why the newly rejected configurations
were never usable/reliable. (Sabrina)
- Refactored 2/5 by splitting ovpn_peer_vpn_addr_conflict into two
separate helpers for v4 and v6. (Sabrina)
- Switched to != htonl(INADDR_ANY) for consistency, rather than !s_addr
in 3/5. (Sabrina)
Ralf Lici (5):
ovpn: always unhash old VPN addresses before rehashing
ovpn: reject duplicate peer VPN addresses
ovpn: reject multipeer peers without VPN addresses
ovpn: reject invalid peer VPN addresses
selftests: ovpn: validate peer VPN addresses
drivers/net/ovpn/netlink.c | 102 +++++++++++++++++--- drivers/net/ovpn/peer.c | 77 +++++++++++++-- drivers/net/ovpn/peer.h | 6 ++ tools/testing/selftests/net/ovpn/common.sh | 13 +++ tools/testing/selftests/net/ovpn/ovpn-cli.c | 54 +++++++---- tools/testing/selftests/net/ovpn/test.sh | 75 +++++++++++++- 6 files changed, 287 insertions(+), 40 deletions(-)
base-commit: 1b78070aaef63512688aebfbc82365ef9d6660f1
Signature · 8 lines
2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v2 1/5 ovpn: always unhash old VPN addresses before rehashing
ovpn_peer_hash_vpn_ip updates the per-peer VPN address hash entries after userspace changes a peer VPN address. The current code removes an old hash entry only when the new address for that family is not the unspecified address.
When an address is cleared to 0.0.0.0 or ::, its hash node therefore remains linked in the bucket selected by the old address. The address comparison performed during lookup prevents the old address from matching, but the table retains a stale entry until the peer is removed or another address is configured for that family.
Always remove both old VPN address hash entries before conditionally adding the currently configured addresses back. This ensures that a cleared address leaves its hash node unhashed.
---
No changes since v1 https://lore.kernel.org/openvpn-devel/e15d597b1c6d0e98727f482113b51f86a8510c50.1785338921.git.ralf@mandelbit.com/
drivers/net/ovpn/peer.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-)
drivers/net/ovpn/peer.c +4 -6
@@ -990,10 +990,11 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *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); +/* remove potential old hashing */ +hlist_nulls_del_init_rcu (&peer->hash_entry_addr4); +hlist_nulls_del_init_rcu (&peer->hash_entry_addr6); +if (peer->vpn_addrs.ipv4.s_addr !=htonl (INADDR_ANY )) { nhead =ovpn_get_hash_head (peer->ovpn->peers->by_vpn_addr4, &peer->vpn_addrs.ipv4,sizeof (peer->vpn_addrs.ipv4));
@@ -1001,9 +1002,6 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) }if (!ipv6_addr_any (&peer->vpn_addrs.ipv6)) { -/* remove potential old hashing */ -hlist_nulls_del_init_rcu (&peer->hash_entry_addr6); - nhead =ovpn_get_hash_head (peer->ovpn->peers->by_vpn_addr6, &peer->vpn_addrs.ipv6,sizeof (peer->vpn_addrs.ipv6));
Signature · 8 lines
2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v2 2/5 ovpn: reject duplicate peer VPN addresses
In reply to Ralf LiciIn 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.
---
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(-)
drivers/net/ovpn/netlink.c +29 -8
@@ -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,
drivers/net/ovpn/peer.c +66 -1
85 lines not on this page · raw message
drivers/net/ovpn/peer.h +6 -0
@@ -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,
Signature · 8 lines
2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v2 3/5 ovpn: reject multipeer peers without VPN addresses
In reply to Ralf LiciIn MP mode, ovpn uses the peer VPN addresses to select the peer for outgoing tunnel packets. Peer creation currently requires a VPN IPv4 or IPv6 attribute, but it only checks for the presence of the attribute and not for a usable address value.
This allows userspace to create an MP peer with only unspecified VPN addresses, or to update an existing peer so that both VPN address families become unspecified. Such a peer cannot be selected through the VPN address hash tables.
Reject MP peer creation or update when the resulting peer would not have at least one VPN address configured.
This changes such configurations from being accepted to being rejected, but they have never been usable because the peer cannot be selected through the VPN address hash tables.
---
Changes since v1 https://lore.kernel.org/openvpn-devel/17ced9a7caee691e602e3c02f5e399aa9a35067c.1785338921.git.ralf@mandelbit.com/
- Explicitly stated that the policy introduced by this commit is not
breaking userspace. (Sabrina)
- Used htonl(INADDR_ANY) instead of directly comparing s_addr. (Sabrina)
drivers/net/ovpn/netlink.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-)
drivers/net/ovpn/netlink.c +30 -6
@@ -346,8 +346,10 @@ static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *info,int ovpn_nl_peer_new_doit (struct sk_buff *skb,struct genl_info *info) { -struct nlattr *attrs[OVPN_A_PEER_MAX +1 ]; +struct in_addr vpn_addr4 = { .s_addr =htonl (INADDR_ANY ) }; +struct in6_addr vpn_addr6 =IN6ADDR_ANY_INIT ;struct ovpn_priv *ovpn = info->user_ptr[0 ]; +struct nlattr *attrs[OVPN_A_PEER_MAX +1 ];struct ovpn_socket *ovpn_sock;struct socket *sock =NULL ;struct ovpn_peer *peer;
@@ -371,11 +373,20 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info)return -EINVAL ;/* in MP mode VPN IPs are required for selecting the right peer */ -if (ovpn->mode ==OVPN_MODE_MP && !attrs[OVPN_A_PEER_VPN_IPV4 ] && - !attrs[OVPN_A_PEER_VPN_IPV6 ]) { -NL_SET_ERR_MSG_FMT_MOD (info->extack, -"VPN IP must be provided in MP mode" ); -return -EINVAL ; +if (ovpn->mode ==OVPN_MODE_MP ) { +if (attrs[OVPN_A_PEER_VPN_IPV4 ]) + vpn_addr4.s_addr = +nla_get_in_addr (attrs[OVPN_A_PEER_VPN_IPV4 ]); +if (attrs[OVPN_A_PEER_VPN_IPV6 ]) + vpn_addr6 = +nla_get_in6_addr (attrs[OVPN_A_PEER_VPN_IPV6 ]); + +if (vpn_addr4.s_addr ==htonl (INADDR_ANY ) && +ipv6_addr_any (&vpn_addr6)) { +NL_SET_ERR_MSG_FMT_MOD (info->extack, +"at least one VPN IP must be configured in MP mode" ); +return -EINVAL ; + } } peer_id =nla_get_u32 (attrs[OVPN_A_PEER_ID ]);
@@ -525,6 +536,9 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info)spin_lock_bh (&ovpn->lock); + vpn_addr4 = peer->vpn_addrs.ipv4; + vpn_addr6 = peer->vpn_addrs.ipv6; +/* 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 ]);
@@ -537,6 +551,16 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) goto addr_conflict; } +/* in MP mode VPN IPs are required for selecting the right peer */ +if (ovpn->mode ==OVPN_MODE_MP && + vpn_addr4.s_addr ==htonl (INADDR_ANY ) && +ipv6_addr_any (&vpn_addr6)) { +NL_SET_ERR_MSG_FMT_MOD (info->extack, +"at least one VPN IP must be configured in MP mode" ); + ret = -EINVAL ; + goto unlock; + } + ret =ovpn_nl_peer_modify (peer, info, attrs);if (ret <0 ) goto unlock;
Signature · 8 lines
2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v2 4/5 ovpn: reject invalid peer VPN addresses
In reply to Ralf LiciIn MP mode, ovpn uses peer VPN addresses as lookup keys for selecting the peer that should receive outgoing tunnel packets. The netlink configuration path currently accepts address values that cannot sensibly identify a VPN peer, such as multicast, broadcast or loopback addresses.
Reject invalid peer VPN addresses when creating or updating an MP peer. Keep accepting the unspecified address as the internal unset value, provided that at least one VPN address family remains configured.
---
Changes since v1 https://lore.kernel.org/openvpn-devel/934f840c132e350415369712806b3d9d77957ca5.1785338921.git.ralf@mandelbit.com/
- Adapted to changes in 2/5 and 3/5; no behavioral changes.
drivers/net/ovpn/netlink.c | 55 +++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 13 deletions(-)
drivers/net/ovpn/netlink.c +42 -13
@@ -179,6 +179,39 @@ static sa_family_t ovpn_nl_family_get(struct nlattr *addr4,return AF_UNSPEC ; } +static int ovpn_nl_peer_check_vpn_addrs (const struct in_addr *addr4, +const struct in6_addr *addr6, +struct genl_info *info) +{ +int addr6_type; + +if (addr4->s_addr ==htonl (INADDR_ANY ) &&ipv6_addr_any (addr6)) { +NL_SET_ERR_MSG_MOD (info->extack, +"at least one VPN IP must be configured in MP mode" ); +return -EINVAL ; + } + +if (ipv4_is_multicast (addr4->s_addr) ||ipv4_is_lbcast (addr4->s_addr) || +ipv4_is_loopback (addr4->s_addr)) { +NL_SET_ERR_MSG_MOD (info->extack, +"VPN IPv4 address must be valid unicast or any" ); +return -EADDRNOTAVAIL ; + } + +if (!ipv6_addr_any (addr6)) { + addr6_type =ipv6_addr_type (addr6); + +if (!(addr6_type &IPV6_ADDR_UNICAST ) || + (addr6_type & (IPV6_ADDR_LOOPBACK | IPV6_ADDR_COMPATv4))) { +NL_SET_ERR_MSG_MOD (info->extack, +"VPN IPv6 address must be valid unicast or any" ); +return -EADDRNOTAVAIL ; + } + } + +return 0 ; +} +static int ovpn_nl_peer_precheck (struct ovpn_priv *ovpn,struct genl_info *info,struct nlattr **attrs)
@@ -381,12 +414,10 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) vpn_addr6 =nla_get_in6_addr (attrs[OVPN_A_PEER_VPN_IPV6 ]); -if (vpn_addr4.s_addr ==htonl (INADDR_ANY ) && -ipv6_addr_any (&vpn_addr6)) { -NL_SET_ERR_MSG_FMT_MOD (info->extack, -"at least one VPN IP must be configured in MP mode" ); -return -EINVAL ; - } + ret =ovpn_nl_peer_check_vpn_addrs (&vpn_addr4, &vpn_addr6, + info); +if (ret <0 ) +return ret; } peer_id =nla_get_u32 (attrs[OVPN_A_PEER_ID ]);
@@ -552,13 +583,11 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) }/* in MP mode VPN IPs are required for selecting the right peer */ -if (ovpn->mode ==OVPN_MODE_MP && - vpn_addr4.s_addr ==htonl (INADDR_ANY ) && -ipv6_addr_any (&vpn_addr6)) { -NL_SET_ERR_MSG_FMT_MOD (info->extack, -"at least one VPN IP must be configured in MP mode" ); - ret = -EINVAL ; - goto unlock; +if (ovpn->mode ==OVPN_MODE_MP ) { + ret =ovpn_nl_peer_check_vpn_addrs (&vpn_addr4, &vpn_addr6, + info); +if (ret <0 ) + goto unlock; } ret =ovpn_nl_peer_modify (peer, info, attrs);
Signature · 8 lines
2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v2 5/5 selftests: ovpn: validate peer VPN addresses
In reply to Ralf LiciExercise peer VPN address validation through both peer creation and update. Check missing, unspecified, duplicate, multicast, broadcast, loopback, IPv4-compatible and IPv4-mapped addresses.
Temporarily configure a peer with both address families to verify that either family can be cleared while the other remains configured, then restore the original addresses before running the existing traffic tests.
Extend ovpn-cli peer updates with an optional VPN address and preserve peer creation errors so the negative tests can observe rejected netlink requests.
---
No changes since v1 https://lore.kernel.org/openvpn-devel/7b19fd9eb8d2c2a4afc4a8d102bdfeeeec8024bf.1785338921.git.ralf@mandelbit.com/
tools/testing/selftests/net/ovpn/common.sh | 13 ++++ tools/testing/selftests/net/ovpn/ovpn-cli.c | 54 ++++++++++----- tools/testing/selftests/net/ovpn/test.sh | 75 ++++++++++++++++++++- 3 files changed, 123 insertions(+), 19 deletions(-)
tools/testing/selftests/net/ovpn/common.sh +13 -0
@@ -136,6 +136,19 @@ ovpn_create_ns() { ip netns add "ovpn_peer${1}" } +ovpn_peer_vpn_addr() { + local peer="$1" + local file + + if [ "${OVPN_PROTO}" == "UDP" ]; then + file="${OVPN_UDP_PEERS_FILE}" + else + file="${OVPN_TCP_PEERS_FILE}" + fi + + awk -v peer="${peer}" '$1 == peer {print $NF; exit}' "${file}" +} + ovpn_setup_ns() { local peer="ovpn_peer${1}" local server_ns="ovpn_peer0"
tools/testing/selftests/net/ovpn/ovpn-cli.c +37 -17
96 lines not on this page · raw message
tools/testing/selftests/net/ovpn/test.sh +73 -2
94 lines not on this page · raw message
Signature · 8 lines
2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Antonio Quartulli
AppliedFrom: Antonio Quartulli <[email protected]>
Quoted 8 lines · Ralf Lici
This is a respin of the series that enforces explicit policy rules for VPN addresses, taking into account Sabrina's suggestions.
In MP mode, ovpn uses peer VPN addresses as lookup keys when selecting the peer for an outgoing packet. The peer configuration path should therefore maintain a few basic invariants around those addresses.
[...]
Applied, thanks!
[1/5] ovpn: always unhash old VPN addresses before rehashing
(no commit info)
[2/5] ovpn: reject duplicate peer VPN addresses
(no commit info)
[3/5] ovpn: reject multipeer peers without VPN addresses
(no commit info)
[4/5] ovpn: reject invalid peer VPN addresses
(no commit info)
[5/5] selftests: ovpn: validate peer VPN addresses
(no commit info)
Best regards,
Signature · 7 lines
Antonio Quartulli <[email protected]> _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel