diff options
Diffstat (limited to '')
| -rw-r--r-- | librecrypt_rng_.c | 310 |
1 files changed, 264 insertions, 46 deletions
diff --git a/librecrypt_rng_.c b/librecrypt_rng_.c index d53b999..3f9b9eb 100644 --- a/librecrypt_rng_.c +++ b/librecrypt_rng_.c @@ -69,68 +69,76 @@ librecrypt_rng_(void *out, size_t n, void *user) #ifndef O_CLOEXEC # define O_CLOEXEC 0 #endif - fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); - if (fd >= 0) { - for (;;) { - r = read(fd, buf, n); - if (r < 0) { - if (errno != EINTR) { - close(fd); - break; - } - saved_errno = EINTR; - } else if (r > 0) { - ret += (size_t)r; - buf = &buf[(size_t)r]; - n -= (size_t)r; - if (!n) { - close(fd); - goto out; - } - } else { +#ifndef O_CLOFORK +# define O_CLOFORK 0 +#endif + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_CLOFORK); + if (fd < 0) + goto no_urandom; /* this is a goto to make coverage test more comprehensive */ + /* TODO we should make sure this is a character special device */ + for (;;) { + r = read(fd, buf, n); + if (r < 0) { + if (errno != EINTR) { close(fd); break; } + saved_errno = EINTR; + } else if (r > 0) { + ret += (size_t)r; + buf = &buf[(size_t)r]; + n -= (size_t)r; + if (!n) { + close(fd); + goto out; + } + } else { + /* /dev/urandom cannot be exhausted, your system + * has been compromised if this happens */ + /* TODO we should probably warn the user */ + abort(); } } - /* Last restort, use non-entropy based RNG */ +no_urandom: + /* Last resort, use non-entropy based RNG */ /* but try to use a good seed */ state = seed; - if (!state) { - /* Hopefully the application has already called srand(3) */ - state = (unsigned int)rand(); + if (state) + goto have_initial_seed; + /* Hopefully the application has already called srand(3) */ + state = (unsigned int)rand(); #if defined(__linux__) && defined(AT_RANDOM) - /* Otherwise can we use random data the kernel gave to the process */ - random_addr = (uintptr_t)getauxval(AT_RANDOM); - if (random_addr) { - at_random = (const void *)random_addr; - for (i = 0u; i < 16u; i++) - state ^= (unsigned int)at_random[i] << (i % sizeof(unsigned int) * 8u); - goto have_initial_seed; - } + /* Otherwise can we use random data the kernel gave to the process */ + random_addr = (uintptr_t)getauxval(AT_RANDOM); + if (random_addr) { + at_random = (const void *)random_addr; + for (i = 0u; i < 16u; i++) + state ^= (unsigned int)at_random[i] << (i % sizeof(unsigned int) * 8u); + goto have_initial_seed; + } #endif #ifndef MAP_UNINITIALIZED # define MAP_UNINITIALIZED 0 #endif - /* But where that is not supported, we can hopefully - * get a random address: here mmap(2) is much better than - * malloc(3), as malloc(3) may be less random when the - * allocation is small, and we don't want to make a big - * allocation. A few bit's are always 0, but that's not - * a big deal. */ - random_ptr = mmap(NULL, 1u, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED, -1, 0); - if (random_ptr != MAP_FAILED) { - random_addr = (uintptr_t)random_ptr; - state ^= (unsigned int)random_addr; - munmap(random_ptr, 1u); - goto have_initial_seed; /* just using goto to simplify the #if'ing */ - } - random_ptr = malloc(1u); /* NULL is OK (MAP_FAILED was actually also OK) */ + /* But where that is not supported, we can hopefully + * get a random address: here mmap(2) is much better than + * malloc(3), as malloc(3) may be less random when the + * allocation is small, and we don't want to make a big + * allocation. A few bit's are always 0, but that's not + * a big deal. */ + random_ptr = mmap(NULL, 1u, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED, -1, 0); + if (random_ptr != MAP_FAILED) { random_addr = (uintptr_t)random_ptr; state ^= (unsigned int)random_addr; - free(random_ptr); + munmap(random_ptr, 1u); + goto have_initial_seed; /* just using goto to simplify the #if'ing */ } + random_ptr = malloc(1u); /* NULL is OK (MAP_FAILED was actually also OK) */ + random_addr = (uintptr_t)random_ptr; + state ^= (unsigned int)random_addr; + free(random_ptr); + have_initial_seed: /* and always do a time-based reseed in case of multithreading, * so multiple passwords don't end up using the same salt */ @@ -160,6 +168,60 @@ out: #else +static size_t open_calls = 0u; +static int open_error = 0; + +int +(open)(const char *path, int flags, ...) +{ + mode_t mode = 0; + + open_calls += 1u; + + if (open_error) { + errno = open_error; + if (open_error == EINTR) + open_error = 0; + return -1; + } + + if ((flags & O_CREAT) || (flags & O_TMPFILE) == O_TMPFILE) { + va_list args; + va_start(args, flags); + mode = va_arg(args, mode_t); + va_end(args); + } + + return openat(AT_FDCWD, path, flags, mode); +} + + +static volatile size_t beyond_ssize_max = (size_t)SSIZE_MAX + 1u; + +static void +test_oversized(void) +{ + volatile int jumped = 0; + char *buf; + + buf = mmap(NULL, 1u, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(buf != MAP_FAILED); + assert(buf != NULL); + buf[0] = 99; + + libtest_getentropy_jmp_val = 1; + if (!setjmp(libtest_getentropy_jmp)) { + EXPECT(librecrypt_rng_(buf, beyond_ssize_max, NULL) == 9999); + } else { + jumped = 1; + } + EXPECT(jumped); + + EXPECT(buf[0] == 99); + assert(!munmap(buf, 1u)); +} + + int main(void) { @@ -173,6 +235,10 @@ main(void) SET_UP_ALARM(); INIT_RESOURCE_TEST(); + open_calls = 0u; + + /* TODO Test with output pattern (useful for other tests) */ + /* Check that output is random */ n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); n2 = librecrypt_rng_(buf2, sizeof(buf2), &user); @@ -180,8 +246,160 @@ main(void) EXPECT(n2 >= 128 && (size_t)n2 <= sizeof(buf2)); EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); +#if defined(__linux__) + libtest_getentropy_calls = 0u; + + /* Check with short getrandom(3) */ + errno = 0; + libtest_getrandom_max_return = 1u; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + libtest_getrandom_max_return = SIZE_MAX; + EXPECT(libtest_getentropy_calls == 0u); + EXPECT(errno == 0); + + /* Check with getrandom(3) with EINTR */ + errno = 0; + libtest_getrandom_error = EINTR; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + assert(!libtest_getrandom_error); + EXPECT(libtest_getentropy_calls == 0u); + EXPECT(errno == EINTR); + + /* Check with getrandom(3) with ENOSYS */ + errno = 0; + libtest_getrandom_error = ENOSYS; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + assert(libtest_getrandom_error == ENOSYS); + EXPECT(libtest_getentropy_calls > 0u); + libtest_getentropy_calls = 0u; + EXPECT(errno == 0); + + /* Check with getrandom(3) other error */ + errno = 0; + libtest_getrandom_error = EDOM; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + assert(libtest_getrandom_error == EDOM); + EXPECT(libtest_getentropy_calls > 0u); + libtest_getentropy_calls = 0u; + libtest_getrandom_error = 0; + EXPECT(errno == 0); + + /* Check with getrandom(3) zero return */ + errno = 0; + libtest_getrandom_max_return = 0u; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + libtest_getrandom_max_return = SIZE_MAX; + EXPECT(libtest_getentropy_calls > 0u); + libtest_getentropy_calls = 0u; + EXPECT(errno == 0); + + /* Don't use getrandom(3) for reminder of the test */ + libtest_getrandom_error = ENOSYS; +#endif + + /* Check with getentropy(3) with EINTR */ + errno = 0; + libtest_getentropy_error = EINTR; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + assert(!libtest_getentropy_error); + EXPECT(libtest_getentropy_calls > 0u); + libtest_getentropy_calls = 0u; + EXPECT(errno == EINTR); + + /* Check with getentropy(3) with ENOSYS */ + errno = 0; + libtest_getentropy_error = ENOSYS; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + assert(libtest_getentropy_error == ENOSYS); + EXPECT(libtest_getentropy_calls > 0u); + libtest_getentropy_calls = 0u; + EXPECT(errno == 0); + + /* Check with getentropy(3) other error */ + errno = 0; + libtest_getentropy_error = EDOM; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + assert(libtest_getentropy_error == EDOM); + EXPECT(libtest_getentropy_calls > 0u); + libtest_getentropy_calls = 0u; + libtest_getentropy_error = 0; + EXPECT(errno == 0); + + /* Check with getentropy(3) */ + assert(open_calls > 0u); + open_calls = 0u; + errno = 0; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + n2 = librecrypt_rng_(buf2, sizeof(buf2), &user); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(n2 >= 128 && (size_t)n2 <= sizeof(buf2)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + EXPECT(libtest_getentropy_calls > 0u); + libtest_getentropy_calls = 0u; + EXPECT(errno == 0); + libtest_getentropy_real = 1; + EXPECT(open_calls == 0u); + + /* TODO Check with getentropy(3) with small buffer */ + + /* Don't use getentropy(3) for reminder of the test */ + libtest_getentropy_error = ENOSYS; + + /* Check with urandom(4) */ + errno = 0; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + n2 = librecrypt_rng_(buf2, sizeof(buf2), &user); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(n2 >= 128 && (size_t)n2 <= sizeof(buf2)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + EXPECT(errno == 0); + /* TODO check that rand(3) was not called */ + + /* Check with urandom(4) not available */ + open_calls = 0; + open_error = ENOENT; + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + n2 = librecrypt_rng_(buf2, sizeof(buf2), &user); + EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); + EXPECT(n2 >= 128 && (size_t)n2 <= sizeof(buf2)); + EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + assert(open_error == ENOENT); + open_error = 0; + EXPECT(open_calls > 0u); + + /* TODO Check with urandom(4) read(3) EINTR */ + /* TODO Check with urandom(4) read(3) EIO */ + /* TODO Check with urandom(4) read(3) exhaustion */ + + /* Don't use urandom(4) for reminder of the test */ + open_error = ENOENT; + + /* TODO Check with rand(3) */ + /* TODO Check with mmap(3) failure */ + /* TODO Check with clock_gettime(3) failure */ + /* Check zero-request */ + errno = 0; EXPECT(librecrypt_rng_(NULL, 0u, NULL) == 0u); + EXPECT(errno == 0); + + test_oversized(); STOP_RESOURCE_TEST(); return 0; |
