crypto: jitterentropy - fix long-held spinlock contention
cover crypto: jitterentropy - fix long-held spinlock contention
Hi Stephan and Herbert,
We have identified a bug in crypto/jitterentropy-kcapi.c, introduced by commit bb5530e40824 and is present from v4.2-rc1 through v7.0-rc5.
The bug can be reached by the AF_ALG socket and may trigger watchdog-visible stalls or denial of service on sufficiently contended systems.
We propose a patch in the follow-up of this thread to mitigate this issue by using a mutex instead.
---- details below ----
Bug details:
Multiple accepted child sockets from one AF_ALG parent share a single jitterentropy_rng instance. jent_kcapi_random() serializes the state of that generator and runs entropy collection, currently with a spinlock. When multiple threads contend for the lock protecting that shared generator, all child sockets serialize on one shared generator instance, so additional readers accumulate lock contention on the same critical section. This can lead to decreased throughput, noticeable lag on interactive systems and potentially trigger watchdog-visible stalls or denial of service when the number of threads used approaches the number of logical CPUs available. The bug is potentially reachable by non-privileged users as AF_ALG is enabled and available to non-privileged users by default on some distributions, including Debian and Arch.
Required kernel config:
CONFIG_CRYPTO_JITTERENTROPY=yCONFIG_CRYPTO_USER_API=yCONFIG_CRYPTO_USER_API_RNG=y
Reproducer:
gcc -pthread poc.c -o poc
./poc <thread_count>
Note: tested on a Intel 13900H, noticeable lag appears when more than 12 of the 20 logical CPUs are utilized.
---8<--- BEGIN poc.c ---8<---
#define _GNU_SOURCE
#include <errno.h>
#include <linux/if_alg.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
struct worker_args {
int fd;
};static void die(const char *what)
{
perror(what);
exit(EXIT_FAILURE);
}static long long nsec_delta(const struct timespec *start, const struct timespec *end)
{
return (end->tv_sec - start->tv_sec) * 1000000000LL +
(end->tv_nsec - start->tv_nsec);
}static int bind_parent_socket(void)
{
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
};
int fd;
strcpy((char *)sa.salg_type, "rng");
strcpy((char *)sa.salg_name, "jitterentropy_rng");
fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (fd < 0)
die("socket(AF_ALG/rng)");
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0)
die("bind(AF_ALG/rng/jitterentropy_rng)");
return fd;
}static void *worker_main(void *opaque)
{
struct worker_args *args = opaque;
unsigned char buf[128];
unsigned long i = 0;
long long max_ns = 0;
for (;;) {
struct timespec start;
struct timespec end;
long long took_ns;
ssize_t ret;
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0)
die("clock_gettime(start)");
ret = read(args->fd, buf, sizeof(buf));
if (clock_gettime(CLOCK_MONOTONIC, &end) != 0)
die("clock_gettime(end)");
if (ret < 0)
die("read(AF_ALG)");
took_ns = nsec_delta(&start, &end);
if (took_ns > max_ns)
max_ns = took_ns;
i++;
if ((i % 10) == 0) {
printf("iter=%lu took_ms=%.3f max_ms=%.3f\n",
i, took_ns / 1000000.0, max_ns / 1000000.0);
fflush(stdout);
}
}
return NULL;
}int main(int argc, char **argv)
{
pthread_t *threads;
struct worker_args *args;
int parent_fd;
int thread_count = argc > 1 ? atoi(argv[1]) : 2;
int i;
parent_fd = bind_parent_socket();
if (parent_fd < 0)
return EXIT_FAILURE;
threads = calloc(thread_count, sizeof(*threads));
args = calloc(thread_count, sizeof(*args));
if (!threads || !args)
die("calloc");
for (i = 0; i < thread_count; i++) {
args[i].fd = accept(parent_fd, NULL, 0);
if (args[i].fd < 0)
die("accept");
if (pthread_create(&threads[i], NULL, worker_main, &args[i]) != 0)
die("pthread_create");
}
for (i = 0; i < thread_count; i++)
pthread_join(threads[i], NULL);
return EXIT_SUCCESS;
}---8<--- END poc.c ---8<---
Best regards
Haixin Xu
Haixin Xu (1):
crypto: jitterentropy - replace long-held spinlock with mutex
crypto/jitterentropy-kcapi.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)
base-commit: 62397b493e14107ae82d8b80938f293d95425bcb
Signature · 1 line
2.53.0
PATCH 1/1 crypto: jitterentropy - replace long-held spinlock with mutex
jent_kcapi_random() serializes the shared jitterentropy state, but it currently holds a spinlock across the jent_read_entropy() call. That path performs expensive jitter collection and SHA3 conditioning, so parallel readers can trigger stalls as contending waiters spin for the same lock.
To prevent non-preemptible lock hold, replace rng->jent_lock with a mutex so contended readers sleep instead of spinning on a shared lock held across expensive entropy generation.
--- crypto/jitterentropy-kcapi.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)
crypto/jitterentropy-kcapi.c +7 -7
@@ -42,6 +42,7 @@#include <linux/fips.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/slab.h> #include <linux/time.h> #include <crypto/internal/rng.h>
@@ -193,7 +194,7 @@ int jent_read_random_block(void *hash_state, char *dst, unsigned int dst_len)***************************************************************************/ struct jitterentropy { -spinlock_t jent_lock; +struct mutex jent_lock;struct rand_data *entropy_collector;struct crypto_shash *tfm;struct shash_desc *sdesc;
@@ -203,7 +204,7 @@ static void jent_kcapi_cleanup(struct crypto_tfm *tfm) {struct jitterentropy *rng =crypto_tfm_ctx (tfm); -spin_lock (&rng->jent_lock); +mutex_lock (&rng->jent_lock);if (rng->sdesc) {shash_desc_zero (rng->sdesc);
@@ -218,7 +219,7 @@ static void jent_kcapi_cleanup(struct crypto_tfm *tfm)if (rng->entropy_collector)jent_entropy_collector_free (rng->entropy_collector); rng->entropy_collector =NULL ; -spin_unlock (&rng->jent_lock); +mutex_unlock (&rng->jent_lock); }static int jent_kcapi_init (struct crypto_tfm *tfm)
@@ -228,7 +229,7 @@ static int jent_kcapi_init(struct crypto_tfm *tfm)struct shash_desc *sdesc;int size, ret =0 ; -spin_lock_init (&rng->jent_lock); +mutex_init (&rng->jent_lock);/* Use SHA3-256 as conditioner */ hash =crypto_alloc_shash (JENT_CONDITIONING_HASH ,0 ,0 );
@@ -257,7 +258,6 @@ static int jent_kcapi_init(struct crypto_tfm *tfm) goto err; } -spin_lock_init (&rng->jent_lock);return 0 ; err:
@@ -272,7 +272,7 @@ static int jent_kcapi_random(struct crypto_rng *tfm,struct jitterentropy *rng =crypto_rng_ctx (tfm);int ret =0 ; -spin_lock (&rng->jent_lock); +mutex_lock (&rng->jent_lock); ret =jent_read_entropy (rng->entropy_collector, rdata, dlen);
@@ -298,7 +298,7 @@ static int jent_kcapi_random(struct crypto_rng *tfm, ret = -EINVAL ; } -spin_unlock (&rng->jent_lock); +mutex_unlock (&rng->jent_lock);return ret; }
Signature · 1 line
2.53.0
Re: [PATCH 1/1] crypto: jitterentropy - replace long-held spinlock with mutex
Am Montag, 30. März 2026, 09:23:46 Mitteleuropäische Sommerzeit schrieb Haixin Xu:
Hi Haixin,
Quoted 9 lines
jent_kcapi_random()serializes the shared jitterentropy state, but it currently holds a spinlock across thejent_read_entropy()call. That path performs expensive jitter collection and SHA3 conditioning, so parallel readers can trigger stalls as contending waiters spin for the same lock.To prevent non-preemptible lock hold, replace
rng->jent_lockwith a mutex so contended readers sleep instead of spinning on a shared lock held across expensive entropy generation.
Thank you very much for the report and the patch.
Ciao
Stephan
Herbert Xu
maintainerRe: [PATCH 1/1] crypto: jitterentropy - replace long-held spinlock with mutex
In reply to Haixin Xucrypto/jitterentropy-kcapi.c static int jent_kcapi_random(struct crypto_rng *tfm, · line 301
Quoted 7 lines · Haixin Xu
crypto/jitterentropy-kcapi.c +1
-1@@ -298,7 +298,7 @@ static int jent_kcapi_random(struct crypto_rng *tfm, ret = -EINVAL ; } -spin_unlock (&rng->jent_lock); +mutex_unlock (&rng->jent_lock);
Are you sure that this function never gets called from softirq context?
Thanks,
Signature · 3 lines
Email: Herbert Xu <[email protected]> Home Page: http://gondor.apana.org.au/~herbert/ PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
Re: [PATCH 1/1] crypto: jitterentropy - replace long-held spinlock with mutex
Hi Herbert,
Quoted 2 lines · Herbert Xu
Are you sure that this function never gets called from softirq context?
Yes, for this implementation and its current call paths, I believe using a mutex here is safe.
As jitterentropy_rng is not used as the generic crypto_default_rng directly, i found only two users of it in tree:
1. AF_ALG RNG sockets via crypto/algif_rng.c _rng_recvmsg() calls crypto_rng_generate() from the calling task's syscall context.
2. DRBG seeding via crypto/drbg.c
drbg_seed() calls crypto_rng_get_bytes(), dbrg_seed() requires holding drbg->drbg_mutex.
There are also other RNG implementations in tree that hold a mutex in the generate function. For example:
- crypto/drbg.c uses drbg->drbg_mutex in drbg_generate_long
- drivers/crypto/qcom-rng.c uses rng->lock in qcom_rng_generate
So my reasoning is that using a mutex here should be safe.
Best regards
Haixin Xu
Herbert Xu
maintainerRe: [PATCH 1/1] crypto: jitterentropy - replace long-held spinlock with mutex
In reply to Haixin XuQuoted 16 lines · Haixin Xu
jent_kcapi_random()serializes the shared jitterentropy state, but it currently holds a spinlock across thejent_read_entropy()call. That path performs expensive jitter collection and SHA3 conditioning, so parallel readers can trigger stalls as contending waiters spin for the same lock.To prevent non-preemptible lock hold, replace
rng->jent_lockwith a mutex so contended readers sleep instead of spinning on a shared lock held across expensive entropy generation.Fixes bb5530e40824 ("crypto: jitterentropy - add jitterentropy RNG")Reported-by Yifan Wu <[email protected]>Reported-by Juefei Pu <[email protected]>Reported-by Yuan Tan <[email protected]>Suggested-by Xin Liu <[email protected]>Signed-off-by Haixin Xu <[email protected]>
Quoted 3 lines
--- crypto/jitterentropy-kcapi.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)
Patch applied. Thanks.
Signature · 3 lines
Email: Herbert Xu <[email protected]> Home Page: http://gondor.apana.org.au/~herbert/ PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt