Included fixes:

* fix TCP selftest failures by reducing number of attempted pings
 * fix RCU ptr deref outside of RCU read section
 * fix UAF in case of TCP peer failed to be added to hashtable
 * fix race condition between iface teardown and new peer being added
 * ensure dstats are updated with BH disabled to avoid concurrency
 -----BEGIN PGP SIGNATURE-----
 
 iJEEABYIADkWIQQKU153ubb5unbkl6Gx/ZpNW1HNdwUCagZUrRsUgAAAAAAEAA5t
 YW51MiwyLjUrMS4xMiwyLDIACgkQsf2aTVtRzXcJ3gEAv0uFO2tjJha8KppS8Bpa
 3asGcAcFP686hMYpm5MjrmIA+wcWETrqwtzf1YcfjKyDCVNGVwrHSyQl5dIGNwGD
 nQgN
 =WFT+
 -----END PGP SIGNATURE-----

Merge tag 'ovpn-net-20260514' of https://github.com/OpenVPN/ovpn-net-next

Antonio Quartulli says:

====================
Included fixes:
* fix TCP selftest failures by reducing number of attempted pings
* fix RCU ptr deref outside of RCU read section
* fix UAF in case of TCP peer failed to be added to hashtable
* fix race condition between iface teardown and new peer being added
* ensure dstats are updated with BH disabled to avoid concurrency

* tag 'ovpn-net-20260514' of https://github.com/OpenVPN/ovpn-net-next:
  ovpn: disable BHs when updating device stats
  ovpn: fix race between deleting interface and adding new peer
  ovpn: respect peer refcount in CMD_NEW_PEER error path
  ovpn: tcp - use cached peer pointer in ovpn_tcp_close()
  selftests: ovpn: reduce remaining ping flood counts
====================

Link: https://patch.msgid.link/20260514231544.795993-1-antonio@openvpn.net
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-05-19 13:51:08 +02:00
commit 956aa1ec97
11 changed files with 67 additions and 38 deletions

View File

@ -201,7 +201,7 @@ void ovpn_decrypt_post(void *data, int ret)
skb = NULL;
drop:
if (unlikely(skb))
dev_dstats_rx_dropped(peer->ovpn->dev);
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
kfree_skb(skb);
drop_nocount:
if (likely(peer))
@ -225,7 +225,7 @@ void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb)
net_info_ratelimited("%s: no available key for peer %u, key-id: %u\n",
netdev_name(peer->ovpn->dev), peer->id,
key_id);
dev_dstats_rx_dropped(peer->ovpn->dev);
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
kfree_skb(skb);
ovpn_peer_put(peer);
return;
@ -301,7 +301,7 @@ void ovpn_encrypt_post(void *data, int ret)
rcu_read_unlock();
err:
if (unlikely(skb))
dev_dstats_tx_dropped(peer->ovpn->dev);
ovpn_dev_dstats_tx_dropped(peer->ovpn->dev);
if (likely(peer))
ovpn_peer_put(peer);
if (likely(ks))
@ -343,7 +343,7 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb,
*/
skb_list_walk_safe(skb, curr, next) {
if (unlikely(!ovpn_encrypt_one(peer, curr))) {
dev_dstats_tx_dropped(ovpn->dev);
ovpn_dev_dstats_tx_dropped(ovpn->dev);
kfree_skb(curr);
}
}
@ -414,7 +414,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
if (unlikely(!curr)) {
net_err_ratelimited("%s: skb_share_check failed for payload packet\n",
netdev_name(dev));
dev_dstats_tx_dropped(ovpn->dev);
ovpn_dev_dstats_tx_dropped(ovpn->dev);
continue;
}
@ -440,7 +440,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
drop:
ovpn_peer_put(peer);
drop_no_peer:
dev_dstats_tx_dropped(ovpn->dev);
ovpn_dev_dstats_tx_dropped(ovpn->dev);
skb_tx_error(skb);
kfree_skb_list(skb);
return NETDEV_TX_OK;

View File

@ -92,6 +92,8 @@ static void ovpn_net_uninit(struct net_device *dev)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
disable_delayed_work_sync(&ovpn->keepalive_work);
ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN);
gro_cells_destroy(&ovpn->gro_cells);
}
@ -208,15 +210,6 @@ static int ovpn_newlink(struct net_device *dev,
return register_netdevice(dev);
}
static void ovpn_dellink(struct net_device *dev, struct list_head *head)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
cancel_delayed_work_sync(&ovpn->keepalive_work);
ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN);
unregister_netdevice_queue(dev, head);
}
static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
@ -235,7 +228,6 @@ static struct rtnl_link_ops ovpn_link_ops = {
.policy = ovpn_policy,
.maxtype = IFLA_OVPN_MAX,
.newlink = ovpn_newlink,
.dellink = ovpn_dellink,
.fill_info = ovpn_fill_info,
};

View File

@ -462,10 +462,12 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info)
sock_release:
ovpn_socket_release(peer);
peer_release:
/* release right away because peer was not yet hashed, thus it is not
* used in any context
/* For UDP, the peer is unreachable until added to the hashtables, so
* dropping the initial reference is enough. For TCP, the peer may be
* concurrently reachable via sk_user_data->peer until
* ovpn_socket_release() detaches; rely on the refcount.
*/
ovpn_peer_release(peer);
ovpn_peer_put(peer);
return ret;
}

View File

@ -354,7 +354,7 @@ static void ovpn_peer_release_rcu(struct rcu_head *head)
* ovpn_peer_release - release peer private members
* @peer: the peer to release
*/
void ovpn_peer_release(struct ovpn_peer *peer)
static void ovpn_peer_release(struct ovpn_peer *peer)
{
ovpn_crypto_state_release(&peer->crypto);
spin_lock_bh(&peer->lock);
@ -1034,14 +1034,29 @@ static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
*/
int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
{
int ret = -ENODEV;
/* Prevent adding new peers while destroying the ovpn interface.
* Failing to do so would end up holding the device reference
* endlessly hostage of the new peer object with no chance of
* release..
*/
netdev_lock(ovpn->dev);
if (ovpn->dev->reg_state != NETREG_REGISTERED)
goto out;
switch (ovpn->mode) {
case OVPN_MODE_MP:
return ovpn_peer_add_mp(ovpn, peer);
ret = ovpn_peer_add_mp(ovpn, peer);
break;
case OVPN_MODE_P2P:
return ovpn_peer_add_p2p(ovpn, peer);
ret = ovpn_peer_add_p2p(ovpn, peer);
break;
}
out:
netdev_unlock(ovpn->dev);
return -EOPNOTSUPP;
return ret;
}
/**

View File

@ -127,7 +127,6 @@ static inline bool ovpn_peer_hold(struct ovpn_peer *peer)
return kref_get_unless_zero(&peer->refcount);
}
void ovpn_peer_release(struct ovpn_peer *peer);
void ovpn_peer_release_kref(struct kref *kref);
/**

View File

@ -11,6 +11,8 @@
#ifndef _NET_OVPN_OVPNSTATS_H_
#define _NET_OVPN_OVPNSTATS_H_
#include <linux/netdevice.h>
/* one stat */
struct ovpn_peer_stat {
atomic64_t bytes;
@ -44,4 +46,18 @@ static inline void ovpn_peer_stats_increment_tx(struct ovpn_peer_stats *stats,
ovpn_peer_stats_increment(&stats->tx, n);
}
static inline void ovpn_dev_dstats_tx_dropped(struct net_device *dev)
{
local_bh_disable();
dev_dstats_tx_dropped(dev);
local_bh_enable();
}
static inline void ovpn_dev_dstats_rx_dropped(struct net_device *dev)
{
local_bh_disable();
dev_dstats_rx_dropped(dev);
local_bh_enable();
}
#endif /* _NET_OVPN_OVPNSTATS_H_ */

View File

@ -152,7 +152,7 @@ static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
if (WARN_ON(!ovpn_peer_hold(peer)))
goto err_nopeer;
schedule_work(&peer->tcp.defer_del_work);
dev_dstats_rx_dropped(peer->ovpn->dev);
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
err_nopeer:
kfree_skb(skb);
}
@ -298,9 +298,9 @@ static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk)
} while (peer->tcp.out_msg.len > 0);
if (!peer->tcp.out_msg.len) {
preempt_disable();
local_bh_disable();
dev_dstats_tx_add(peer->ovpn->dev, skb->len);
preempt_enable();
local_bh_enable();
}
kfree_skb(peer->tcp.out_msg.skb);
@ -331,7 +331,7 @@ static void ovpn_tcp_send_sock_skb(struct ovpn_peer *peer, struct sock *sk,
ovpn_tcp_send_sock(peer, sk);
if (peer->tcp.out_msg.skb) {
dev_dstats_tx_dropped(peer->ovpn->dev);
ovpn_dev_dstats_tx_dropped(peer->ovpn->dev);
kfree_skb(skb);
return;
}
@ -353,7 +353,7 @@ void ovpn_tcp_send_skb(struct ovpn_peer *peer, struct sock *sk,
if (sock_owned_by_user(sk)) {
if (skb_queue_len(&peer->tcp.out_queue) >=
READ_ONCE(net_hotdata.max_backlog)) {
dev_dstats_tx_dropped(peer->ovpn->dev);
ovpn_dev_dstats_tx_dropped(peer->ovpn->dev);
kfree_skb(skb);
goto unlock;
}
@ -581,14 +581,19 @@ static void ovpn_tcp_close(struct sock *sk, long timeout)
rcu_read_lock();
sock = rcu_dereference_sk_user_data(sk);
if (!sock || !sock->peer || !ovpn_peer_hold(sock->peer)) {
if (!sock) {
rcu_read_unlock();
return;
}
peer = sock->peer;
if (!peer || !ovpn_peer_hold(peer)) {
rcu_read_unlock();
return;
}
rcu_read_unlock();
ovpn_peer_del(sock->peer, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT);
ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT);
peer->tcp.sk_cb.prot->close(sk, timeout);
ovpn_peer_put(peer);
}

View File

@ -125,7 +125,7 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
return 0;
drop:
dev_dstats_rx_dropped(ovpn->dev);
ovpn_dev_dstats_rx_dropped(ovpn->dev);
drop_noovpn:
kfree_skb(skb);
return 0;

View File

@ -53,7 +53,7 @@ ovpn_run_ping_traffic() {
for p in $(seq 1 ${OVPN_NUM_PEERS}); do
ovpn_cmd_ok "send ping traffic to peer ${p}" \
ip netns exec ovpn_peer0 ping -qfc 500 -w 3 \
ip netns exec ovpn_peer0 ping -qfc 100 -w 3 \
5.5.5.$((p + 1))
done
}

View File

@ -66,7 +66,7 @@ ovpn_mark_run_baseline_traffic() {
for p in $(seq 1 3); do
ovpn_cmd_ok "send baseline traffic to peer ${p}" \
ip netns exec ovpn_peer0 ping -qfc 500 -w 3 \
ip netns exec ovpn_peer0 ping -qfc 100 -w 3 \
5.5.5.$((p + 1))
done
}
@ -101,7 +101,7 @@ ovpn_mark_verify_drop_traffic() {
local total_count
for p in $(seq 1 3); do
if ping_output=$(ip netns exec ovpn_peer0 ping -qfc 500 -w 1 \
if ping_output=$(ip netns exec ovpn_peer0 ping -qfc 100 -w 1 \
5.5.5.$((p + 1)) 2>&1); then
printf '%s\n' "expected ping to peer ${p} to fail \
after nft drop rule"
@ -144,7 +144,7 @@ ovpn_mark_verify_traffic_recovery() {
sleep 1
for p in $(seq 1 3); do
ovpn_cmd_ok "send recovery traffic to peer ${p}" \
ip netns exec ovpn_peer0 ping -qfc 500 -w 3 \
ip netns exec ovpn_peer0 ping -qfc 100 -w 3 \
5.5.5.$((p + 1))
done
}

View File

@ -110,7 +110,7 @@ ovpn_run_basic_traffic() {
ovpn_run_lan_traffic() {
ovpn_cmd_ok "ping LAN behind peer1" \
ip netns exec ovpn_peer0 ping -qfc 500 -w 3 "${OVPN_LAN_IP}"
ip netns exec ovpn_peer0 ping -qfc 100 -w 3 "${OVPN_LAN_IP}"
}
ovpn_run_float_mode() {
@ -127,7 +127,7 @@ ovpn_run_float_mode() {
for p in $(seq 1 ${OVPN_NUM_PEERS}); do
peer_ns="ovpn_peer${p}"
ovpn_cmd_ok "ping tunnel after float peer ${p}" \
ip netns exec "${peer_ns}" ping -qfc 500 -w 3 5.5.5.1
ip netns exec "${peer_ns}" ping -qfc 100 -w 3 5.5.5.1
done
}