ref: bfe9da47cfa2f51ebef78c59332b5ba2deba306d
dir: /third_party/boringssl/src/crypto/fipsmodule/rand/urandom_test.cc/
/* 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: snprintf(buf, sizeof(buf), "getrandom(_, %zu, %d)", 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