mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
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:
commit
2bb155e921
|
|
@ -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 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@ 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)
|
||||
|
|
|
|||
|
|
@ -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 @@ 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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user