|
|
|
/* Copyright (c) 2019, Google Inc.
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
|
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
|
|
|
|
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include <openssl/rand.h>
|
|
|
|
|
|
|
|
#include "internal.h"
|
|
|
|
#include "getrandom_fillin.h"
|
|
|
|
|
|
|
|
#if defined(OPENSSL_X86_64) && !defined(BORINGSSL_SHARED_LIBRARY) && \
|
|
|
|
!defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE) && defined(USE_NR_getrandom)
|
|
|
|
|
|
|
|
#include <linux/random.h>
|
|
|
|
#include <sys/ptrace.h>
|
|
|
|
#include <sys/syscall.h>
|
|
|
|
#include <sys/user.h>
|
|
|
|
|
|
|
|
#include "fork_detect.h"
|
|
|
|
|
|
|
|
#if !defined(PTRACE_O_EXITKILL)
|
|
|
|
#define PTRACE_O_EXITKILL (1 << 20)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// This test can be run with $OPENSSL_ia32cap=~0x4000000000000000 in order to
|
|
|
|
// simulate the absence of RDRAND of machines that have it.
|
|
|
|
|
|
|
|
// Event represents a system call from urandom.c that is observed by the ptrace
|
|
|
|
// code in |GetTrace|.
|
|
|
|
struct Event {
|
|
|
|
enum class Syscall {
|
|
|
|
kGetRandom,
|
|
|
|
kOpen,
|
|
|
|
kUrandomRead,
|
|
|
|
kUrandomIoctl,
|
|
|
|
kAbort,
|
|
|
|
};
|
|
|
|
|
|
|
|
explicit Event(Syscall syscall) : type(syscall) {}
|
|
|
|
|
|
|
|
bool operator==(const Event &other) const {
|
|
|
|
return type == other.type && length == other.length &&
|
|
|
|
flags == other.flags &&
|
|
|
|
((filename == nullptr && other.filename == nullptr) ||
|
|
|
|
strcmp(filename, other.filename) == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Event GetRandom(size_t length, unsigned flags) {
|
|
|
|
Event e(Syscall::kGetRandom);
|
|
|
|
e.length = length;
|
|
|
|
e.flags = flags;
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Event Open(const char *filename) {
|
|
|
|
Event e(Syscall::kOpen);
|
|
|
|
e.filename = filename;
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Event UrandomRead(size_t length) {
|
|
|
|
Event e(Syscall::kUrandomRead);
|
|
|
|
e.length = length;
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Event UrandomIoctl() {
|
|
|
|
Event e(Syscall::kUrandomIoctl);
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Event Abort() {
|
|
|
|
Event e(Syscall::kAbort);
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string String() const {
|
|
|
|
char buf[256];
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case Syscall::kGetRandom:
|
Fix the easy -Wformat-signedness errors.
GCC has a warning that complains about even more type mismatches in
printf. Some of these are a bit messy and will be fixed in separate CLs.
This covers the easy ones.
The .*s stuff is unfortunate, but printf has no size_t-clean string
printer. ALPN protocol lengths are bound by uint8_t, so it doesn't
really matter.
The IPv6 printing one is obnoxious and arguably a false positive. It's
really a C language flaw: all types smaller than int get converted to
int when you do arithmetic. So something like this first doesn't
overflow the shift because it computes over int, but then the result
overall is stored as an int.
uint8_t a, b;
(a << 8) | b
On the one hand, this fixes a "missing" cast to uint16_t before the
shift. At the same time, the incorrect final type means passing it to
%x, which expects unsigned int. The compiler has forgotten this value
actually fits in uint16_t and flags a warning. Mitigate this by storing
in a uint16_t first.
The story doesn't quite end here. Arguments passed to variadic functions
go through integer promotion[0], so the argument is still passed to
snprintf as an int! But then va_arg allows for a signedness mismatch[1],
provided the value is representable in both types. The combination means
that %x, though actually paired with unsigned, also accept uint8_t and
uint16_t, because those are guaranteed to promote to an int that meets
[1]. GCC recognizes [1] applies here.
(There's also PRI16x, but that's a bit tedious to use and, in glibc, is
defined as plain "x" anyway.)
[0] https://en.cppreference.com/w/c/language/conversion#Default_argument_promotions
[1] https://en.cppreference.com/w/c/variadic/va_arg
Bug: 450
Change-Id: Ic1d41356755a18ab922956dd2e07b560470341f4
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/50765
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
3 years ago
|
|
|
snprintf(buf, sizeof(buf), "getrandom(_, %zu, %u)", length, flags);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Syscall::kOpen:
|
|
|
|
snprintf(buf, sizeof(buf), "open(%s, _)", filename);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Syscall::kUrandomRead:
|
|
|
|
snprintf(buf, sizeof(buf), "read(urandom_fd, _, %zu)", length);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Syscall::kUrandomIoctl:
|
|
|
|
return "ioctl(urandom_fd, RNDGETENTCNT, _)";
|
|
|
|
|
|
|
|
case Syscall::kAbort:
|
|
|
|
return "abort()";
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::string(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
const Syscall type;
|
|
|
|
size_t length = 0;
|
|
|
|
unsigned flags = 0;
|
|
|
|
const char *filename = nullptr;
|
|
|
|
};
|
|
|
|
|
|
|
|
static std::string ToString(const std::vector<Event> &trace) {
|
|
|
|
std::string ret;
|
|
|
|
for (const auto &event : trace) {
|
|
|
|
if (!ret.empty()) {
|
|
|
|
ret += ", ";
|
|
|
|
}
|
|
|
|
ret += event.String();
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The following are flags to tell |GetTrace| to inject faults, using ptrace,
|
|
|
|
// into the entropy-related system calls.
|
|
|
|
|
|
|
|
// getrandom gives |ENOSYS|.
|
|
|
|
static const unsigned NO_GETRANDOM = 1;
|
|
|
|
// opening /dev/urandom fails.
|
|
|
|
static const unsigned NO_URANDOM = 2;
|
|
|
|
// getrandom always returns |EAGAIN| if given |GRNG_NONBLOCK|.
|
|
|
|
static const unsigned GETRANDOM_NOT_READY = 4;
|
|
|
|
// The ioctl on urandom returns only 255 bits of entropy the first time that
|
|
|
|
// it's called.
|
|
|
|
static const unsigned URANDOM_NOT_READY = 8;
|
|
|
|
// getrandom gives |EINVAL| unless |NO_GETRANDOM| is set.
|
|
|
|
static const unsigned GETRANDOM_ERROR = 16;
|
|
|
|
// Reading from /dev/urandom gives |EINVAL|.
|
|
|
|
static const unsigned URANDOM_ERROR = 32;
|
|
|
|
static const unsigned NEXT_FLAG = 64;
|
|
|
|
|
|
|
|
// GetTrace runs |thunk| in a forked process and observes the resulting system
|
|
|
|
// calls using ptrace. It simulates a variety of failures based on the contents
|
|
|
|
// of |flags| and records the observed events by appending to |out_trace|.
|
|
|
|
static void GetTrace(std::vector<Event> *out_trace, unsigned flags,
|
|
|
|
std::function<void()> thunk) {
|
|
|
|
const int child_pid = fork();
|
|
|
|
ASSERT_NE(-1, child_pid);
|
|
|
|
|
|
|
|
if (child_pid == 0) {
|
|
|
|
// Child process
|
|
|
|
if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0) {
|
|
|
|
perror("PTRACE_TRACEME");
|
|
|
|
_exit(1);
|
|
|
|
}
|
|
|
|
raise(SIGSTOP);
|
|
|
|
thunk();
|
|
|
|
_exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parent process
|
|
|
|
int status;
|
|
|
|
ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
|
|
|
|
ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
|
|
|
|
|
|
|
|
// Set options so that:
|
|
|
|
// a) the child process is killed once this process dies.
|
|
|
|
// b) System calls result in a WSTOPSIG value of (SIGTRAP | 0x80) rather
|
|
|
|
// than just SIGTRAP. (This doesn't matter here, but it's recommended
|
|
|
|
// practice so that it's distinct from the signal itself.)
|
|
|
|
ASSERT_EQ(0, ptrace(PTRACE_SETOPTIONS, child_pid, nullptr,
|
|
|
|
PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD))
|
|
|
|
<< strerror(errno);
|
|
|
|
|
|
|
|
// urandom_fd tracks the file descriptor number for /dev/urandom in the child
|
|
|
|
// process, if it opens it.
|
|
|
|
int urandom_fd = -1;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
// Advance the child to the next system call.
|
|
|
|
ASSERT_EQ(0, ptrace(PTRACE_SYSCALL, child_pid, 0, 0));
|
|
|
|
ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
|
|
|
|
|
|
|
|
// The child may have aborted rather than made a system call.
|
|
|
|
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGABRT) {
|
|
|
|
out_trace->push_back(Event::Abort());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise the only valid ptrace event is a system call stop.
|
|
|
|
ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80));
|
|
|
|
|
|
|
|
struct user_regs_struct regs;
|
|
|
|
ASSERT_EQ(0, ptrace(PTRACE_GETREGS, child_pid, nullptr, ®s));
|
|
|
|
const auto syscall_number = regs.orig_rax;
|
|
|
|
|
|
|
|
bool is_opening_urandom = false;
|
|
|
|
bool is_urandom_ioctl = false;
|
|
|
|
uintptr_t ioctl_output_addr = 0;
|
|
|
|
// inject_error is zero to indicate that the system call should run
|
|
|
|
// normally. Otherwise it's, e.g. -EINVAL, to indicate that the system call
|
|
|
|
// should not run and that error should be injected on return.
|
|
|
|
int inject_error = 0;
|
|
|
|
|
|
|
|
switch (syscall_number) {
|
|
|
|
case __NR_getrandom:
|
|
|
|
if (flags & NO_GETRANDOM) {
|
|
|
|
inject_error = -ENOSYS;
|
|
|
|
} else if (flags & GETRANDOM_ERROR) {
|
|
|
|
inject_error = -EINVAL;
|
|
|
|
} else if (flags & GETRANDOM_NOT_READY) {
|
|
|
|
if (regs.rdx & GRND_NONBLOCK) {
|
|
|
|
inject_error = -EAGAIN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out_trace->push_back(
|
|
|
|
Event::GetRandom(/*length=*/regs.rsi, /*flags=*/regs.rdx));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case __NR_openat:
|
|
|
|
case __NR_open: {
|
|
|
|
// It's assumed that any arguments to open(2) are constants in read-only
|
|
|
|
// memory and thus the pointer in the child's context will also be a
|
|
|
|
// valid pointer in our address space.
|
|
|
|
const char *filename = reinterpret_cast<const char *>(
|
|
|
|
(syscall_number == __NR_openat) ? regs.rsi : regs.rdi);
|
|
|
|
out_trace->push_back(Event::Open(filename));
|
|
|
|
is_opening_urandom = strcmp(filename, "/dev/urandom") == 0;
|
|
|
|
if (is_opening_urandom && (flags & NO_URANDOM)) {
|
|
|
|
inject_error = -ENOENT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case __NR_read: {
|
|
|
|
const int read_fd = regs.rdi;
|
|
|
|
if (urandom_fd >= 0 && urandom_fd == read_fd) {
|
|
|
|
out_trace->push_back(Event::UrandomRead(/*length=*/regs.rdx));
|
|
|
|
if (flags & URANDOM_ERROR) {
|
|
|
|
inject_error = -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case __NR_ioctl: {
|
|
|
|
const int ioctl_fd = regs.rdi;
|
|
|
|
if (urandom_fd >= 0 && ioctl_fd == urandom_fd &&
|
|
|
|
regs.rsi == RNDGETENTCNT) {
|
|
|
|
out_trace->push_back(Event::UrandomIoctl());
|
|
|
|
is_urandom_ioctl = true;
|
|
|
|
ioctl_output_addr = regs.rdx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inject_error) {
|
|
|
|
// Replace the system call number with -1 to cause the kernel to ignore
|
|
|
|
// the call. The -ENOSYS will be replaced later with the value of
|
|
|
|
// |inject_error|.
|
|
|
|
regs.orig_rax = -1;
|
|
|
|
ASSERT_EQ(0, ptrace(PTRACE_SETREGS, child_pid, nullptr, ®s));
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_EQ(0, ptrace(PTRACE_SYSCALL, child_pid, 0, 0));
|
|
|
|
ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
|
|
|
|
// If the system call was exit/exit_group, the process may be terminated
|
|
|
|
// rather than have exited the system call.
|
|
|
|
if (WIFEXITED(status)) {
|
|
|
|
ASSERT_EQ(0, WEXITSTATUS(status));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise the next state must be a system call exit stop. This is
|
|
|
|
// indistinguishable from a system call entry, we just have to keep track
|
|
|
|
// and know that these events happen in pairs.
|
|
|
|
ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80));
|
|
|
|
|
|
|
|
if (inject_error) {
|
|
|
|
if (inject_error != -ENOSYS) {
|
|
|
|
ASSERT_EQ(0, ptrace(PTRACE_GETREGS, child_pid, nullptr, ®s));
|
|
|
|
regs.rax = inject_error;
|
|
|
|
ASSERT_EQ(0, ptrace(PTRACE_SETREGS, child_pid, nullptr, ®s));
|
|
|
|
}
|
|
|
|
} else if (is_opening_urandom) {
|
|
|
|
ASSERT_EQ(0, ptrace(PTRACE_GETREGS, child_pid, nullptr, ®s));
|
|
|
|
urandom_fd = regs.rax;
|
|
|
|
} else if (is_urandom_ioctl) {
|
|
|
|
// The result is the number of bits of entropy that the kernel currently
|
|
|
|
// believes that it has. urandom.c waits until 256 bits are ready.
|
|
|
|
int result = 256;
|
|
|
|
|
|
|
|
// If we are simulating urandom not being ready then we have the ioctl
|
|
|
|
// indicate one too few bits of entropy the first time it's queried.
|
|
|
|
if (flags & URANDOM_NOT_READY) {
|
|
|
|
result--;
|
|
|
|
flags &= ~URANDOM_NOT_READY;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ptrace always works with ill-defined "words", which appear to be 64-bit
|
|
|
|
// on x86-64. Since the ioctl result is a 32-bit int, do a
|
|
|
|
// read-modify-write to inject the answer.
|
|
|
|
const uintptr_t aligned_addr = ioctl_output_addr & ~7;
|
|
|
|
const uintptr_t offset = ioctl_output_addr - aligned_addr;
|
|
|
|
union {
|
|
|
|
uint64_t word;
|
|
|
|
uint8_t bytes[8];
|
|
|
|
} u;
|
|
|
|
u.word = ptrace(PTRACE_PEEKDATA, child_pid,
|
|
|
|
reinterpret_cast<void *>(aligned_addr), nullptr);
|
|
|
|
memcpy(&u.bytes[offset], &result, sizeof(result));
|
|
|
|
ASSERT_EQ(0, ptrace(PTRACE_POKEDATA, child_pid,
|
|
|
|
reinterpret_cast<void *>(aligned_addr),
|
|
|
|
reinterpret_cast<void *>(u.word)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestFunction is the function that |GetTrace| is asked to trace.
|
|
|
|
static void TestFunction() {
|
|
|
|
uint8_t byte;
|
|
|
|
RAND_bytes(&byte, sizeof(byte));
|
|
|
|
RAND_bytes(&byte, sizeof(byte));
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool have_fork_detection() {
|
|
|
|
return CRYPTO_get_fork_generation() != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestFunctionPRNGModel is a model of how the urandom.c code will behave when
|
|
|
|
// |TestFunction| is run. It should return the same trace of events that
|
|
|
|
// |GetTrace| will observe the real code making.
|
|
|
|
static std::vector<Event> TestFunctionPRNGModel(unsigned flags) {
|
|
|
|
#if defined(BORINGSSL_FIPS)
|
|
|
|
static const bool is_fips = true;
|
|
|
|
#else
|
|
|
|
static const bool is_fips = false;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
std::vector<Event> ret;
|
|
|
|
bool urandom_probed = false;
|
|
|
|
bool getrandom_ready = false;
|
|
|
|
|
|
|
|
// Probe for getrandom support
|
|
|
|
ret.push_back(Event::GetRandom(1, GRND_NONBLOCK));
|
|
|
|
std::function<void()> wait_for_entropy;
|
|
|
|
std::function<bool(bool, size_t)> sysrand;
|
|
|
|
|
|
|
|
if (flags & NO_GETRANDOM) {
|
|
|
|
ret.push_back(Event::Open("/dev/urandom"));
|
|
|
|
if (flags & NO_URANDOM) {
|
|
|
|
ret.push_back(Event::Abort());
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
wait_for_entropy = [&ret, &urandom_probed, flags] {
|
|
|
|
if (!is_fips || urandom_probed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Probe urandom for entropy.
|
|
|
|
ret.push_back(Event::UrandomIoctl());
|
|
|
|
if (flags & URANDOM_NOT_READY) {
|
|
|
|
// If the first attempt doesn't report enough entropy, probe
|
|
|
|
// repeatedly until it does, which will happen with the second attempt.
|
|
|
|
ret.push_back(Event::UrandomIoctl());
|
|
|
|
}
|
|
|
|
|
|
|
|
urandom_probed = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
sysrand = [&ret, &wait_for_entropy, flags](bool block, size_t len) {
|
|
|
|
if (block) {
|
|
|
|
wait_for_entropy();
|
|
|
|
}
|
|
|
|
ret.push_back(Event::UrandomRead(len));
|
|
|
|
if (flags & URANDOM_ERROR) {
|
|
|
|
ret.push_back(Event::Abort());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
if (flags & GETRANDOM_ERROR) {
|
|
|
|
ret.push_back(Event::Abort());
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
getrandom_ready = (flags & GETRANDOM_NOT_READY) == 0;
|
|
|
|
wait_for_entropy = [&ret, &getrandom_ready] {
|
|
|
|
if (getrandom_ready) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.push_back(Event::GetRandom(1, GRND_NONBLOCK));
|
|
|
|
ret.push_back(Event::GetRandom(1, 0));
|
|
|
|
getrandom_ready = true;
|
|
|
|
};
|
|
|
|
sysrand = [&ret, &wait_for_entropy](bool block, size_t len) {
|
|
|
|
if (block) {
|
|
|
|
wait_for_entropy();
|
|
|
|
}
|
|
|
|
ret.push_back(Event::GetRandom(len, block ? 0 : GRND_NONBLOCK));
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t kSeedLength = CTR_DRBG_ENTROPY_LEN * (is_fips ? 10 : 1);
|
|
|
|
const size_t kAdditionalDataLength = 32;
|
|
|
|
|
|
|
|
if (!have_rdrand()) {
|
|
|
|
if ((!have_fork_detection() && !sysrand(true, kAdditionalDataLength)) ||
|
|
|
|
// Initialise CRNGT.
|
|
|
|
(is_fips && !sysrand(true, 16)) ||
|
|
|
|
!sysrand(true, kSeedLength) ||
|
|
|
|
// Second entropy draw.
|
|
|
|
(!have_fork_detection() && !sysrand(true, kAdditionalDataLength))) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
// First additional data. If fast RDRAND isn't available then a
|
|
|
|
// non-blocking OS entropy draw will be tried.
|
|
|
|
(!have_fast_rdrand() && !have_fork_detection() &&
|
|
|
|
!sysrand(false, kAdditionalDataLength)) ||
|
|
|
|
// Opportuntistic entropy draw in FIPS mode because RDRAND was used.
|
|
|
|
// In non-FIPS mode it's just drawn from |CRYPTO_sysrand| in a blocking
|
|
|
|
// way.
|
|
|
|
!sysrand(!is_fips, CTR_DRBG_ENTROPY_LEN) ||
|
|
|
|
// Second entropy draw's additional data.
|
|
|
|
(!have_fast_rdrand() && !have_fork_detection() &&
|
|
|
|
!sysrand(false, kAdditionalDataLength))) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void CheckInvariants(const std::vector<Event> &events) {
|
|
|
|
// If RDRAND is available then there should be no blocking syscalls in FIPS
|
|
|
|
// mode.
|
|
|
|
#if defined(BORINGSSL_FIPS)
|
|
|
|
if (have_rdrand()) {
|
|
|
|
for (const auto &event : events) {
|
|
|
|
switch (event.type) {
|
|
|
|
case Event::Syscall::kGetRandom:
|
|
|
|
if ((event.flags & GRND_NONBLOCK) == 0) {
|
|
|
|
ADD_FAILURE() << "Blocking getrandom found with RDRAND: "
|
|
|
|
<< ToString(events);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Event::Syscall::kUrandomIoctl:
|
|
|
|
ADD_FAILURE() << "Urandom polling found with RDRAND: "
|
|
|
|
<< ToString(events);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests that |TestFunctionPRNGModel| is a correct model for the code in
|
|
|
|
// urandom.c, at least to the limits of the the |Event| type.
|
|
|
|
TEST(URandomTest, Test) {
|
|
|
|
char buf[256];
|
|
|
|
|
|
|
|
#define TRACE_FLAG(flag) \
|
|
|
|
snprintf(buf, sizeof(buf), #flag ": %d", (flags & flag) != 0); \
|
|
|
|
SCOPED_TRACE(buf);
|
|
|
|
|
|
|
|
for (unsigned flags = 0; flags < NEXT_FLAG; flags++) {
|
|
|
|
TRACE_FLAG(NO_GETRANDOM);
|
|
|
|
TRACE_FLAG(NO_URANDOM);
|
|
|
|
TRACE_FLAG(GETRANDOM_NOT_READY);
|
|
|
|
TRACE_FLAG(URANDOM_NOT_READY);
|
|
|
|
TRACE_FLAG(GETRANDOM_ERROR);
|
|
|
|
TRACE_FLAG(URANDOM_ERROR);
|
|
|
|
|
|
|
|
const std::vector<Event> expected_trace = TestFunctionPRNGModel(flags);
|
|
|
|
CheckInvariants(expected_trace);
|
|
|
|
std::vector<Event> actual_trace;
|
|
|
|
GetTrace(&actual_trace, flags, TestFunction);
|
|
|
|
|
|
|
|
if (expected_trace != actual_trace) {
|
|
|
|
ADD_FAILURE() << "Expected: " << ToString(expected_trace)
|
|
|
|
<< "\nFound: " << ToString(actual_trace);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
|
|
|
|
if (getenv("BORINGSSL_IGNORE_MADV_WIPEONFORK")) {
|
|
|
|
CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing();
|
|
|
|
}
|
|
|
|
|
|
|
|
return RUN_ALL_TESTS();
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
printf("PASS\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // X86_64 && !SHARED_LIBRARY && !UNSAFE_DETERMINISTIC_MODE &&
|
|
|
|
// USE_NR_getrandom
|