From 3ce1c27b56fa9856693e5c98331cebaa2a3accfa Mon Sep 17 00:00:00 2001
From: "Dr. Matthias St. Pierre" <Matthias.St.Pierre@ncp-e.com>
Date: Thu, 8 Feb 2018 16:40:32 +0100
Subject: [PATCH] DRBG: add locking api

This commit adds three new accessors to the internal DRBG lock

   int RAND_DRBG_lock(RAND_DRBG *drbg)
   int RAND_DRBG_unlock(RAND_DRBG *drbg)
   int RAND_DRBG_enable_locking(RAND_DRBG *drbg)

The three shared DRBGs are intended to be used concurrently, so they
have locking enabled by default. It is the callers responsibility to
guard access to the shared DRBGs by calls to RAND_DRBG_lock() and
RAND_DRBG_unlock().

All other DRBG instances don't have locking enabled by default, because
they are intendended to be used by a single thread. If it is desired,
locking can be enabled by using RAND_DRBG_enable_locking().

Reviewed-by: Rich Salz <rsalz@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/5294)
---
 crypto/err/openssl.txt    |  3 ++
 crypto/rand/drbg_lib.c    | 90 ++++++++++++++++++++++++++++++++++++---
 crypto/rand/rand_err.c    |  6 +++
 crypto/rand/rand_lib.c    | 17 ++++----
 include/internal/rand.h   |  4 ++
 include/openssl/randerr.h |  3 ++
 util/libcrypto.num        |  3 ++
 7 files changed, 111 insertions(+), 15 deletions(-)

diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index 3ed71fe53a..8d00463d85 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -890,6 +890,7 @@ RAND_F_DRBG_GET_ENTROPY:105:drbg_get_entropy
 RAND_F_DRBG_SETUP:117:drbg_setup
 RAND_F_GET_ENTROPY:106:get_entropy
 RAND_F_RAND_BYTES:100:RAND_bytes
+RAND_F_RAND_DRBG_ENABLE_LOCKING:119:RAND_DRBG_enable_locking
 RAND_F_RAND_DRBG_GENERATE:107:RAND_DRBG_generate
 RAND_F_RAND_DRBG_INSTANTIATE:108:RAND_DRBG_instantiate
 RAND_F_RAND_DRBG_NEW:109:RAND_DRBG_new
@@ -2256,6 +2257,7 @@ RAND_R_ADDITIONAL_INPUT_TOO_LONG:102:additional input too long
 RAND_R_ALREADY_INSTANTIATED:103:already instantiated
 RAND_R_ARGUMENT_OUT_OF_RANGE:105:argument out of range
 RAND_R_CANNOT_OPEN_FILE:121:Cannot open file
+RAND_R_DRBG_ALREADY_INITIALIZED:129:drbg already initialized
 RAND_R_DRBG_NOT_INITIALISED:104:drbg not initialised
 RAND_R_ENTROPY_INPUT_TOO_LONG:106:entropy input too long
 RAND_R_ENTROPY_OUT_OF_RANGE:124:entropy out of range
@@ -2274,6 +2276,7 @@ RAND_R_IN_ERROR_STATE:114:in error state
 RAND_R_NOT_A_REGULAR_FILE:122:Not a regular file
 RAND_R_NOT_INSTANTIATED:115:not instantiated
 RAND_R_NO_DRBG_IMPLEMENTATION_SELECTED:128:no drbg implementation selected
+RAND_R_PARENT_LOCKING_NOT_ENABLED:130:parent locking not enabled
 RAND_R_PERSONALISATION_STRING_TOO_LONG:116:personalisation string too long
 RAND_R_PRNG_NOT_SEEDED:100:PRNG not seeded
 RAND_R_RANDOM_POOL_OVERFLOW:125:random pool overflow
diff --git a/crypto/rand/drbg_lib.c b/crypto/rand/drbg_lib.c
index 4404e4f720..13b640bb9b 100644
--- a/crypto/rand/drbg_lib.c
+++ b/crypto/rand/drbg_lib.c
@@ -90,6 +90,21 @@ static RAND_DRBG *drbg_private;
  * |randomness| argument). This will immediately reseed the <master> DRBG.
  * The <public> and <private> DRBG will detect this on their next generate
  * call and reseed, pulling randomness from <master>.
+ *
+ * LOCKING
+ *
+ * The three shared DRBGs are intended to be used concurrently, so they
+ * support locking by default. It is the callers responsibility to wrap
+ * calls to functions like RAND_DRBG_generate() which modify the DRBGs
+ * internal state with calls to RAND_DRBG_lock() and RAND_DRBG_unlock().
+ * The functions RAND_bytes() and RAND_priv_bytes() take the locks
+ * automatically, so using the RAND api is thread safe as before.
+ *
+ * All other DRBG instances don't have locking enabled by default, because
+ * they are intendended to be used by a single thread. If it is desired,
+ * locking can be enabled using RAND_DRBG_enable_locking(). However, instead
+ * of accessing a single DRBG instance concurrently from different threads,
+ * it is recommended to instantiate a separate DRBG instance per thread.
  */
 
 
@@ -656,6 +671,69 @@ int RAND_DRBG_set_reseed_time_interval(RAND_DRBG *drbg, time_t interval)
     return 1;
 }
 
+
+/*
+ * Locks the given drbg. Locking a drbg which does not have locking
+ * enabled is considered a successful no-op.
+ *
+ * Returns 1 on success, 0 on failure.
+ */
+int RAND_DRBG_lock(RAND_DRBG *drbg)
+{
+    if (drbg->lock != NULL)
+        return CRYPTO_THREAD_write_lock(drbg->lock);
+
+    return 1;
+}
+
+/*
+ * Unlocks the given drbg. Unlocking a drbg which does not have locking
+ * enabled is considered a successful no-op.
+ *
+ * Returns 1 on success, 0 on failure.
+ */
+int RAND_DRBG_unlock(RAND_DRBG *drbg)
+{
+    if (drbg->lock != NULL)
+        return CRYPTO_THREAD_unlock(drbg->lock);
+
+    return 1;
+}
+
+/*
+ * Enables locking for the given drbg
+ *
+ * Locking can only be enabled if the random generator
+ * is in the uninitialized state.
+ *
+ * Returns 1 on success, 0 on failure.
+ */
+int RAND_DRBG_enable_locking(RAND_DRBG *drbg)
+{
+    if (drbg->state != DRBG_UNINITIALISED) {
+        RANDerr(RAND_F_RAND_DRBG_ENABLE_LOCKING,
+                RAND_R_DRBG_ALREADY_INITIALIZED);
+        return 0;
+    }
+
+    if (drbg->lock == NULL) {
+        if (drbg->parent != NULL && drbg->lock == NULL) {
+            RANDerr(RAND_F_RAND_DRBG_ENABLE_LOCKING,
+                    RAND_R_PARENT_LOCKING_NOT_ENABLED);
+            return 0;
+        }
+
+        drbg->lock = CRYPTO_THREAD_lock_new();
+        if (drbg->lock == NULL) {
+            RANDerr(RAND_F_RAND_DRBG_ENABLE_LOCKING,
+                    RAND_R_FAILED_TO_CREATE_LOCK);
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
 /*
  * Get and set the EXDATA
  */
@@ -782,9 +860,9 @@ static int drbg_bytes(unsigned char *out, int count)
     if (drbg == NULL)
         return 0;
 
-    CRYPTO_THREAD_write_lock(drbg->lock);
+    RAND_DRBG_lock(drbg);
     ret = RAND_DRBG_bytes(drbg, out, count);
-    CRYPTO_THREAD_unlock(drbg->lock);
+    RAND_DRBG_unlock(drbg);
 
     return ret;
 }
@@ -811,11 +889,11 @@ static int drbg_add(const void *buf, int num, double randomness)
         return 0;
     }
 
-    CRYPTO_THREAD_write_lock(drbg->lock);
+    RAND_DRBG_lock(drbg);
     ret = rand_drbg_restart(drbg, buf,
                             (size_t)(unsigned int)num,
                             (size_t)(8*randomness));
-    CRYPTO_THREAD_unlock(drbg->lock);
+    RAND_DRBG_unlock(drbg);
 
     return ret;
 }
@@ -835,9 +913,9 @@ static int drbg_status(void)
     if (drbg == NULL)
         return 0;
 
-    CRYPTO_THREAD_write_lock(drbg->lock);
+    RAND_DRBG_lock(drbg);
     ret = drbg->state == DRBG_READY ? 1 : 0;
-    CRYPTO_THREAD_unlock(drbg->lock);
+    RAND_DRBG_unlock(drbg);
     return ret;
 }
 
diff --git a/crypto/rand/rand_err.c b/crypto/rand/rand_err.c
index 9eadbf9a67..e8ec44ae12 100644
--- a/crypto/rand/rand_err.c
+++ b/crypto/rand/rand_err.c
@@ -19,6 +19,8 @@ static const ERR_STRING_DATA RAND_str_functs[] = {
     {ERR_PACK(ERR_LIB_RAND, RAND_F_DRBG_SETUP, 0), "drbg_setup"},
     {ERR_PACK(ERR_LIB_RAND, RAND_F_GET_ENTROPY, 0), "get_entropy"},
     {ERR_PACK(ERR_LIB_RAND, RAND_F_RAND_BYTES, 0), "RAND_bytes"},
+    {ERR_PACK(ERR_LIB_RAND, RAND_F_RAND_DRBG_ENABLE_LOCKING, 0),
+     "RAND_DRBG_enable_locking"},
     {ERR_PACK(ERR_LIB_RAND, RAND_F_RAND_DRBG_GENERATE, 0),
      "RAND_DRBG_generate"},
     {ERR_PACK(ERR_LIB_RAND, RAND_F_RAND_DRBG_INSTANTIATE, 0),
@@ -49,6 +51,8 @@ static const ERR_STRING_DATA RAND_str_reasons[] = {
     {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_ARGUMENT_OUT_OF_RANGE),
     "argument out of range"},
     {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_CANNOT_OPEN_FILE), "Cannot open file"},
+    {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_DRBG_ALREADY_INITIALIZED),
+    "drbg already initialized"},
     {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_DRBG_NOT_INITIALISED),
     "drbg not initialised"},
     {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_ENTROPY_INPUT_TOO_LONG),
@@ -80,6 +84,8 @@ static const ERR_STRING_DATA RAND_str_reasons[] = {
     {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_NOT_INSTANTIATED), "not instantiated"},
     {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_NO_DRBG_IMPLEMENTATION_SELECTED),
     "no drbg implementation selected"},
+    {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_PARENT_LOCKING_NOT_ENABLED),
+    "parent locking not enabled"},
     {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_PERSONALISATION_STRING_TOO_LONG),
     "personalisation string too long"},
     {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_PRNG_NOT_SEEDED), "PRNG not seeded"},
diff --git a/crypto/rand/rand_lib.c b/crypto/rand/rand_lib.c
index faec18dd99..289acf3039 100644
--- a/crypto/rand/rand_lib.c
+++ b/crypto/rand/rand_lib.c
@@ -200,17 +200,16 @@ size_t rand_drbg_get_entropy(RAND_DRBG *drbg,
             /*
              * Get random from parent, include our state as additional input.
              * Our lock is already held, but we need to lock our parent before
-             * generating bits from it.
+             * generating bits from it. (Note: taking the lock will be a no-op
+             * if locking if drbg->parent->lock == NULL.)
              */
-            if (drbg->parent->lock)
-                CRYPTO_THREAD_write_lock(drbg->parent->lock);
+            RAND_DRBG_lock(drbg->parent);
             if (RAND_DRBG_generate(drbg->parent,
                                    buffer, bytes_needed,
                                    0,
                                    (unsigned char *)drbg, sizeof(*drbg)) != 0)
                 bytes = bytes_needed;
-            if (drbg->parent->lock)
-                CRYPTO_THREAD_unlock(drbg->parent->lock);
+            RAND_DRBG_unlock(drbg->parent);
 
             entropy_available = RAND_POOL_add_end(pool, bytes, 8 * bytes);
         }
@@ -406,9 +405,9 @@ int RAND_poll(void)
         if (drbg == NULL)
             return 0;
 
-        CRYPTO_THREAD_write_lock(drbg->lock);
+        RAND_DRBG_lock(drbg);
         ret = rand_drbg_restart(drbg, NULL, 0, 0);
-        CRYPTO_THREAD_unlock(drbg->lock);
+        RAND_DRBG_unlock(drbg);
 
         return ret;
 
@@ -798,9 +797,9 @@ int RAND_priv_bytes(unsigned char *buf, int num)
         return 0;
 
     /* We have to lock the DRBG before generating bits from it. */
-    CRYPTO_THREAD_write_lock(drbg->lock);
+    RAND_DRBG_lock(drbg);
     ret = RAND_DRBG_bytes(drbg, buf, num);
-    CRYPTO_THREAD_unlock(drbg->lock);
+    RAND_DRBG_unlock(drbg);
     return ret;
 }
 
diff --git a/include/internal/rand.h b/include/internal/rand.h
index 575e6cadeb..a7d2912069 100644
--- a/include/internal/rand.h
+++ b/include/internal/rand.h
@@ -47,6 +47,10 @@ int RAND_DRBG_bytes(RAND_DRBG *drbg, unsigned char *out, size_t outlen);
 int RAND_DRBG_set_reseed_interval(RAND_DRBG *drbg, unsigned int interval);
 int RAND_DRBG_set_reseed_time_interval(RAND_DRBG *drbg, time_t interval);
 
+int RAND_DRBG_lock(RAND_DRBG *drbg);
+int RAND_DRBG_unlock(RAND_DRBG *drbg);
+int RAND_DRBG_enable_locking(RAND_DRBG *drbg);
+
 RAND_DRBG *RAND_DRBG_get0_master(void);
 RAND_DRBG *RAND_DRBG_get0_public(void);
 RAND_DRBG *RAND_DRBG_get0_private(void);
diff --git a/include/openssl/randerr.h b/include/openssl/randerr.h
index ae5a2ea992..4cfc06d9c4 100644
--- a/include/openssl/randerr.h
+++ b/include/openssl/randerr.h
@@ -24,6 +24,7 @@ int ERR_load_RAND_strings(void);
 # define RAND_F_DRBG_SETUP                                117
 # define RAND_F_GET_ENTROPY                               106
 # define RAND_F_RAND_BYTES                                100
+# define RAND_F_RAND_DRBG_ENABLE_LOCKING                  119
 # define RAND_F_RAND_DRBG_GENERATE                        107
 # define RAND_F_RAND_DRBG_INSTANTIATE                     108
 # define RAND_F_RAND_DRBG_NEW                             109
@@ -46,6 +47,7 @@ int ERR_load_RAND_strings(void);
 # define RAND_R_ALREADY_INSTANTIATED                      103
 # define RAND_R_ARGUMENT_OUT_OF_RANGE                     105
 # define RAND_R_CANNOT_OPEN_FILE                          121
+# define RAND_R_DRBG_ALREADY_INITIALIZED                  129
 # define RAND_R_DRBG_NOT_INITIALISED                      104
 # define RAND_R_ENTROPY_INPUT_TOO_LONG                    106
 # define RAND_R_ENTROPY_OUT_OF_RANGE                      124
@@ -64,6 +66,7 @@ int ERR_load_RAND_strings(void);
 # define RAND_R_NOT_A_REGULAR_FILE                        122
 # define RAND_R_NOT_INSTANTIATED                          115
 # define RAND_R_NO_DRBG_IMPLEMENTATION_SELECTED           128
+# define RAND_R_PARENT_LOCKING_NOT_ENABLED                130
 # define RAND_R_PERSONALISATION_STRING_TOO_LONG           116
 # define RAND_R_PRNG_NOT_SEEDED                           100
 # define RAND_R_RANDOM_POOL_OVERFLOW                      125
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 5005d9a628..b133c66546 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -4501,3 +4501,6 @@ EVP_sha512_256                          4442	1_1_1	EXIST::FUNCTION:
 EVP_sha512_224                          4443	1_1_1	EXIST::FUNCTION:
 OCSP_basic_sign_ctx                     4444	1_1_1	EXIST::FUNCTION:OCSP
 RAND_DRBG_bytes                         4445	1_1_1	EXIST::FUNCTION:
+RAND_DRBG_lock                          4446	1_1_1	EXIST::FUNCTION:
+RAND_DRBG_unlock                        4447	1_1_1	EXIST::FUNCTION:
+RAND_DRBG_enable_locking                4448	1_1_1	EXIST::FUNCTION:
-- 
2.25.1