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]>
---
 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 ae4b87a2faec..a869545cd145 100644
--- a/drivers/net/ovpn/crypto_aead.c
+++ b/drivers/net/ovpn/crypto_aead.c
@@ -150,7 +150,7 @@ 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;
+       struct ovpn_key_ctx *key = NULL;
        unsigned int plaintext_len;
        struct aead_request *req;
        struct sk_buff *trailer;
@@ -165,6 +165,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:
@@ -264,7 +270,7 @@ 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;
+       struct ovpn_key_ctx *key = NULL;
        unsigned int payload_offset;
        int ret, payload_len, nfrags;
        struct aead_request *req;
@@ -274,13 +280,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 d3633cb4e2a9..2b57964679c7 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

Reply via email to