Protect the PSA RNG from duplication on fork

If a process forks with the PSA subsystem active, the PSA RNG state is
duplicated. This led to the parent process and the child process generating
the same sequence of random numbers.

Fix this by forcing a reseed if the value of `getpid()` changes. This is the
same technique used in OpenSSL ≥1.1.1d.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
This commit is contained in:
Gilles Peskine 2026-01-16 19:30:34 +01:00
parent ce8a71c071
commit 0b93865aed
3 changed files with 51 additions and 0 deletions

View File

@ -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

View File

@ -13,11 +13,17 @@
#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 <unistd.h>
#endif
void psa_random_internal_init(mbedtls_psa_random_context_t *rng)
{
/* Set default configuration if
@ -53,6 +59,9 @@ 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);
}
@ -60,6 +69,27 @@ 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) */
int ret = mbedtls_psa_drbg_reseed(&rng->drbg, NULL, 0);
if (ret == 0) {
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);
}
}
#endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */
while (output_size > 0) {
size_t request_size =
(output_size > MBEDTLS_PSA_RANDOM_MAX_REQUEST ?

View File

@ -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.