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

openSUSE Build Service is sponsored by