diff --git a/ChangeLog.d/rng-cloning.txt b/ChangeLog.d/rng-cloning.txt index fc7d4db7f5..6f68d26bb8 100644 --- a/ChangeLog.d/rng-cloning.txt +++ b/ChangeLog.d/rng-cloning.txt @@ -6,6 +6,10 @@ Features prediction resistance in the PSA random generator. Security + * If an application called psa_crypto_init() then fork() and continued to + use cryptography APIs (possibly indirectly, e.g. for TLS), the random + generator states were duplicated. Fix this by forcing a RNG reseed in + the child process. CVE-2026-25835 * Applications running in environments where the application state is cloned (for example due to resuming a frozen system state multiple times, or due to cloning a virtual machine image) should arrange to diff --git a/framework b/framework index 9b92164c47..3d57272bbc 160000 --- a/framework +++ b/framework @@ -1 +1 @@ -Subproject commit 9b92164c47fdaecb2600b417733507e2a105c3a5 +Subproject commit 3d57272bbc67fb576a7737155835d2db700cc469 diff --git a/library/psa_crypto_random.c b/library/psa_crypto_random.c index f5eb658ab6..af6e5de4b2 100644 --- a/library/psa_crypto_random.c +++ b/library/psa_crypto_random.c @@ -13,11 +13,23 @@ #include "psa_crypto_core.h" #include "psa_crypto_random.h" #include "psa_crypto_random_impl.h" +#include "threading_internal.h" #if defined(MBEDTLS_PSA_INJECT_ENTROPY) #include "entropy_poll.h" #endif +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) +/* For getpid(), for fork protection */ +#include +#if defined(MBEDTLS_HAVE_TIME) +#include +#else +/* For gettimeofday(), for fork protection without actual entropy */ +#include +#endif +#endif + void psa_random_internal_init(mbedtls_psa_random_context_t *rng) { /* Set default configuration if @@ -53,13 +65,100 @@ psa_status_t psa_random_internal_seed(mbedtls_psa_random_context_t *rng) const unsigned char drbg_seed[] = "PSA"; int ret = mbedtls_psa_drbg_seed(&rng->drbg, &rng->entropy, drbg_seed, sizeof(drbg_seed) - 1); +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) + rng->pid = getpid(); +#endif return mbedtls_to_psa_error(ret); } +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) +static psa_status_t psa_random_internal_reseed_child( + mbedtls_psa_random_context_t *rng, + intmax_t pid) +{ + /* Reseeding from actual entropy gives the child a unique RNG state + * which the parent process cannot predict, and wipes the + * parent's RNG state from the child. + * + * However, in some library configurations, there is no actual + * entropy source, only a nonvolatile seed (MBEDTLS_ENTROPY_NV_SEED + * enabled and no actual entropy source enabled). In such a + * configuration, the reseed operation is deterministic and + * always injects the same content, so with the DRBG reseed + * process alone, for example, two child processes forked in + * close sequence would end up with the same RNG state. + + * To avoid this, we use a personalization string that has a high + * likelihood of being unique. This way, the child has a unique state. + * The parent can predict the child's RNG state until the next time + * it reseeds or generates some random output, but that's + * unavoidable in the absence of actual entropy. + */ + struct { + /* Using the PID mostly guarantees that each child gets a + * unique state. */ + /* Use intmax_t, not pid_t, because some Unix-like platforms + * don't define pid_t, or more likely nowadays they define + * pid_t but only with certain platform macros which might not + * be the exact ones we use. In practice, this only costs + * a couple of instructions to pass and compare two words + * rather than one. + */ + intmax_t pid; + /* In case an old child had died and its PID is reused for + * a new child of the same process, also include the time. */ +#if defined(MBEDTLS_HAVE_TIME) + mbedtls_ms_time_t now; +#else + struct timeval now; +#endif + } perso; + memset(&perso, 0, sizeof(perso)); + perso.pid = pid; +#if defined(MBEDTLS_HAVE_TIME) + perso.now = mbedtls_ms_time(); +#else + /* We don't have mbedtls_ms_time(), but the platform has getpid(). + * Use gettimeofday(), which is a classic Unix function. Modern POSIX + * has stopped requiring gettimeofday() (in favor of clock_gettime()), + * but this is fallback code for restricted configurations, so it's + * more likely to be used on embedded platforms that only have a subset + * of Unix APIs and are more likely to have the classic gettimeofday(). */ + if (gettimeofday(&perso.now, NULL) == -1) { + return PSA_ERROR_INSUFFICIENT_ENTROPY; + } +#endif + int ret = mbedtls_psa_drbg_reseed(&rng->drbg, + (unsigned char *) &perso, sizeof(perso)); + return mbedtls_to_psa_error(ret); +} +#endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */ + psa_status_t psa_random_internal_generate( mbedtls_psa_random_context_t *rng, uint8_t *output, size_t output_size) { +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) + intmax_t pid = getpid(); + if (pid != rng->pid) { + /* This is a (grand...)child of the original process, but + * we inherited the RNG state from our parent. We must reseed! */ +#if defined(MBEDTLS_THREADING_C) + mbedtls_mutex_lock(&mbedtls_threading_psa_rngdata_mutex); +#endif /* defined(MBEDTLS_THREADING_C) */ + psa_status_t status = psa_random_internal_reseed_child(rng, pid); + if (status == PSA_SUCCESS) { + rng->pid = pid; + } +#if defined(MBEDTLS_THREADING_C) + mbedtls_mutex_unlock(&mbedtls_threading_psa_rngdata_mutex); +#endif /* defined(MBEDTLS_THREADING_C) */ + if (status != PSA_SUCCESS) { + return status; + } + } +#endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */ + while (output_size > 0) { size_t request_size = (output_size > MBEDTLS_PSA_RANDOM_MAX_REQUEST ? diff --git a/library/psa_crypto_random_impl.h b/library/psa_crypto_random_impl.h index 3ed85e5c4e..68bb3a39f8 100644 --- a/library/psa_crypto_random_impl.h +++ b/library/psa_crypto_random_impl.h @@ -70,6 +70,23 @@ typedef struct { void (* entropy_free)(mbedtls_entropy_context *ctx); mbedtls_entropy_context entropy; mbedtls_psa_drbg_context_t drbg; +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) + /* Fork protection: normally pid = getpid(). If the value changes, + * we are in a (grand)*child of the original process, so reseed + * the RNG to ensure that the child and the original process have + * distinct RNG states. See psa_random_internal_generate(). + * + * The type is intmax_t, not pid_t, for portability reasons: + * pid_t is defined in `unistd.h`, but on some platforms, it may + * only be defined if a certain compatibility level is requested + * by defining a macro such as _POSIX_C_SOURCE or _XOPEN_SOURCE. + * The macro needs to be defined before any system header, which + * may be hard to do in some C files that include this header + * (e.g. test suites). So we sidestep this complication, at the + * cost of possibly a few more instructions to compare pid values. + */ + intmax_t pid; +#endif } mbedtls_psa_random_context_t; /** Initialize the PSA DRBG. diff --git a/tests/suites/test_suite_psa_crypto_entropy.data b/tests/suites/test_suite_psa_crypto_entropy.data index 03dd6c8222..ba3797eca2 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.data +++ b/tests/suites/test_suite_psa_crypto_entropy.data @@ -104,6 +104,18 @@ PSA external RNG failure: RSA PKCS#1v1.5 (software implementation) depends_on:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_EXPORT:MBEDTLS_PSA_BUILTIN_ALG_RSA_PKCS1V15_SIGN external_rng_failure_sign:PSA_KEY_TYPE_RSA_KEY_PAIR:"3082025e02010002818100af057d396ee84fb75fdbb5c2b13c7fe5a654aa8aa2470b541ee1feb0b12d25c79711531249e1129628042dbbb6c120d1443524ef4c0e6e1d8956eeb2077af12349ddeee54483bc06c2c61948cd02b202e796aebd94d3a7cbf859c2c1819c324cb82b9cd34ede263a2abffe4733f077869e8660f7d6834da53d690ef7985f6bc3020301000102818100874bf0ffc2f2a71d14671ddd0171c954d7fdbf50281e4f6d99ea0e1ebcf82faa58e7b595ffb293d1abe17f110b37c48cc0f36c37e84d876621d327f64bbe08457d3ec4098ba2fa0a319fba411c2841ed7be83196a8cdf9daa5d00694bc335fc4c32217fe0488bce9cb7202e59468b1ead119000477db2ca797fac19eda3f58c1024100e2ab760841bb9d30a81d222de1eb7381d82214407f1b975cbbfe4e1a9467fd98adbd78f607836ca5be1928b9d160d97fd45c12d6b52e2c9871a174c66b488113024100c5ab27602159ae7d6f20c3c2ee851e46dc112e689e28d5fcbbf990a99ef8a90b8bb44fd36467e7fc1789ceb663abda338652c3c73f111774902e840565927091024100b6cdbd354f7df579a63b48b3643e353b84898777b48b15f94e0bfc0567a6ae5911d57ad6409cf7647bf96264e9bd87eb95e263b7110b9a1f9f94acced0fafa4d024071195eec37e8d257decfc672b07ae639f10cbb9b0c739d0c809968d644a94e3fd6ed9287077a14583f379058f76a8aecd43c62dc8c0f41766650d725275ac4a1024100bb32d133edc2e048d463388b7be9cb4be29f4b6250be603e70e3647501c97ddde20a4e71be95fd5e71784e25aca4baf25be5738aae59bbfe1c997781447a2b24":PSA_ALG_RSA_PKCS1V15_SIGN_RAW:32 +PSA RNG after fork: parent gets random never +psa_rng_fork:-1 + +PSA RNG after fork: parent gets random before +psa_rng_fork:0 + +PSA RNG after fork: parent gets random between +psa_rng_fork:1 + +PSA RNG after fork: parent gets random after +psa_rng_fork:2 + PSA validate entropy injection: good, minimum size validate_entropy_seed_injection:MBEDTLS_PSA_INJECT_ENTROPY_MIN_SIZE:PSA_SUCCESS:MBEDTLS_PSA_INJECT_ENTROPY_MIN_SIZE:PSA_ERROR_NOT_PERMITTED diff --git a/tests/suites/test_suite_psa_crypto_entropy.function b/tests/suites/test_suite_psa_crypto_entropy.function index 413e1ea5d8..5b0bba3cb7 100644 --- a/tests/suites/test_suite_psa_crypto_entropy.function +++ b/tests/suites/test_suite_psa_crypto_entropy.function @@ -2,6 +2,10 @@ #include #include +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) +#include +#endif + #include /* Some tests in this module configure entropy sources. */ @@ -149,6 +153,20 @@ exit: } #endif /* !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) */ +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) && \ + !defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG) +static void child_psa_get_random(void *param, + uint8_t *output, size_t output_size, + size_t *output_length) +{ + (void) param; + PSA_ASSERT(psa_generate_random(output, output_size)); + *output_length = output_size; +exit: + ; +} +#endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE && !MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG */ + /* Calculating the minimum allowed entropy size in bytes */ #define MBEDTLS_PSA_INJECT_ENTROPY_MIN_SIZE MAX(MBEDTLS_ENTROPY_MIN_PLATFORM, \ MBEDTLS_ENTROPY_BLOCK_SIZE) @@ -662,6 +680,81 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:!MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG:MBEDTLS_PLATFORM_IS_UNIXLIKE */ +/* Test that if a program calls fork(), the PSA RNG returns different byte + * sequences in each child process, and that they're different from the + * parent process. + * + * The argument parent_when controls when the parent calls + * psa_generate_random(): -1 = never, 0 = before forking, >0 = after forking + * that many children. + * + * Note that passing tests don't mean that everything is fine, they only + * mean that things are not too obviously broken. It's possible to badly + * design the RNG so that, for example, different child processes will + * have the same RNG output sequence but at an offset, or so that a child + * process's RNG is seeded from RNG output of the parent (making it + * predictable if an adversary happens to be able to get the right chunk + * of RNG output from th parent), or different sequencing of forking + * grand^n-children ends up with them having identical RNG output sequences. + * These bad designs are practically impossible to detect through testing, + * and must be excluded by human reasoning on the RNG design. + */ +void psa_rng_fork(int parent_when) +{ + struct { + /* We read 16 bytes from the RNG. This is large enough so that the + * probability of a coincidence is negligible, and small enough that + * the RNG won't spontaneously decide reseed to unless it has + * prediction resistance. */ + unsigned char rng_output[16]; + } child[2], parent; + memset(child, 0, sizeof(child)); + memset(&parent, 0, sizeof(parent)); + + PSA_INIT(); + + /* Create some child processes, have them generate random data + * and report that data back to the original process. */ + for (size_t i = 0; i < ARRAY_LENGTH(child); i++) { + mbedtls_test_set_step(i); + if ((size_t) parent_when == i) { + PSA_ASSERT(psa_generate_random(parent.rng_output, + sizeof(parent.rng_output))); + } + size_t length; + TEST_EQUAL(mbedtls_test_fork_run_child( + child_psa_get_random, NULL, + child[i].rng_output, sizeof(child[i].rng_output), + &length), 0); + TEST_EQUAL(length, sizeof(child[i].rng_output)); + } + + if (parent_when == ARRAY_LENGTH(child)) { + PSA_ASSERT(psa_generate_random(parent.rng_output, + sizeof(parent.rng_output))); + } + + /* Did the children have different RNG states? */ + TEST_ASSERT(memcmp(child[0].rng_output, + child[1].rng_output, + sizeof(parent.rng_output)) != 0); + /* If parent_when >= 0: did the children have different RNG states + * from the parent? + * If parent_when < 0: did the children get nonzero RNG output? + */ + for (size_t i = 0; i < ARRAY_LENGTH(child); i++) { + mbedtls_test_set_step(i); + TEST_ASSERT(memcmp(parent.rng_output, + child[i].rng_output, + sizeof(parent.rng_output)) != 0); + } + +exit: + PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_PSA_INJECT_ENTROPY */ void validate_entropy_seed_injection(int seed_length_a, int expected_status_a,