@@ -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);
@@ -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))
@@ -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;
}
@@ -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);
}
@@ -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;