diff --git a/library/psa_crypto_random.c b/library/psa_crypto_random.c index b0e4c859a7..af6e5de4b2 100644 --- a/library/psa_crypto_random.c +++ b/library/psa_crypto_random.c @@ -22,6 +22,12 @@ #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) @@ -65,6 +71,69 @@ psa_status_t psa_random_internal_seed(mbedtls_psa_random_context_t *rng) 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) @@ -77,15 +146,15 @@ psa_status_t psa_random_internal_generate( #if defined(MBEDTLS_THREADING_C) mbedtls_mutex_lock(&mbedtls_threading_psa_rngdata_mutex); #endif /* defined(MBEDTLS_THREADING_C) */ - int ret = mbedtls_psa_drbg_reseed(&rng->drbg, NULL, 0); - if (ret == 0) { + 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 (ret != 0) { - return mbedtls_to_psa_error(ret); + if (status != PSA_SUCCESS) { + return status; } } #endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */