From: Pavel Charvat Date: Tue, 22 Mar 2022 15:49:04 +0000 (+0000) Subject: Random: Implemented strong random source. X-Git-Tag: v6.5.14~3 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=8ad9729461149b6c7a46c01e2cc305b23c19af9a;p=libucw.git Random: Implemented strong random source. --- diff --git a/ucw/random-strong.c b/ucw/random-strong.c index 3b19f5fd..f5728233 100644 --- a/ucw/random-strong.c +++ b/ucw/random-strong.c @@ -14,7 +14,10 @@ #include #include +#include #include +#include +#include #ifdef CONFIG_UCW_GETRANDOM #include @@ -96,3 +99,188 @@ bool strongrand_getrandom_mem_try(void *buf UNUSED, size_t size UNUSED) return false; } #endif + +/*** Generic interface ***/ + +void strongrand_close(struct strongrand *c) +{ + if (c && c->close) + c->close(c); +} + +void strongrand_reset(struct strongrand *c) +{ + if (c->reset) + c->reset(c); +} + +void strongrand_mem(struct strongrand *c, void *ptr, size_t size) +{ + ASSERT(c->read); + while (size) + { + int n = MIN(size, 0x7fffffff); + c->read(c, ptr, n); + size -= n; + ptr = (byte *)ptr + n; + } +} + +/*** Kernel random source ***/ + +struct strongrand_std { + struct strongrand sr; + int fd; // Non-negative descriptor or special STRONGRAND_FD_x value + uint flags; // STRONGRAND_STD_x + uint buf_size; // Size of @buf or 0 + uint buf_avail; // Remaining buffered bytes, <= @buf_size + pid_t last_pid; // Last known process id; maintained with STRONGRAND_STD_x_RESET + byte buf[]; // Buffered randomness +}; + +// Use getrandom() +#define STRONGRAND_FD_KERNEL_GETRANDOM -1 + +// Not yet open /dev/[u]random +#define STRONGRAND_FD_KERNEL_DEVICE -2 + +static void strongrand_std_close(struct strongrand *sr) +{ + struct strongrand_std *srs = (struct strongrand_std *)sr; + if (srs->fd >= 0) + close(srs->fd); + DBG("RANDOM[%s]: Closed", sr->name); + xfree(srs); +} + +static void strongrand_std_reset(struct strongrand *sr) +{ + struct strongrand_std *srs = (struct strongrand_std *)sr; + srs->buf_avail = 0; +} + +static void strongrand_std_open_device(struct strongrand_std *srs) +{ + ASSERT(srs->fd == STRONGRAND_FD_KERNEL_DEVICE); + ASSERT(srs->flags & (STRONGRAND_STD_URANDOM | STRONGRAND_STD_RANDOM)); + const char *path = (srs->flags & STRONGRAND_STD_URANDOM) ? "/dev/urandom" : "/dev/random"; + int fd = open(path, O_RDONLY); + if (fd < 0) + die("RANDOM[%s]: Cannot open %s: %m", srs->sr.name, path); + srs->fd = fd; + DBG("RANDOM[%s]: Opened device, path=%s, fd=%d", srs->sr.name, path, fd); +} + +static void strongrand_std_read_unbuffered(struct strongrand *sr, byte *ptr, int size) +{ + struct strongrand_std *srs = (struct strongrand_std *)sr; +#ifdef CONFIG_UCW_GETRANDOM + if (srs->fd == STRONGRAND_FD_KERNEL_GETRANDOM) + { + while (size) + { + int n = getrandom(ptr, size, (srs->flags & STRONGRAND_STD_RANDOM) ? GRND_RANDOM : 0); + if (n < 0) + { + if (errno == EINTR || errno == EAGAIN) + continue; + die("RANDOM[%s]: Failed to read %u bytes with getrandom(): %m", srs->sr.name, size); + } + DBG("RANDOM[%s]: Read %u bytes with getrandom()", srs->sr.name, n); + ASSERT(n <= size); + ptr += n; + size -= n; + } + return; + } +#endif + if (srs->fd < 0) + { + ASSERT(srs->flags & STRONGRAND_STD_DELAYED); + strongrand_std_open_device(srs); + } + while (size) + { + int n = read(srs->fd, ptr, size); + if (n < 0) + { + if (errno == EINTR || errno == EAGAIN) + continue; + die("RANDOM[%s]: Failed to read %u bytes: %m", srs->sr.name, size); + } + ASSERT(n <= size); + DBG("RANDOM[%s]: Read %u bytes", srs->sr.name, n); + ptr += n; + size -= n; + } +} + +static void strongrand_std_read_buffered(struct strongrand *sr, byte *ptr, int size) +{ + struct strongrand_std *srs = (struct strongrand_std *)sr; + if (srs->flags & (STRONGRAND_STD_AUTO_RESET | STRONGRAND_STD_ASSERT_RESET)) + { + pid_t pid = getpid(); + if (pid != srs->last_pid) + { + DBG("RANDOM[%s]: Detected changed pid from %u to %u", sr->name, (uint)srs->last_pid, (uint)pid); + srs->last_pid = pid; + if (srs->flags & STRONGRAND_STD_ASSERT_RESET) + ASSERT(!srs->buf_avail); + srs->buf_avail = 0; + } + } + while (size) + { + if (!srs->buf_avail) + { + DBG("RANDOM[%s]: Refilling buffer", srs->sr.name); + strongrand_std_read_unbuffered(sr, srs->buf, srs->buf_size); + srs->buf_avail = srs->buf_size; + } + uint n = MIN(srs->buf_avail, (uint)size); + memcpy(ptr, srs->buf + (srs->buf_size - srs->buf_avail), n); + srs->buf_avail -= n; + size -= n; + ptr += n; + } +} + +static struct strongrand *strongrand_std_open_internal(int fd, const char *name, uint buf_size, uint flags) +{ + struct strongrand_std *srs = xmalloc(sizeof(*srs) + buf_size); + bzero(srs, sizeof(*srs)); + srs->sr.name = name; + srs->sr.close = strongrand_std_close; + srs->sr.reset = buf_size ? strongrand_std_reset : NULL; + srs->sr.read = buf_size ? strongrand_std_read_buffered : strongrand_std_read_unbuffered; + srs->fd = fd; + srs->flags = flags; + srs->buf_size = buf_size; + if (flags & (STRONGRAND_STD_AUTO_RESET | STRONGRAND_STD_ASSERT_RESET)) + srs->last_pid = getpid(); + return &srs->sr; +} + +struct strongrand *strongrand_std_open(uint buf_size, uint flags) +{ + const char *name; + if (flags & STRONGRAND_STD_URANDOM) + { + ASSERT(!(flags & STRONGRAND_STD_RANDOM)); + name = "urandom"; + } + else + { + ASSERT(flags & STRONGRAND_STD_RANDOM); + name = "random"; + } +#ifdef CONFIG_UCW_GETRANDOM + if (strongrand_getrandom_detect()) + return strongrand_std_open_internal(STRONGRAND_FD_KERNEL_GETRANDOM, name, buf_size, flags); +#endif + struct strongrand *sr = strongrand_std_open_internal(STRONGRAND_FD_KERNEL_DEVICE, name, buf_size, flags); + if (!(flags & STRONGRAND_STD_DELAYED)) + strongrand_std_open_device((struct strongrand_std *)sr); + return sr; +} diff --git a/ucw/random-test.c b/ucw/random-test.c index 1e51aa74..2fd37595 100644 --- a/ucw/random-test.c +++ b/ucw/random-test.c @@ -20,6 +20,7 @@ #include static int o_test_fast; +static int o_test_strong; static double o_bench_scale = 1.0; static int o_bench_only_safe; @@ -27,6 +28,7 @@ static int o_bench_all; static int o_bench_libs; static int o_bench_legacy; static int o_bench_fast; +static int o_bench_strong; static struct opt_section options = { OPT_ITEMS { @@ -35,10 +37,12 @@ static struct opt_section options = { OPT_HELP("Options:"), OPT_HELP_OPTION, OPT_BOOL(0, "test-fast", o_test_fast, 0, "\tTest fast random generator"), + OPT_BOOL(0, "test-strong", o_test_strong, 0, "\tTest strong random generator"), OPT_BOOL(0, "bench-all", o_bench_all, 0, "\tBench everything"), OPT_BOOL(0, "bench-libs", o_bench_libs, 0, "\tBench libc"), OPT_BOOL(0, "bench-legacy", o_bench_legacy, 0, "\tBench legacy interface"), OPT_BOOL(0, "bench-fast", o_bench_fast, 0, "\tBench fast random generator"), + OPT_BOOL(0, "bench-strong", o_bench_strong, 0, "\tBench strong random generator"), OPT_DOUBLE(0, "bench-scale", o_bench_scale, OPT_REQUIRED_VALUE, "\tIncrease/decrease the length of benchmark (default: 1.0)"), OPT_BOOL(0, "bench-only-safe", o_bench_only_safe, 0, "\tDon't benchmark too verbose or risky functions"), OPT_END, @@ -79,6 +83,23 @@ static void test_fast(void) printf("OK\n"); } +static void test_strong(void) +{ + byte buf[100]; + for (uint j = 0; j < 2; j++) + { + struct strongrand *r = strongrand_std_open((j == 0) ? 0 : 128, STRONGRAND_STD_URANDOM); + for (uint i = 1; i <= 100; i++) + { + strongrand_mem(r, buf, i); + if (i % 10 == 0) + strongrand_reset(r); + } + strongrand_close(r); + } + printf("OK\n"); +} + #define BENCH(func) BENCH_SLOW(1, func) #define BENCH_SLOW(mult, func) \ do { \ @@ -189,6 +210,30 @@ static void bench_fast(void) fastrand_delete(r); } +static void bench_strong(void) +{ + byte buf[100]; + struct strongrand *r; + + msg(L_INFO, "Benchmarking unbuffered strong random generator"); + r = strongrand_std_open(0, STRONGRAND_STD_URANDOM); + BENCH_SLOW(50, strongrand_mem(r, buf, 1)); + BENCH_SLOW(200, strongrand_mem(r, buf, 100)); + strongrand_close(r); + + msg(L_INFO, "Benchmarking buffered strong random generator"); + r = strongrand_std_open(128, STRONGRAND_STD_URANDOM); + BENCH_SLOW(50, strongrand_mem(r, buf, 1)); + BENCH_SLOW(200, strongrand_mem(r, buf, 100)); + strongrand_close(r); + + msg(L_INFO, "Benchmarking buffered strong random generator with autoreset"); + r = strongrand_std_open(128, STRONGRAND_STD_URANDOM | STRONGRAND_STD_AUTO_RESET); + BENCH_SLOW(50, strongrand_mem(r, buf, 1)); + BENCH_SLOW(200, strongrand_mem(r, buf, 100)); + strongrand_close(r); +} + int main(int argc UNUSED, char **argv) { cf_def_file = INSTALL_CONFIG_DIR "/common"; @@ -196,6 +241,8 @@ int main(int argc UNUSED, char **argv) if (o_test_fast) test_fast(); + if (o_test_strong) + test_strong(); if (o_bench_all || o_bench_libs) bench_libs(); @@ -203,6 +250,8 @@ int main(int argc UNUSED, char **argv) bench_legacy(); if (o_bench_all || o_bench_fast) bench_fast(); + if (o_bench_all || o_bench_strong) + bench_strong(); return 0; } diff --git a/ucw/random-test.t b/ucw/random-test.t index 0f866cd0..0027f60d 100644 --- a/ucw/random-test.t +++ b/ucw/random-test.t @@ -1,3 +1,7 @@ Name: random-fast Run: ../obj/ucw/random-test --test-fast Out: OK + +Name: random-strong +Run: ../obj/ucw/random-test --test-strong +Out: OK diff --git a/ucw/random.h b/ucw/random.h index 1d3b8a14..ec31b0de 100644 --- a/ucw/random.h +++ b/ucw/random.h @@ -25,6 +25,10 @@ #define fastrand_max_u64 ucw_fastrand_max_u64 #define fastrand_mem ucw_fastrand_mem #define fastrand_double ucw_fastrand_double +#define strongrand_close ucw_strongrand_close +#define strongrand_reset ucw_strongrand_reset +#define strongrand_mem ucw_strongrand_mem +#define strongrand_std_open ucw_strongrand_std_open #define strongrand_getrandom_mem_try ucw_strongrand_getrandom_mem_try #endif @@ -104,7 +108,61 @@ double fastrand_double(struct fastrand *c); /* random-strong.c */ -/* FIXME: interface for strong randomness */ +/** + * Context structure for a strong random generator. + * Useful for things like generating random ids or cryptography. + * The library is generally thread-safe, but each context must be used by at most one thread at one time. + * + * Also be careful with fork(). Unless said otherwise, it's forbidden to use the context + * in a forked child, we can even assert changes of pid. See strongrand_reset() if you + * need that. + **/ +struct strongrand { + const char *name; /* Context name for logging. */ + void (*read)(struct strongrand *sr, byte *ptr, int size); /* (mandatory) Generate @size random bytes. */ + void (*close)(struct strongrand *sr); /* Called from strongrand_close(). */ + void (*reset)(struct strongrand *sr); /* Called from strongrand_reset(). */ +}; + +/** Close a previously opened context. **/ +void strongrand_close(struct strongrand *c); + +/** + * If you want to use the same context in multiple forked subprocesses, you must + * call this function after the fork() to flush any buffered randomness. + * Some future back-ends could use it for even more things, for example + * to re-connect somewhere. + **/ +void strongrand_reset(struct strongrand *sr); + +/** Generate @size random bytes. **/ +void strongrand_mem(struct strongrand *c, void *ptr, size_t size); + +/** Kernel's random generator. **/ + +/** + * Open kernel's random generator. + * Requires exactly one of these flags, among others: + * -- STRONGRAND_STD_URANDOM + * -- STRONGRAND_STD_RANDOM + * + * @buf_size can be 0 for unbuffered reading. + * + * If at least one of the following conditions are met, + * strongrand_reset() after fork() is not necessary: + * -- zero @buf_size + * -- STRONGRAND_STD_AUTO_RESET flag + * -- no read since open (empty buffer) + **/ +struct strongrand *strongrand_std_open(uint buf_size, uint flags); + +enum strongrand_std_flags { + STRONGRAND_STD_URANDOM = 0x1, // Use kernel's "urandom" source. + STRONGRAND_STD_RANDOM = 0x2, // Use kernel's "random" source. + STRONGRAND_STD_DELAYED = 0x4, // Delay non-trivial initializations like opening file descriptor to first read. + STRONGRAND_STD_AUTO_RESET = 0x8, // Automatically do strongrand_reset() after changes of getpid(); adds overhead to each read. + STRONGRAND_STD_ASSERT_RESET = 0x10, // Assert correct usage of strongrand_reset() after changes of getpid(); for debugging. +}; // Internals bool strongrand_getrandom_mem_try(void *buf, size_t size);