[Openvpn-devel,RFC,net-next,v2,07/14] ovpn: make key contexts refcounted

Message ID cc179c678d26e116d5d63cce58ae741de6dd78ab.1782986577.git.ralf@mandelbit.com
State Changes Requested
Headers
Series ovpn: add epoch data channel keys |

Commit Message

Ralf Lici July 2, 2026, 11:52 a.m. UTC
  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 <ralf@mandelbit.com>
---
No functional changes since v1 https://lore.kernel.org/openvpn-devel/4ae05d37fc69c2c29d83f824616b1aee6315ab6b.1782919654.git.ralf@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(-)
  

Patch

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;