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.
449 lines
14 KiB
449 lines
14 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/ctrdrbg.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, |
|
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 Abort() { |
|
Event e(Syscall::kAbort); |
|
return e; |
|
} |
|
|
|
std::string String() const { |
|
char buf[256]; |
|
|
|
switch (type) { |
|
case Syscall::kGetRandom: |
|
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::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; |
|
// getrandom gives |EINVAL| unless |NO_GETRANDOM| is set. |
|
static const unsigned GETRANDOM_ERROR = 8; |
|
// Reading from /dev/urandom gives |EINVAL|. |
|
static const unsigned URANDOM_ERROR = 16; |
|
static const unsigned NEXT_FLAG = 32; |
|
|
|
// 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; |
|
// 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; |
|
} |
|
} |
|
|
|
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; |
|
} |
|
} |
|
} |
|
|
|
// 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 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) { |
|
if (is_fips) { |
|
// FIPS builds require getrandom. |
|
ret.push_back(Event::Abort()); |
|
return ret; |
|
} |
|
|
|
ret.push_back(Event::Open("/dev/urandom")); |
|
if (flags & NO_URANDOM) { |
|
ret.push_back(Event::Abort()); |
|
return ret; |
|
} |
|
|
|
sysrand = [&ret, flags](bool block, size_t len) { |
|
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. |
|
!sysrand(true, kSeedLength + (is_fips ? 16 : 0)) || |
|
// 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; |
|
|
|
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(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
|
|
|