Epoch key rotation needs to replace encrypt and decrypt key contexts while packets may still hold references from the async AEAD path.
Publish key contexts through RCU, add a per-context kref, and let encrypt and decrypt requests hold a context until their completion callback runs. Direct-key behavior is otherwise unchanged. Signed-off-by: Ralf Lici <[email protected]> --- No functional changes since v1 https://lore.kernel.org/openvpn-devel/4ae05d37fc69c2c29d83f824616b1aee6315ab6b.1782919654.git.r...@mandelbit.com/ drivers/net/ovpn/crypto.h | 34 ++++++++++++++++++++-- drivers/net/ovpn/crypto_aead.c | 20 ++++++++++--- drivers/net/ovpn/crypto_key.c | 53 +++++++++++++++++++--------------- drivers/net/ovpn/io.c | 8 +++-- drivers/net/ovpn/skb.h | 2 ++ 5 files changed, 86 insertions(+), 31 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index c36cd8299afd..1d62f2be23c9 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -10,6 +10,9 @@ #ifndef _NET_OVPN_OVPNCRYPTO_H_ #define _NET_OVPN_OVPNCRYPTO_H_ +#include <linux/kref.h> +#include <linux/rcupdate.h> + #include "crypto_limits.h" #include "pktid.h" #include "proto.h" @@ -47,6 +50,8 @@ struct ovpn_key_ctx { struct ovpn_key_usage usage; atomic64_t decrypt_failures; unsigned long decrypt_failure_flags; + struct kref refcount; + struct rcu_head rcu; }; struct ovpn_crypto_key_slot { @@ -54,8 +59,8 @@ struct ovpn_crypto_key_slot { enum ovpn_cipher_alg cipher_alg; struct ovpn_limit usage_limit; - struct ovpn_key_ctx *encrypt; - struct ovpn_key_ctx *decrypt; + struct ovpn_key_ctx __rcu *encrypt; + struct ovpn_key_ctx __rcu *decrypt; struct kref refcount; struct rcu_head rcu; }; @@ -137,6 +142,31 @@ static inline void ovpn_crypto_key_slot_put(struct ovpn_crypto_key_slot *ks) kref_put(&ks->refcount, ovpn_crypto_key_slot_release); } +void ovpn_key_ctx_release(struct kref *kref); + +static inline void ovpn_key_ctx_put(struct ovpn_key_ctx *key) +{ + if (key) + kref_put(&key->refcount, ovpn_key_ctx_release); +} + +static inline int ovpn_key_ctx_get(struct ovpn_key_ctx **out, + struct ovpn_key_ctx __rcu **rcu_ptr) +{ + struct ovpn_key_ctx *key; + + rcu_read_lock(); + key = rcu_dereference(*rcu_ptr); + if (unlikely(!key || !kref_get_unless_zero(&key->refcount))) { + rcu_read_unlock(); + return -ENOKEY; + } + rcu_read_unlock(); + + *out = key; + return 0; +} + int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs, const struct ovpn_peer_key_reset *pkr); diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 31f5327694d2..39bd320169d3 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -150,8 +150,8 @@ static struct scatterlist *ovpn_aead_crypto_req_sg(struct crypto_aead *aead, int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { - struct ovpn_key_ctx *key = ks->encrypt; unsigned int plaintext_len, tag_size; + struct ovpn_key_ctx *key = NULL; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; @@ -164,6 +164,12 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, ovpn_skb_cb(skb)->peer = peer; ovpn_skb_cb(skb)->ks = ks; plaintext_len = skb->len; + + ret = ovpn_key_ctx_get(&key, &ks->encrypt); + if (unlikely(ret)) + return ret; + ovpn_skb_cb(skb)->key = key; + tag_size = crypto_aead_authsize(key->tfm); /* Sample AEAD header format: @@ -263,8 +269,8 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { - struct ovpn_key_ctx *key = ks->decrypt; unsigned int payload_offset, tag_size; + struct ovpn_key_ctx *key = NULL; int ret, payload_len, nfrags; struct aead_request *req; struct sk_buff *trailer; @@ -272,13 +278,19 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, void *tmp; u8 *iv; + ovpn_skb_cb(skb)->peer = peer; + ovpn_skb_cb(skb)->ks = ks; + + ret = ovpn_key_ctx_get(&key, &ks->decrypt); + if (unlikely(ret)) + return ret; + ovpn_skb_cb(skb)->key = key; + tag_size = crypto_aead_authsize(key->tfm); payload_offset = ovpn_aead_direct_payload_offset(tag_size); payload_len = skb->len - payload_offset; ovpn_skb_cb(skb)->payload_offset = payload_offset; - ovpn_skb_cb(skb)->peer = peer; - ovpn_skb_cb(skb)->ks = ks; /* sanity check on packet size, payload size must be >= 0 */ if (unlikely(payload_len < 0)) diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c index cdf35e2b241c..44110a0b38eb 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -82,6 +82,22 @@ static void ovpn_key_ctx_free(struct ovpn_key_ctx *key) kfree(key); } +static void ovpn_key_ctx_free_rcu(struct rcu_head *head) +{ + struct ovpn_key_ctx *key; + + key = container_of(head, struct ovpn_key_ctx, rcu); + ovpn_key_ctx_free(key); +} + +void ovpn_key_ctx_release(struct kref *kref) +{ + struct ovpn_key_ctx *key; + + key = container_of(kref, struct ovpn_key_ctx, refcount); + call_rcu(&key->rcu, ovpn_key_ctx_free_rcu); +} + static struct ovpn_key_ctx * ovpn_key_ctx_new(const char *title, const char *alg_name, const struct ovpn_key_direction *dir, bool encrypt) @@ -113,6 +129,7 @@ ovpn_key_ctx_new(const char *title, const char *alg_name, ovpn_key_usage_init(&key->usage); atomic64_set(&key->decrypt_failures, 0); key->decrypt_failure_flags = 0; + kref_init(&key->refcount); /* initialize only the packet ID direction this context owns */ if (encrypt) @@ -128,8 +145,8 @@ void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks) if (!ks) return; - ovpn_key_ctx_free(ks->encrypt); - ovpn_key_ctx_free(ks->decrypt); + ovpn_key_ctx_put(rcu_access_pointer(ks->encrypt)); + ovpn_key_ctx_put(rcu_access_pointer(ks->decrypt)); kfree(ks); } @@ -137,6 +154,7 @@ struct ovpn_crypto_key_slot * ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) { struct ovpn_crypto_key_slot *ks = NULL; + struct ovpn_key_ctx *key; const char *alg_name; int ret; @@ -168,21 +186,19 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) ks->cipher_alg = kc->cipher_alg; ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); - ks->encrypt = ovpn_key_ctx_new("encrypt", alg_name, &kc->encrypt, - true); - if (IS_ERR(ks->encrypt)) { - ret = PTR_ERR(ks->encrypt); - ks->encrypt = NULL; + key = ovpn_key_ctx_new("encrypt", alg_name, &kc->encrypt, true); + if (IS_ERR(key)) { + ret = PTR_ERR(key); goto destroy_ks; } + RCU_INIT_POINTER(ks->encrypt, key); - ks->decrypt = ovpn_key_ctx_new("decrypt", alg_name, &kc->decrypt, - false); - if (IS_ERR(ks->decrypt)) { - ret = PTR_ERR(ks->decrypt); - ks->decrypt = NULL; + key = ovpn_key_ctx_new("decrypt", alg_name, &kc->decrypt, false); + if (IS_ERR(key)) { + ret = PTR_ERR(key); goto destroy_ks; } + RCU_INIT_POINTER(ks->decrypt, key); return ks; @@ -193,17 +209,8 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) enum ovpn_cipher_alg ovpn_crypto_key_slot_alg(struct ovpn_crypto_key_slot *ks) { - const char *alg_name; - - if (!ks->encrypt || !ks->encrypt->tfm) + if (!ks) return OVPN_CIPHER_ALG_NONE; - alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt->tfm)); - - if (!strcmp(alg_name, ALG_NAME_AES)) - return OVPN_CIPHER_ALG_AES_GCM; - else if (!strcmp(alg_name, ALG_NAME_CHACHAPOLY)) - return OVPN_CIPHER_ALG_CHACHA20_POLY1305; - else - return OVPN_CIPHER_ALG_NONE; + return ks->cipher_alg; } diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 2b1df68a1b1e..006d83fe3e2a 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -125,14 +125,14 @@ void ovpn_decrypt_post(void *data, int ret) payload_offset = ovpn_skb_cb(skb)->payload_offset; ks = ovpn_skb_cb(skb)->ks; - key = ks->decrypt; + key = ovpn_skb_cb(skb)->key; peer = ovpn_skb_cb(skb)->peer; /* crypto is done, cleanup skb CB and its members */ kfree(ovpn_skb_cb(skb)->crypto_tmp); if (unlikely(ret == -EBADMSG)) { - if (unlikely(ovpn_aead_decrypt_failure_record(key))) + if (key && unlikely(ovpn_aead_decrypt_failure_record(key))) ovpn_nl_key_swap_notify(peer, ks->key_id); goto drop; } @@ -224,6 +224,7 @@ void ovpn_decrypt_post(void *data, int ret) ovpn_peer_put(peer); if (likely(ks)) ovpn_crypto_key_slot_put(ks); + ovpn_key_ctx_put(key); } /* RX path entry point: decrypt packet and forward it to the device */ @@ -256,6 +257,7 @@ void ovpn_encrypt_post(void *data, int ret) struct ovpn_crypto_key_slot *ks; struct sk_buff *skb = data; struct ovpn_socket *sock; + struct ovpn_key_ctx *key; struct ovpn_peer *peer; unsigned int orig_len; @@ -267,6 +269,7 @@ void ovpn_encrypt_post(void *data, int ret) ks = ovpn_skb_cb(skb)->ks; peer = ovpn_skb_cb(skb)->peer; + key = ovpn_skb_cb(skb)->key; /* crypto is done, cleanup skb CB and its members */ kfree(ovpn_skb_cb(skb)->crypto_tmp); @@ -322,6 +325,7 @@ void ovpn_encrypt_post(void *data, int ret) ovpn_peer_put(peer); if (likely(ks)) ovpn_crypto_key_slot_put(ks); + ovpn_key_ctx_put(key); kfree_skb(skb); } diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h index 4fb7ea025426..75d64161d774 100644 --- a/drivers/net/ovpn/skb.h +++ b/drivers/net/ovpn/skb.h @@ -22,6 +22,7 @@ * struct ovpn_cb - ovpn skb control block * @peer: the peer this skb was received from/sent to * @ks: the crypto key slot used to encrypt/decrypt this skb + * @key: the key context used to encrypt/decrypt this skb * @crypto_tmp: pointer to temporary memory used for crypto operations * containing the IV, the scatter gather list and the aead request * @payload_offset: offset in the skb where the payload starts @@ -30,6 +31,7 @@ struct ovpn_cb { struct ovpn_peer *peer; struct ovpn_crypto_key_slot *ks; + struct ovpn_key_ctx *key; void *crypto_tmp; unsigned int payload_offset; bool nosignal; -- 2.54.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
