Included fixes:

* release key slot crypto transforms from a workqueue rather than an RCU
   callback, because crypto_free_aead() may sleep with async or hardware
   implementations
 * run all deferred ovpn work on a module-owned workqueue and drain it on
   module exit, so no work item can still be executing module text after
   the module is unloaded
 * finish crypto callback cleanup (key slot release and leftover skb)
   before dropping the peer reference that gates netdev unregistration
   and module removal
 * avoid dereferencing a NULL key slot when userspace asks to kill a key
   that is not installed on the peer
 -----BEGIN PGP SIGNATURE-----
 
 iJEEABYIADkWIQQr0db7q+Rc7Zog28Fc8QQzwdnOtwUCanjnnRsUgAAAAAAEAA5t
 YW51MiwyLjUrMS4xMiwyLDIACgkQXPEEM8HZzre4UwEAqIw3zlYNPzJeNRq4GB0b
 is5pBp/ZWiWIpJjdNSHmtpgA/1/1oBil7ZTdM0KhUCcCR1LHIXLb2sJOFUfhzowc
 QO4K
 =qG8X
 -----END PGP SIGNATURE-----

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

Antonio Quartulli says:

====================
Included fixes:

* release key slot crypto transforms from a workqueue rather than an RCU
  callback, because crypto_free_aead() may sleep with async or hardware
  implementations
* run all deferred ovpn work on a module-owned workqueue and drain it on
  module exit, so no work item can still be executing module text after
  the module is unloaded
* finish crypto callback cleanup (key slot release and leftover skb)
  before dropping the peer reference that gates netdev unregistration
  and module removal
* avoid dereferencing a NULL key slot when userspace asks to kill a key
  that is not installed on the peer

* tag 'ovpn-net-20260809' of https://github.com/OpenVPN/ovpn-net-next:
  ovpn: defer key slot crypto freeing to workqueue
  ovpn: run deferred work on a module-owned workqueue
  ovpn: finish crypto callback cleanup before peer release
  ovpn: fix NULL dereference when killing missing key
====================

Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/20260809212142.2249027-1-antonio@openvpn.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-08-12 18:09:18 -07:00
commit 2bb155e921
9 changed files with 63 additions and 37 deletions

View File

@ -18,20 +18,12 @@
#include "crypto_aead.h"
#include "crypto.h"
static void ovpn_ks_destroy_rcu(struct rcu_head *head)
{
struct ovpn_crypto_key_slot *ks;
ks = container_of(head, struct ovpn_crypto_key_slot, rcu);
ovpn_aead_crypto_key_slot_destroy(ks);
}
void ovpn_crypto_key_slot_release(struct kref *kref)
{
struct ovpn_crypto_key_slot *ks;
ks = container_of(kref, struct ovpn_crypto_key_slot, refcount);
call_rcu(&ks->rcu, ovpn_ks_destroy_rcu);
queue_rcu_work(ovpn_wq, &ks->free_work);
}
/* can only be invoked when all peer references have been dropped (i.e. RCU
@ -58,15 +50,19 @@ void ovpn_crypto_state_release(struct ovpn_crypto_state *cs)
bool ovpn_crypto_kill_key(struct ovpn_crypto_state *cs, u8 key_id)
{
struct ovpn_crypto_key_slot *ks = NULL;
struct ovpn_crypto_key_slot *tmp;
int slot = 0;
spin_lock_bh(&cs->lock);
if (rcu_access_pointer(cs->slots[0])->key_id == key_id) {
ks = rcu_replace_pointer(cs->slots[0], NULL,
lockdep_is_held(&cs->lock));
} else if (rcu_access_pointer(cs->slots[1])->key_id == key_id) {
ks = rcu_replace_pointer(cs->slots[1], NULL,
lockdep_is_held(&cs->lock));
tmp = rcu_access_pointer(cs->slots[slot]);
if (!tmp || tmp->key_id != key_id) {
slot = 1;
tmp = rcu_access_pointer(cs->slots[slot]);
}
if (tmp && tmp->key_id == key_id)
ks = rcu_replace_pointer(cs->slots[slot], NULL,
lockdep_is_held(&cs->lock));
spin_unlock_bh(&cs->lock);
if (ks)

View File

@ -10,6 +10,8 @@
#ifndef _NET_OVPN_OVPNCRYPTO_H_
#define _NET_OVPN_OVPNCRYPTO_H_
#include <linux/workqueue.h>
#include "pktid.h"
#include "proto.h"
@ -45,8 +47,8 @@ struct ovpn_crypto_key_slot {
struct ovpn_pktid_recv pid_recv ____cacheline_aligned_in_smp;
struct ovpn_pktid_xmit pid_xmit ____cacheline_aligned_in_smp;
struct rcu_work free_work;
struct kref refcount;
struct rcu_head rcu;
};
struct ovpn_crypto_state {

View File

@ -9,6 +9,7 @@
#include <crypto/aead.h>
#include <linux/skbuff.h>
#include <linux/workqueue.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/udp.h>
@ -380,13 +381,19 @@ static struct crypto_aead *ovpn_aead_init(const char *title,
return ERR_PTR(ret);
}
void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks)
static void ovpn_aead_crypto_key_slot_free(struct ovpn_crypto_key_slot *ks)
{
if (!ks)
return;
crypto_free_aead(ks->encrypt);
crypto_free_aead(ks->decrypt);
}
static void ovpn_aead_crypto_key_slot_free_work(struct work_struct *work)
{
struct ovpn_crypto_key_slot *ks;
ks = container_of(to_rcu_work(work), struct ovpn_crypto_key_slot,
free_work);
ovpn_aead_crypto_key_slot_free(ks);
kfree(ks);
}
@ -420,6 +427,7 @@ ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc)
ks->encrypt = NULL;
ks->decrypt = NULL;
INIT_RCU_WORK(&ks->free_work, ovpn_aead_crypto_key_slot_free_work);
kref_init(&ks->refcount);
ks->key_id = kc->key_id;
@ -453,7 +461,8 @@ ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc)
return ks;
destroy_ks:
ovpn_aead_crypto_key_slot_destroy(ks);
ovpn_aead_crypto_key_slot_free(ks);
kfree(ks);
return ERR_PTR(ret);
}

View File

@ -22,7 +22,6 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
struct ovpn_crypto_key_slot *
ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc);
void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks);
enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks);

View File

@ -204,10 +204,10 @@ void ovpn_decrypt_post(void *data, int ret)
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
kfree_skb(skb);
drop_nocount:
if (likely(peer))
ovpn_peer_put(peer);
if (likely(ks))
ovpn_crypto_key_slot_put(ks);
if (likely(peer))
ovpn_peer_put(peer);
}
/* RX path entry point: decrypt packet and forward it to the device */
@ -302,11 +302,11 @@ void ovpn_encrypt_post(void *data, int ret)
err:
if (unlikely(skb))
ovpn_dev_dstats_tx_dropped(peer->ovpn->dev);
if (likely(peer))
ovpn_peer_put(peer);
kfree_skb(skb);
if (likely(ks))
ovpn_crypto_key_slot_put(ks);
kfree_skb(skb);
if (likely(peer))
ovpn_peer_put(peer);
}
static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)

View File

@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/workqueue.h>
#include <net/gro_cells.h>
#include <net/ip.h>
#include <net/rtnetlink.h>
@ -26,6 +27,9 @@
#include "tcp.h"
#include "udp.h"
/* module-owned workqueue on which all ovpn-specific work is queued */
struct workqueue_struct *ovpn_wq;
static void ovpn_priv_free(struct net_device *net)
{
struct ovpn_priv *ovpn = netdev_priv(net);
@ -264,10 +268,16 @@ static int __init ovpn_init(void)
ovpn_tcp_init();
ovpn_wq = alloc_workqueue("ovpn", WQ_PERCPU, 0);
if (!ovpn_wq) {
pr_err("ovpn: cannot allocate workqueue\n");
return -ENOMEM;
}
err = rtnl_link_register(&ovpn_link_ops);
if (err) {
pr_err("ovpn: can't register rtnl link ops: %d\n", err);
return err;
goto destroy_wq;
}
err = ovpn_nl_register();
@ -280,6 +290,9 @@ static int __init ovpn_init(void)
unreg_rtnl:
rtnl_link_unregister(&ovpn_link_ops);
destroy_wq:
destroy_workqueue(ovpn_wq);
ovpn_wq = NULL;
return err;
}
@ -288,7 +301,11 @@ static __exit void ovpn_cleanup(void)
ovpn_nl_unregister();
rtnl_link_unregister(&ovpn_link_ops);
flush_workqueue(ovpn_wq);
rcu_barrier();
destroy_workqueue(ovpn_wq);
ovpn_wq = NULL;
}
module_init(ovpn_init);

View File

@ -15,6 +15,10 @@
#include <uapi/linux/if_link.h>
#include <uapi/linux/ovpn.h>
struct workqueue_struct;
extern struct workqueue_struct *ovpn_wq;
/**
* struct ovpn_peer_collection - container of peers for MultiPeer mode
* @by_id: table of peers index by ID

View File

@ -62,7 +62,7 @@ void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
/* now that interval and timeout have been changed, kick
* off the worker so that the next delay can be recomputed
*/
mod_delayed_work(system_percpu_wq, &peer->ovpn->keepalive_work, 0);
mod_delayed_work(ovpn_wq, &peer->ovpn->keepalive_work, 0);
}
/**
@ -1371,7 +1371,7 @@ static time64_t ovpn_peer_keepalive_work_single(struct ovpn_peer *peer,
peer->id);
if (WARN_ON(!ovpn_peer_hold(peer)))
return 0;
if (!schedule_work(&peer->keepalive_work))
if (!queue_work(ovpn_wq, &peer->keepalive_work))
ovpn_peer_put(peer);
}
@ -1463,8 +1463,8 @@ void ovpn_peer_keepalive_work(struct work_struct *work)
netdev_dbg(ovpn->dev,
"scheduling keepalive work: now=%llu next_run=%llu delta=%llu\n",
next_run, now, next_run - now);
schedule_delayed_work(&ovpn->keepalive_work,
(next_run - now) * HZ);
queue_delayed_work(ovpn_wq, &ovpn->keepalive_work,
(next_run - now) * HZ);
}
unlock_ovpn(ovpn, &release_list);
}

View File

@ -151,7 +151,7 @@ static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
/* take reference for deferred peer deletion. should never fail */
if (WARN_ON(!ovpn_peer_hold(peer)))
goto err_nopeer;
if (!schedule_work(&peer->tcp.defer_del_work))
if (!queue_work(ovpn_wq, &peer->tcp.defer_del_work))
ovpn_peer_put(peer);
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
err_nopeer:
@ -284,13 +284,12 @@ static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk)
* stream therefore we abort the connection
*/
ovpn_peer_hold(peer);
if (!schedule_work(&peer->tcp.defer_del_work))
if (!queue_work(ovpn_wq, &peer->tcp.defer_del_work))
ovpn_peer_put(peer);
/* we bail out immediately and keep tx_in_progress set
* to true. This way we prevent more TX attempts
* which would lead to more invocations of
* schedule_work()
* which would lead to more invocations of queue_work()
*/
return;
}
@ -487,7 +486,7 @@ static void ovpn_tcp_write_space(struct sock *sk)
rcu_read_lock();
sock = rcu_dereference_sk_user_data(sk);
if (likely(sock && sock->peer)) {
schedule_work(&sock->tcp_tx_work);
queue_work(ovpn_wq, &sock->tcp_tx_work);
sock->peer->tcp.sk_cb.sk_write_space(sk);
}
rcu_read_unlock();