Mirror of BoringSSL (grpc依赖) https://boringssl.googlesource.com/boringssl
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

521 lines
16 KiB

/* 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, &regs));
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, &regs));
}
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, &regs));
regs.rax = inject_error;
ASSERT_EQ(0, ptrace(PTRACE_SETREGS, child_pid, nullptr, &regs));
}
} else if (is_opening_urandom) {
ASSERT_EQ(0, ptrace(PTRACE_GETREGS, child_pid, nullptr, &regs));
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