[Openvpn-devel,RFC,net-next,v2,03/14] ovpn: limit AEAD decrypt verification failures

Message ID 9ddcecb7c766c02e48f3bbe7c88eba4e195df68f.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
  OpenVPN userspace tracks AEAD tag verification failures per decrypt key.
It asks for a new key after more than 2^35 failures and stops attempting
AEAD decryption with that key after more than 2^36 failures.

Mirror that behavior in ovpn. Count only crypto_aead_decrypt
authentication failures, reported by the crypto API as -EBADMSG, and
notify userspace once when the soft threshold is crossed. Once the hard
threshold is exceeded, reject new decrypt attempts with the same key
before submitting more crypto work.

This applies to all ovpn AEAD ciphers, not only AES-GCM, and is
independent from the AES-GCM packet/block usage limits.

Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
No changes since v1 https://lore.kernel.org/openvpn-devel/ff21c6997ad080e4b23541fccba120730de8e96d.1782919654.git.ralf@mandelbit.com/

 drivers/net/ovpn/crypto.h      |  2 ++
 drivers/net/ovpn/crypto_aead.c | 24 ++++++++++++++++++++++++
 drivers/net/ovpn/crypto_aead.h |  1 +
 drivers/net/ovpn/io.c          |  6 ++++++
 4 files changed, 33 insertions(+)
  

Patch

diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h
index 09d3a945c5d9..2be7ff54fc40 100644
--- a/drivers/net/ovpn/crypto.h
+++ b/drivers/net/ovpn/crypto.h
@@ -43,6 +43,8 @@  struct ovpn_crypto_key_slot {
 
 	struct crypto_aead *encrypt;
 	struct crypto_aead *decrypt;
+	atomic64_t decrypt_failures;
+	unsigned long decrypt_failure_flags;
 	u8 nonce_tail_xmit[OVPN_NONCE_TAIL_SIZE];
 	u8 nonce_tail_recv[OVPN_NONCE_TAIL_SIZE];
 
diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c
index 9b10ae6f6002..9e34a553f59a 100644
--- a/drivers/net/ovpn/crypto_aead.c
+++ b/drivers/net/ovpn/crypto_aead.c
@@ -29,6 +29,25 @@ 
 
 #define ALG_NAME_AES		"gcm(aes)"
 #define ALG_NAME_CHACHAPOLY	"rfc7539(chacha20,poly1305)"
+#define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY	BIT_ULL(35)
+#define OVPN_AEAD_DECRYPT_FAILURE_LIMIT		BIT_ULL(36)
+#define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY_BIT	0
+
+static bool
+ovpn_aead_decrypt_failure_exceeded(const struct ovpn_crypto_key_slot *ks)
+{
+	return atomic64_read(&ks->decrypt_failures) >
+	       OVPN_AEAD_DECRYPT_FAILURE_LIMIT;
+}
+
+bool ovpn_aead_decrypt_failure_record(struct ovpn_crypto_key_slot *ks)
+{
+	u64 failures = atomic64_inc_return(&ks->decrypt_failures);
+
+	return failures > OVPN_AEAD_DECRYPT_FAILURE_NOTIFY &&
+	       !test_and_set_bit(OVPN_AEAD_DECRYPT_FAILURE_NOTIFY_BIT,
+				 &ks->decrypt_failure_flags);
+}
 
 static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks)
 {
@@ -270,6 +289,9 @@  int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 	if (unlikely(payload_len < 0))
 		return -EINVAL;
 
+	if (unlikely(ovpn_aead_decrypt_failure_exceeded(ks)))
+		return -EKEYREJECTED;
+
 	/* Prepare the skb data buffer to be accessed up until the auth tag.
 	 * This is required because this area is directly mapped into the sg
 	 * list.
@@ -436,6 +458,8 @@  ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc)
 	ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg);
 	ovpn_key_usage_init(&ks->usage_xmit);
 	ovpn_key_usage_init(&ks->usage_recv);
+	atomic64_set(&ks->decrypt_failures, 0);
+	ks->decrypt_failure_flags = 0;
 
 	ks->encrypt = ovpn_aead_init("encrypt", alg_name,
 				     kc->encrypt.cipher_key,
diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h
index 65a2ff307898..e5ff0b9b5ee3 100644
--- a/drivers/net/ovpn/crypto_aead.h
+++ b/drivers/net/ovpn/crypto_aead.h
@@ -25,5 +25,6 @@  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);
+bool ovpn_aead_decrypt_failure_record(struct ovpn_crypto_key_slot *ks);
 
 #endif /* _NET_OVPN_OVPNAEAD_H_ */
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 31c750b9481a..8085cc345c59 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -129,6 +129,12 @@  void ovpn_decrypt_post(void *data, int ret)
 	/* 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(ks)))
+			ovpn_nl_key_swap_notify(peer, ks->key_id);
+		goto drop;
+	}
+
 	if (unlikely(ret < 0))
 		goto drop;