mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-09-22 09:34:56 +02:00
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:
+11
-15
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 @@ error:
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -204,10 +204,10 @@ drop:
|
||||
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 @@ err_unlock:
|
||||
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)
|
||||
|
||||
+18
-1
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ err:
|
||||
/* 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();
|
||||
|
||||
Reference in New Issue
Block a user