Sysctls y el grupo de bloqueo.
Puedes aumentar el valor del kernel.random.read_wakeup_threshold
sysctl. Este sysctl cambia el comportamiento del grupo de bloqueo , lo que obliga a los usuarios del grupo de bloqueo a esperar Hasta que la estimación de entropía exceda este valor. De la página de manual en random(4)
:
read_wakeup_threshold
This file contains the number of bits of entropy required for
waking up processes that sleep waiting for entropy from
/dev/random. The default is 64.
Sin embargo, tenga en cuenta que el comportamiento de bloqueo que describe solo se aplica al sistema en el inicio muy temprano. Una vez que tenga suficiente entropía, cambiará a un comportamiento de no bloqueo, independientemente de cuán baja sea la estimación actual. Esto se debe a que solo necesita una cierta cantidad de bits de entropía una vez , y puede crear una cantidad virtualmente ilimitada de datos pseudoaleatorios criptográficamente seguros una vez que los tenga. La estimación de entropía bajando no cambia este hecho.
Respondiendo a tu pregunta exacta
Ahora, ¿es posible cambiar este umbral inicial para el grupo de no bloqueo (si no quieres aprender todo el meollo del asunto, salta al final de esta respuesta)? Sospeché que no lo era, pero no estaba seguro, así que fui a consultar la documentación más autorizada disponible, la fuente. El syscall getrandom(2)
es definido en el controlador de aleatoriedad del kernel. Tenga en cuenta que esto es específico del kernel de Linux 4.14 (los cambios importantes en el controlador de aleatoriedad se hicieron en 4.8).
Comentarios agregados por mí para aclarar.
getrandom()
SYSCALL_DEFINE3(getrandom, char __user *, buf, size_t, count,
unsigned int, flags)
{
int ret;
// If the flags bitmask is invalid, return an error.
if (flags & ~(GRND_NONBLOCK|GRND_RANDOM))
return -EINVAL;
// Cap the requested size at the maximum value.
if (count > INT_MAX)
count = INT_MAX;
// If the blocking pool is selected, read from it and return.
// The _random_read() function will deal with blocking.
if (flags & GRND_RANDOM)
return _random_read(flags & GRND_NONBLOCK, buf, count);
// At this point, we know the non-blocking pool was selected.
// Is the CRNG ready? Evaluates true if it is not.
if (!crng_ready()) {
// If we want to bail out and not block, return -EAGAIN.
if (flags & GRND_NONBLOCK)
return -EAGAIN;
// Otherwise, block until random bytes become available.
ret = wait_for_random_bytes();
if (unlikely(ret))
return ret;
}
// Finally, read from the non-blocking pool and return.
return urandom_read(NULL, buf, count, NULL);
}
Bien, la mayoría de esto es bastante evidente, pero ¿para qué sirven las funciones crng_ready()
y wait_for_random_bytes()
? El último está definido en el mismo archivo.
wait_for_random_bytes()
int wait_for_random_bytes(void)
{
// If crng_ready() returns true (which is likely), return 0.
if (likely(crng_ready()))
return 0;
// Otherwise, wait until it does return true before returning.
return wait_event_interruptible(crng_init_wait, crng_ready());
}
EXPORT_SYMBOL(wait_for_random_bytes);
Así que ahora sabemos en la definición de getrandom()
que, si se selecciona el grupo de no bloqueo, se comprobará si crng_ready()
devuelve verdadero. Si no devuelve verdadero, entonces esperaremos, durmiendo hasta que lo haga. ¿Qué hace crng_ready()
? Resulta que está definido como una macro simple.
crng_ready()
// If the crng_init variable is > 0 (which is likely), evaluate true.
#define crng_ready() (likely(crng_init > 0))
La variable comienza como cero, pero lo que importa es dónde se establece exactamente en 1. Parece que esto se hace en la función crng_fast_load()
, definido aquí .
crng_fast_load()
static int crng_fast_load(const char *cp, size_t len)
{
unsigned long flags;
char *p;
// Enter the atomic section.
if (!spin_trylock_irqsave(&primary_crng.lock, flags))
return 0;
// If crng_ready() is already true, leave the atomic section and return.
if (crng_ready()) {
spin_unlock_irqrestore(&primary_crng.lock, flags);
return 0;
}
// Mix in the values at cp with the CRNG state. Increment crng_init_cnt
// for each byte from cp that gets mixed in (up to len times).
p = (unsigned char *) &primary_crng.state[4];
while (len > 0 && crng_init_cnt < CRNG_INIT_CNT_THRESH) {
p[crng_init_cnt % CHACHA20_KEY_SIZE] ^= *cp;
cp++; crng_init_cnt++; len--;
}
// Leave the atomic section.
spin_unlock_irqrestore(&primary_crng.lock, flags);
// If crng_init_cnt is >= CRNG_INIT_CNT_THRESH, set crng_init to 1.
if (crng_init_cnt >= CRNG_INIT_CNT_THRESH) {
invalidate_batched_entropy();
crng_init = 1;
wake_up_interruptible(&crng_init_wait);
pr_notice("random: fast init done\n");
}
return 1;
}
A partir de esto, vemos que crng_init_cnt
se incrementa para cada byte que crng_fast_load()
toma. La función se llama temprano en el arranque en varias funciones de recopilación de entropía para agregar la mayor cantidad posible de datos al grupo desde el principio. ¡Casi estámos allí! Lo último que debe hacer es determinar cuál es el valor de CRNG_INIT_CNT_THRESH
, definido aquí .
CRNG_INIT_CNT_THRESH y CHACHA20_KEY_SIZE
#define CRNG_INIT_CNT_THRESH (2*CHACHA20_KEY_SIZE)
Entonces es el doble CHACHA20_KEY_SIZE
. Este está definido en un archivo de encabezado, crypto/chacha20.h
.
#define CHACHA20_KEY_SIZE 32
Entonces CRNG_INIT_CNT_THRESH
es 64. ¡Y ahí está tu respuesta!
Recapitulación
Veamos a dónde conduce todo esto:
-
CHACHA20_KEY_SIZE
está codificado en 32.
-
CRNG_INIT_CNT_THRESH
es doble CHACHA20_KEY_SIZE
, lo que hace que sea 64.
-
crng_init_cnt
se incrementa por cada byte de aleatoriedad temprana recopilada.
- Cuando se reúnen al menos 64 bytes de aleatoriedad,
crng_init
se establece en 1.
- Cuando
crng_init
es 1, crng_ready()
se evalúa como verdadero.
- Cuando
crng_ready()
se evalúa como verdadero, getrandom()
se reanuda y devuelve.
La cantidad de entropía temprana requerida antes de que getrandom()
se reanude y devuelva no es de 128 bits. Ya está codificado a 64 bytes (512 bits), el doble de la cantidad que quería.