File 3331-crypto-Make-crypto_one_time_aead-4-threadsafe.patch of Package erlang
From 1fa37b093797d25c2a3be49495be61fb1fb67e8f Mon Sep 17 00:00:00 2001
From: Dan Gudmundsson <dgud@erlang.org>
Date: Mon, 9 Feb 2026 19:07:57 +0100
Subject: [PATCH] crypto: Make crypto_one_time_aead/4 threadsafe
Protect it by a mutex, could crash the emulator if invoked in parallel
with the same state.
---
lib/crypto/c_src/aead.c | 14 ++++++++++-
lib/crypto/test/crypto_SUITE.erl | 42 ++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/lib/crypto/c_src/aead.c b/lib/crypto/c_src/aead.c
index 9cb2ff8e18..4756fb5a31 100644
--- a/lib/crypto/c_src/aead.c
+++ b/lib/crypto/c_src/aead.c
@@ -37,10 +37,13 @@ struct aead_cipher_ctx {
int encflg;
unsigned int tag_len;
ErlNifEnv *env;
+ ErlNifMutex * aead_m; /* Should protect 'ctx', which needs to be thread-safe */
};
static void aead_cipher_ctx_dtor(ErlNifEnv* env, struct aead_cipher_ctx* ctx) {
enif_free_env(ctx->env);
+ if (ctx->aead_m)
+ enif_mutex_destroy(ctx->aead_m);
if (ctx->ctx)
EVP_CIPHER_CTX_free(ctx->ctx);
@@ -78,6 +81,9 @@ ERL_NIF_TERM aead_cipher_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
ctx_res->env = enif_alloc_env();
encflg_arg = argv[3];
+ if ((ctx_res->aead_m = enif_mutex_create("crypto_aead_m")) == NULL)
+ {ret = EXCP_ERROR(env, "Create mutex failed"); goto done;}
+
/* Fetch the flag telling if we are going to encrypt (=true) or decrypt (=false) */
if (encflg_arg == atom_true)
ctx_res->encflg = 1;
@@ -144,6 +150,7 @@ ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
const struct cipher_type_t *cipherp;
EVP_CIPHER_CTX *ctx = NULL;
const EVP_CIPHER *cipher = NULL;
+ struct aead_cipher_ctx *ctx_res = NULL;
ErlNifBinary key, iv, aad, in, tag;
unsigned int tag_len;
unsigned char *outp, *tagp, *tag_data, *in_data;
@@ -228,9 +235,11 @@ ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
} else {
/* argc = 4 {state, IV, InData, AAD } */
- struct aead_cipher_ctx *ctx_res = NULL;
if (!enif_get_resource(env, argv[0], aead_cipher_ctx_rtype, (void**)&ctx_res))
{ret = EXCP_BADARG_N(env, 0, "Bad State"); goto done;}
+
+ enif_mutex_lock(ctx_res->aead_m);
+
if (!enif_inspect_iolist_as_binary(env, argv[1], &iv))
{ret = EXCP_BADARG_N(env, 1, "non-binary iv"); goto done;}
if (!enif_inspect_iolist_as_binary(env, argv[2], &in))
@@ -352,6 +361,9 @@ ERL_NIF_TERM aead_cipher_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
done:
if (ctx && argc == 7 )
EVP_CIPHER_CTX_free(ctx);
+ if (ctx_res) {
+ enif_mutex_unlock(ctx_res->aead_m);
+ }
return ret;
#else
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index 0257830459..b2dd65def4 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -71,6 +71,7 @@
compute/1,
compute_bug/0,
compute_bug/1,
+ concurrent_access/1,
crypto_load/1,
crypto_load_and_call/1,
encapsulate/1,
@@ -232,6 +233,7 @@ all() ->
cipher_info,
hash_info,
hash_equals,
+ concurrent_access,
pbkdf2_hmac,
pbkdf2_hmac_invalid_input
].
@@ -1248,6 +1250,46 @@ compute(Config) when is_list(Config) ->
end,
lists:foreach(fun do_compute/1, Gen).
+%%--------------------------------------------------------------------
+concurrent_access(_Config) ->
+ %% Check that we are thread safe, the states may not be used/updated
+ %% concurrently.
+
+ Algorithm = aes_128_gcm,
+ Secret = ~"secret",
+ #{iv_length := IvLen, key_length := KeyLen} = crypto:cipher_info(Algorithm),
+
+ IV = crypto:strong_rand_bytes(IvLen),
+ Key = crypto:pbkdf2_hmac(sha256, Secret, IV, 16, KeyLen),
+ EncState = crypto:crypto_one_time_aead_init(Algorithm, Key, 2, true),
+ DecState = crypto:crypto_one_time_aead_init(Algorithm, Key, 2, false),
+
+ Crypto = #{crypto_iv_len => IvLen,
+ crypto_key_len => KeyLen,
+ crypto_enc_state => EncState,
+ crypto_dec_state => DecState},
+ io:format("Crypto State: ~p\n", [Crypto]),
+ Bin = ~"Binary text",
+ AAD = ~"AAD",
+ persistent_term:put({?MODULE, concurrent_access}, Crypto),
+ _ = [spawn_link(fun() -> crypto_enc_test(Bin, AAD, 10_000) end) ||
+ _ <- lists:seq(1, erlang:system_info(schedulers)*3)],
+ ok.
+
+crypto_enc_test(_Bin, _AAD, 0) ->
+ ok;
+crypto_enc_test(Bin, AAD, Cnt) ->
+ #{crypto_iv_len := IvLen, crypto_enc_state := EncState} = persistent_term:get({?MODULE, concurrent_access}),
+ IV = crypto:strong_rand_bytes(IvLen),
+
+ try crypto:crypto_one_time_aead(EncState, IV, Bin, AAD)
+ catch
+ C:E:St ->
+ io:format("\nC: ~p\nE: ~p\nSt: ~p", [C, E, St]),
+ erlang:raise(C, E, St)
+ end,
+ crypto_enc_test(Bin, AAD, Cnt - 1).
+
%%--------------------------------------------------------------------
encapsulate(_Config) ->
case openssl_version() of
--
2.51.0