crypton-2.2.0: cbits/tests/sysdrg/sysdrg.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
void crypton_sysdrg_test_key(uint8_t out[32]);
void crypton_sysdrg_test_lock(void);
void crypton_sysdrg_test_unlock(void);
int crypton_sysdrg_bytes(uint8_t *out, int len);
uint32_t crypton_sysdrg_generation(void);
uint64_t crypton_sysdrg_thread_used(void);
static uint64_t used_in_thread;
static int fail = 0;
static void check(const char *what, int ok) {
printf("%-52s %s\n", what, ok ? "ok" : "FAIL");
if (!ok) fail = 1;
}
/* Holds the process generator's lock for a moment, so that a fork can be
* made to happen while it is held. Releasing after a delay rather than on
* demand is deliberate: a prepare handler has to be able to take the lock,
* and a holder that waited to be told would stop the fork instead of the
* child. */
static pthread_mutex_t gate = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t gate_cv = PTHREAD_COND_INITIALIZER;
static int holding = 0;
static void *lock_holder(void *arg) {
(void) arg;
crypton_sysdrg_test_lock();
pthread_mutex_lock(&gate);
holding = 1;
pthread_cond_broadcast(&gate_cv);
pthread_mutex_unlock(&gate);
usleep(200000);
crypton_sysdrg_test_unlock();
return NULL;
}
static void *thread_body(void *arg) {
uint8_t *out = (uint8_t *) arg;
check("a second OS thread gets bytes", crypton_sysdrg_bytes(out, 32) == 32);
used_in_thread = crypton_sysdrg_thread_used();
return NULL;
}
int main(void) {
uint8_t a[32], b[32], big[4096];
memset(a, 0, 32); memset(b, 0, 32);
check("asking for 32 bytes gives 32", crypton_sysdrg_bytes(a, 32) == 32);
check("asking again gives something else",
crypton_sysdrg_bytes(b, 32) == 32 && memcmp(a, b, 32) != 0);
check("a 4096-byte request is filled", crypton_sysdrg_bytes(big, 4096) == 4096);
check("zero length is fine", crypton_sysdrg_bytes(a, 0) == 0);
/* two OS threads must not share a stream. The main thread has already
* produced over 4 KiB by here, so a shared generator would show it. */
uint8_t t1[32], t2[32];
pthread_t p1, p2;
pthread_create(&p1, NULL, thread_body, t1);
pthread_join(p1, NULL);
pthread_create(&p2, NULL, thread_body, t2);
pthread_join(p2, NULL);
check("two OS threads do not produce the same bytes", memcmp(t1, t2, 32) != 0);
check("a new OS thread starts its own stream, not the main one's",
used_in_thread == 32);
/* crossing the per-thread reseed limit (1 MiB) must not repeat */
uint8_t before[32], after[32];
crypton_sysdrg_bytes(before, 32);
for (int i = 0; i < 1100; i++) { uint8_t junk[1024]; crypton_sysdrg_bytes(junk, 1024); }
crypton_sysdrg_bytes(after, 32);
check("output after a reseed differs from before", memcmp(before, after, 32) != 0);
/* fork: parent and child must diverge */
int fds[2];
if (pipe(fds) != 0) { perror("pipe"); return 2; }
uint8_t parent[32], child[32];
pid_t pid = fork();
if (pid == 0) {
close(fds[0]);
crypton_sysdrg_bytes(child, 32);
ssize_t w = write(fds[1], child, 32);
_exit(w == 32 ? 0 : 1);
}
close(fds[1]);
crypton_sysdrg_bytes(parent, 32);
ssize_t r = read(fds[0], child, 32);
int status = 0; waitpid(pid, &status, 0);
check("the child was able to produce bytes", r == 32 && status == 0);
check("parent and child do not produce the same bytes", memcmp(parent, child, 32) != 0);
check("the fork was noticed", crypton_sysdrg_generation() == 0);
/* Backtracking resistance. The key that produced a draw is replaced
* once the bytes are out, so a state read afterwards is not the state
* that made them and cannot be wound back to remake them. Comparing
* the key before and against the key after is the whole of it: without
* the rekey it is the same key and the counter alone says where to
* start. */
uint8_t key_before[32], key_after[32], drawn[32];
memset(key_before, 0, 32); memset(key_after, 0, 32);
crypton_sysdrg_test_key(key_before);
crypton_sysdrg_bytes(drawn, 32);
crypton_sysdrg_test_key(key_after);
check("a draw replaces the key that made it",
memcmp(key_before, key_after, 32) != 0);
/* A fork while another thread holds the process generator's lock. The
* child's first draw has to reseed, the generation having changed, and
* reseeding takes that lock. With no prepare and parent handlers the
* child inherits it locked and waits on it for ever -- the thread that
* would release it did not come across the fork. The alarm is what
* turns that into a failure rather than a hung test run. */
pthread_t holder;
if (pthread_create(&holder, NULL, lock_holder, NULL) != 0) {
perror("pthread_create"); return 2;
}
pthread_mutex_lock(&gate);
while (!holding) pthread_cond_wait(&gate_cv, &gate);
pthread_mutex_unlock(&gate);
pid_t locked_pid = fork();
if (locked_pid == 0) {
uint8_t c[32];
alarm(5);
_exit(crypton_sysdrg_bytes(c, 32) == 32 ? 0 : 1);
}
pthread_join(holder, NULL);
int locked_status = 0;
waitpid(locked_pid, &locked_status, 0);
check("a child forked while the lock was held can draw",
WIFEXITED(locked_status) && WEXITSTATUS(locked_status) == 0);
return fail;
}