From: Antonio Quartulli <[email protected]>
The cached local source address bind->local is updated in place on a
live, RCU-published ovpn_bind while holding peer->lock: the UDP output
error paths reset it (ovpn_udp4_output()/ovpn_udp6_output()) and the RX
float path learns it (ovpn_peer_endpoints_update()). The UDP TX fast
path and the netlink dump, however, read bind->local holding only
rcu_read_lock(), never peer->lock.
For bind->local.ipv6 this is a torn read: struct in6_addr is 128 bit and
is copied as multiple words, so a concurrent in-place update can make a
reader observe a mix of the old and new address. The mangled source
address then feeds ip6_dst_lookup_flow() and udp_tunnel6_xmit_skb(). For
bind->local.ipv4 (a single aligned word) it is a data race without
tearing.
A spinlock on the per-packet TX path is not acceptable, and
READ_ONCE()/WRITE_ONCE() cannot atomically access the 128-bit IPv6
address (the >8-byte access is rejected at build time and per-word
accesses still can't yield a consistent snapshot).
Serialize the IPv6 field with a per-peer seqcount_spinlock_t tied to the
existing peer->lock: the in-place writers (already under peer->lock) bump
it, and readers take a lock-free read_seqcount_begin()/retry() snapshot
via the new ovpn_peer_local_ipv6() helper. The single-word IPv4 field is
handled with plain READ_ONCE()/WRITE_ONCE(). bind->remote is untouched:
it is immutable for a given bind object (only swapped via whole-bind RCU
replacement), so reading it locklessly remains safe.
Fixes: 08857b5ec5d9 ("ovpn: implement basic TX path (UDP)")
Signed-off-by: Antonio Quartulli <[email protected]>
---
drivers/net/ovpn/netlink.c | 13 +++++++++++--
drivers/net/ovpn/peer.c | 26 +++++++++++++++++++++++++-
drivers/net/ovpn/peer.h | 6 ++++++
drivers/net/ovpn/udp.c | 17 +++++++++++++----
4 files changed, 55 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
index 4dad85294198..8e21fa3e7822 100644
--- a/drivers/net/ovpn/netlink.c
+++ b/drivers/net/ovpn/netlink.c
@@ -610,14 +610,23 @@ static int ovpn_nl_send_peer(struct sk_buff *skb, const
struct genl_info *info,
bind = rcu_dereference(peer->bind);
if (bind) {
if (bind->remote.in4.sin_family == AF_INET) {
+ /* bind->local is updated in place under peer->lock;
+ * READ_ONCE() pairs with the WRITE_ONCE() updaters
+ */
if (nla_put_in_addr(skb, OVPN_A_PEER_REMOTE_IPV4,
bind->remote.in4.sin_addr.s_addr) ||
nla_put_net16(skb, OVPN_A_PEER_REMOTE_PORT,
bind->remote.in4.sin_port) ||
nla_put_in_addr(skb, OVPN_A_PEER_LOCAL_IPV4,
- bind->local.ipv4.s_addr))
+ READ_ONCE(bind->local.ipv4.s_addr)))
goto err_unlock;
} else if (bind->remote.in4.sin_family == AF_INET6) {
+ struct in6_addr local_ipv6;
+
+ /* read the 128-bit local address under the peer
+ * seqcount to avoid a torn read
+ */
+ ovpn_peer_local_ipv6(peer, bind, &local_ipv6);
if (nla_put_in6_addr(skb, OVPN_A_PEER_REMOTE_IPV6,
&bind->remote.in6.sin6_addr) ||
nla_put_u32(skb, OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID,
@@ -625,7 +634,7 @@ static int ovpn_nl_send_peer(struct sk_buff *skb, const
struct genl_info *info,
nla_put_net16(skb, OVPN_A_PEER_REMOTE_PORT,
bind->remote.in6.sin6_port) ||
nla_put_in6_addr(skb, OVPN_A_PEER_LOCAL_IPV6,
- &bind->local.ipv6))
+ &local_ipv6))
goto err_unlock;
}
}
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 8aa07560bb30..bbb1946fa5b4 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -112,6 +112,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32
id)
RCU_INIT_POINTER(peer->bind, NULL);
ovpn_crypto_state_init(&peer->crypto);
spin_lock_init(&peer->lock);
+ seqcount_spinlock_init(&peer->bind_local_seq, &peer->lock);
kref_init(&peer->refcount);
ovpn_peer_stats_init(&peer->vpn_stats);
ovpn_peer_stats_init(&peer->link_stats);
@@ -175,6 +176,27 @@ int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer,
return 0;
}
+/**
+ * ovpn_peer_local_ipv6 - read the cached local IPv6 endpoint of a peer
+ * @peer: the peer owning the binding
+ * @bind: the binding to read the local address from
+ * @dst: where the local IPv6 address is copied to
+ *
+ * bind->local is updated in place under peer->lock (TX error path and RX
+ * float path). Read the 128-bit address under the peer seqcount so that
+ * lockless readers never observe a torn value.
+ */
+void ovpn_peer_local_ipv6(const struct ovpn_peer *peer,
+ const struct ovpn_bind *bind, struct in6_addr *dst)
+{
+ unsigned int seq;
+
+ do {
+ seq = read_seqcount_begin(&peer->bind_local_seq);
+ *dst = bind->local.ipv6;
+ } while (read_seqcount_retry(&peer->bind_local_seq, seq));
+}
+
/* variable name __tbl2 needs to be different from __tbl1
* in the macro below to avoid confusing clang
*/
@@ -237,7 +259,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer,
struct sk_buff *skb)
netdev_name(peer->ovpn->dev),
peer->id, &bind->local.ipv4.s_addr,
&ip_hdr(skb)->daddr);
- bind->local.ipv4.s_addr = ip_hdr(skb)->daddr;
+ WRITE_ONCE(bind->local.ipv4.s_addr, ip_hdr(skb)->daddr);
reset_cache = true;
}
break;
@@ -268,7 +290,9 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer,
struct sk_buff *skb)
netdev_name(peer->ovpn->dev),
peer->id, &bind->local.ipv6,
&ipv6_hdr(skb)->daddr);
+ write_seqcount_begin(&peer->bind_local_seq);
bind->local.ipv6 = ipv6_hdr(skb)->daddr;
+ write_seqcount_end(&peer->bind_local_seq);
reset_cache = true;
}
break;
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index dfa5c0037e02..c0994c606554 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -10,6 +10,7 @@
#ifndef _NET_OVPN_OVPNPEER_H_
#define _NET_OVPN_OVPNPEER_H_
+#include <linux/seqlock.h>
#include <net/dst_cache.h>
#include <net/strparser.h>
@@ -56,6 +57,8 @@
* @link_stats: per-peer link/transport TX/RX stats
* @delete_reason: why peer was deleted (i.e. timeout, transport error, ..)
* @lock: protects binding to peer (bind) and keepalive* fields
+ * @bind_local_seq: seqcount serializing in-place updates of bind->local
+ * (done under @lock) against lockless readers on the TX path
* @refcount: reference counter
* @rcu: used to free peer in an RCU safe way
* @release_entry: entry for the socket release list
@@ -110,6 +113,7 @@ struct ovpn_peer {
struct ovpn_peer_stats link_stats;
enum ovpn_del_peer_reason delete_reason;
spinlock_t lock; /* protects bind and keepalive* */
+ seqcount_spinlock_t bind_local_seq; /* protects bind->local */
struct kref refcount;
struct rcu_head rcu;
struct llist_node release_entry;
@@ -151,6 +155,8 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv
*ovpn,
struct sk_buff *skb);
void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer);
void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer);
+void ovpn_peer_local_ipv6(const struct ovpn_peer *peer,
+ const struct ovpn_bind *bind, struct in6_addr *dst);
bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb,
struct ovpn_peer *peer);
diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c
index 8811aa9eedeb..60d32dc5af4a 100644
--- a/drivers/net/ovpn/udp.c
+++ b/drivers/net/ovpn/udp.c
@@ -147,7 +147,10 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct
ovpn_bind *bind,
{
struct rtable *rt;
struct flowi4 fl = {
- .saddr = bind->local.ipv4.s_addr,
+ /* bind->local is updated in place under peer->lock; a single
+ * aligned word is read/written atomically via {READ,WRITE}_ONCE
+ */
+ .saddr = READ_ONCE(bind->local.ipv4.s_addr),
.daddr = bind->remote.in4.sin_addr.s_addr,
.fl4_sport = inet_sk(sk)->inet_sport,
.fl4_dport = bind->remote.in4.sin_port,
@@ -169,7 +172,7 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct
ovpn_bind *bind,
*/
fl.saddr = 0;
spin_lock_bh(&peer->lock);
- bind->local.ipv4.s_addr = 0;
+ WRITE_ONCE(bind->local.ipv4.s_addr, 0);
spin_unlock_bh(&peer->lock);
dst_cache_reset(cache);
}
@@ -178,7 +181,7 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct
ovpn_bind *bind,
if (IS_ERR(rt) && PTR_ERR(rt) == -EINVAL) {
fl.saddr = 0;
spin_lock_bh(&peer->lock);
- bind->local.ipv4.s_addr = 0;
+ WRITE_ONCE(bind->local.ipv4.s_addr, 0);
spin_unlock_bh(&peer->lock);
dst_cache_reset(cache);
@@ -224,7 +227,6 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct
ovpn_bind *bind,
int ret;
struct flowi6 fl = {
- .saddr = bind->local.ipv6,
.daddr = bind->remote.in6.sin6_addr,
.fl6_sport = inet_sk(sk)->inet_sport,
.fl6_dport = bind->remote.in6.sin6_port,
@@ -233,6 +235,11 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct
ovpn_bind *bind,
.flowi6_oif = bind->remote.in6.sin6_scope_id,
};
+ /* bind->local is updated in place under peer->lock; read the 128-bit
+ * address under the peer seqcount to avoid a torn read
+ */
+ ovpn_peer_local_ipv6(peer, bind, &fl.saddr);
+
local_bh_disable();
dst = dst_cache_get_ip6(cache, &fl.saddr);
if (dst)
@@ -245,7 +252,9 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct
ovpn_bind *bind,
*/
fl.saddr = in6addr_any;
spin_lock_bh(&peer->lock);
+ write_seqcount_begin(&peer->bind_local_seq);
bind->local.ipv6 = in6addr_any;
+ write_seqcount_end(&peer->bind_local_seq);
spin_unlock_bh(&peer->lock);
dst_cache_reset(cache);
}
--
2.53.0
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel