| // Copyright 2019 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <stdlib.h> | |
| #include <gtest/gtest.h> | |
| #include <optional> | |
| #include <openssl/bytestring.h> | |
| #include <openssl/ctrdrbg.h> | |
| #include <openssl/rand.h> | |
| #include "../bcm_support.h" | |
| #include "../fipsmodule/rand/internal.h" | |
| #include "../internal.h" | |
| #include "internal.h" | |
| #if (defined(OPENSSL_X86_64) || defined(OPENSSL_AARCH64)) && \ | |
| !defined(BORINGSSL_SHARED_LIBRARY) && defined(OPENSSL_RAND_URANDOM) | |
| #include <elf.h> | |
| #include <linux/random.h> | |
| #include <sys/ptrace.h> | |
| #include <sys/socket.h> | |
| #include <sys/syscall.h> | |
| #include <sys/uio.h> | |
| #include <sys/un.h> | |
| #include <sys/user.h> | |
| BSSL_NAMESPACE_BEGIN | |
| namespace { | |
| #if !defined(PTRACE_O_EXITKILL) | |
| #define PTRACE_O_EXITKILL (1 << 20) | |
| #endif | |
| #if defined(OPENSSL_ANDROID) | |
| static const bool kIsAndroid = true; | |
| #else | |
| static const bool kIsAndroid = false; | |
| #endif | |
| #if defined(BORINGSSL_FIPS) | |
| static const bool kIsFIPS = true; | |
| #else | |
| static const bool kIsFIPS = false; | |
| #endif | |
| static const bool kUsesDaemon = kIsFIPS && kIsAndroid; | |
| // kDaemonWriteLength is the number of bytes that the entropy daemon writes. | |
| static const size_t kDaemonWriteLength = 496; | |
| // 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, | |
| kSocket, | |
| kConnect, | |
| kSocketRead, | |
| kSocketClose, | |
| kAbort, | |
| }; | |
| explicit Event(Syscall syscall) : type(syscall) {} | |
| bool operator==(const Event &other) const { | |
| return type == other.type && // | |
| length == other.length && // | |
| flags == other.flags && // | |
| filename == other.filename; | |
| } | |
| static Event GetRandom(size_t length, unsigned flags) { | |
| Event e(Syscall::kGetRandom); | |
| e.length = length; | |
| e.flags = flags; | |
| return e; | |
| } | |
| static Event Open(const std::string &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 Socket() { | |
| Event e(Syscall::kSocket); | |
| return e; | |
| } | |
| static Event Connect() { | |
| Event e(Syscall::kConnect); | |
| return e; | |
| } | |
| static Event SocketRead(size_t length) { | |
| Event e(Syscall::kSocketRead); | |
| e.length = length; | |
| return e; | |
| } | |
| static Event SocketClose() { | |
| Event e(Syscall::kSocketClose); | |
| 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.c_str()); | |
| break; | |
| case Syscall::kUrandomRead: | |
| snprintf(buf, sizeof(buf), "read(urandom_fd, _, %zu)", length); | |
| break; | |
| case Syscall::kSocket: | |
| return "socket(UNIX, STREAM, _)"; | |
| case Syscall::kConnect: | |
| return "connect(sock, _, _)"; | |
| case Syscall::kSocketRead: | |
| snprintf(buf, sizeof(buf), "read(sock_fd, _, %zu)", length); | |
| break; | |
| case Syscall::kSocketClose: | |
| return "close(sock)"; | |
| case Syscall::kAbort: | |
| return "abort()"; | |
| } | |
| return std::string(buf); | |
| } | |
| const Syscall type; | |
| size_t length = 0; | |
| unsigned flags = 0; | |
| std::string filename; | |
| }; | |
| 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 SOCKET_ERROR = 32; | |
| static const unsigned CONNECT_ERROR = 64; | |
| static const unsigned SOCKET_READ_ERROR = 128; | |
| static const unsigned SOCKET_READ_SHORT = 256; | |
| static const unsigned NEXT_FLAG = 512; | |
| // regs_read fetches the registers of `child_pid` and writes them to `out_regs`. | |
| // That structure will contain at least the following members: | |
| // syscall: the syscall number, if registers were read just before entering | |
| // one. | |
| // args[0..2]: syscall arguments, if registers were read just before | |
| // entering one. | |
| // ret: the syscall return value, if registers were read just after finishing | |
| // one. | |
| // | |
| // This call returns true on success and false otherwise. | |
| static bool regs_read(struct regs *out_regs, int child_pid); | |
| // regs_set_ret sets the return value of the system call that `child_pid` has | |
| // just finished, to `ret`. It returns true on success and false otherwise. | |
| static bool regs_set_ret(int child_pid, int ret); | |
| // regs_break_syscall causes the system call that `child_pid` is about to enter | |
| // to fail to run. | |
| static bool regs_break_syscall(int child_pid, const struct regs *orig_regs); | |
| struct regs { | |
| uintptr_t syscall; | |
| uintptr_t args[3]; | |
| uintptr_t ret; | |
| struct user_regs_struct regs; | |
| }; | |
| #if defined(OPENSSL_X86_64) | |
| static bool regs_read(struct regs *out_regs, int child_pid) { | |
| if (ptrace(PTRACE_GETREGS, child_pid, nullptr, &out_regs->regs) != 0) { | |
| return false; | |
| } | |
| out_regs->syscall = out_regs->regs.orig_rax; | |
| out_regs->ret = out_regs->regs.rax; | |
| out_regs->args[0] = out_regs->regs.rdi; | |
| out_regs->args[1] = out_regs->regs.rsi; | |
| out_regs->args[2] = out_regs->regs.rdx; | |
| return true; | |
| } | |
| static bool regs_set_ret(int child_pid, int ret) { | |
| struct regs regs; | |
| if (!regs_read(®s, child_pid)) { | |
| return false; | |
| } | |
| regs.regs.rax = ret; | |
| return ptrace(PTRACE_SETREGS, child_pid, nullptr, ®s.regs) == 0; | |
| } | |
| static bool regs_break_syscall(int child_pid, const struct regs *orig_regs) { | |
| // Replace the syscall number with -1 to cause the kernel to fail the call. | |
| struct user_regs_struct regs = orig_regs->regs; | |
| regs.orig_rax = -1; | |
| return ptrace(PTRACE_SETREGS, child_pid, nullptr, ®s) == 0; | |
| } | |
| #elif defined(OPENSSL_AARCH64) | |
| static bool regs_read(struct regs *out_regs, int child_pid) { | |
| struct iovec io; | |
| io.iov_base = &out_regs->regs; | |
| io.iov_len = sizeof(out_regs->regs); | |
| if (ptrace(PTRACE_GETREGSET, child_pid, NT_PRSTATUS, &io) != 0) { | |
| return false; | |
| } | |
| out_regs->syscall = out_regs->regs.regs[8]; | |
| out_regs->ret = out_regs->regs.regs[0]; | |
| out_regs->args[0] = out_regs->regs.regs[0]; | |
| out_regs->args[1] = out_regs->regs.regs[1]; | |
| out_regs->args[2] = out_regs->regs.regs[2]; | |
| return true; | |
| } | |
| static bool set_regset(int child_pid, int regset, const void *data, | |
| size_t len) { | |
| struct iovec io; | |
| io.iov_base = const_cast<void *>(data); | |
| io.iov_len = len; | |
| return ptrace(PTRACE_SETREGSET, child_pid, reinterpret_cast<void *>(regset), | |
| &io) == 0; | |
| } | |
| static bool regs_set_ret(int child_pid, int ret) { | |
| struct regs regs; | |
| if (!regs_read(®s, child_pid)) { | |
| return false; | |
| } | |
| regs.regs.regs[0] = ret; | |
| return set_regset(child_pid, NT_PRSTATUS, ®s.regs, sizeof(regs.regs)); | |
| } | |
| static bool regs_break_syscall(int child_pid, const struct regs *orig_regs) { | |
| // Replace the syscall number with -1 to cause the kernel to fail the call. | |
| int syscall = -1; | |
| return set_regset(child_pid, NT_ARM_SYSTEM_CALL, &syscall, sizeof(syscall)); | |
| } | |
| #endif | |
| // memcpy_to_remote copies `n` bytes from `in_src` in the local address space, | |
| // to `dest` in the address space of `child_pid`. | |
| static void memcpy_to_remote(int child_pid, uint64_t dest, const void *in_src, | |
| size_t n) { | |
| const uint8_t *src = reinterpret_cast<const uint8_t *>(in_src); | |
| // ptrace always works with ill-defined "words", which appear to be 64-bit | |
| // on 64-bit systems. | |
| #if !defined(OPENSSL_64_BIT) | |
| #error "This code probably doesn't work" | |
| #endif | |
| while (n) { | |
| const uintptr_t aligned_addr = dest & ~7; | |
| const uintptr_t offset = dest - aligned_addr; | |
| const size_t space = 8 - offset; | |
| size_t todo = n; | |
| if (todo > space) { | |
| todo = space; | |
| } | |
| uint64_t word; | |
| if (offset == 0 && todo == 8) { | |
| word = CRYPTO_load_u64_le(src); | |
| } else { | |
| uint8_t bytes[8]; | |
| CRYPTO_store_u64_le( | |
| bytes, ptrace(PTRACE_PEEKDATA, child_pid, | |
| reinterpret_cast<void *>(aligned_addr), nullptr)); | |
| memcpy(&bytes[offset], src, todo); | |
| word = CRYPTO_load_u64_le(bytes); | |
| } | |
| ASSERT_EQ(0, ptrace(PTRACE_POKEDATA, child_pid, | |
| reinterpret_cast<void *>(aligned_addr), | |
| reinterpret_cast<void *>(word))); | |
| src += todo; | |
| n -= todo; | |
| dest += todo; | |
| } | |
| } | |
| static uint8_t get_byte_from_remote(int child_pid, uint64_t ptr) { | |
| // ptrace always works with ill-defined "words", which appear to be 64-bit | |
| // on 64-bit systems. | |
| #if !defined(OPENSSL_64_BIT) | |
| #error "This code probably doesn't work" | |
| #endif | |
| const uintptr_t aligned_addr = ptr & ~7; | |
| const uintptr_t offset = ptr - aligned_addr; | |
| uint64_t word = ptrace(PTRACE_PEEKDATA, child_pid, | |
| reinterpret_cast<void *>(aligned_addr), 0); | |
| uint8_t bytes[8]; | |
| CRYPTO_store_u64_le(bytes, word); | |
| return bytes[offset]; | |
| } | |
| static std::string get_string_from_remote(int child_pid, uint64_t ptr) { | |
| std::string ret; | |
| for (;;) { | |
| const uint8_t byte = get_byte_from_remote(child_pid, ptr); | |
| if (byte == 0) { | |
| break; | |
| } | |
| ret.push_back((char)byte); | |
| ptr++; | |
| } | |
| return ret; | |
| } | |
| // 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) | |
| << "Child was not stopped with SIGSTOP: " << status; | |
| // 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; | |
| // sock_fd tracks the file descriptor number for the socket to the entropy | |
| // daemon, if one is opened. | |
| int sock_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)) | |
| << "Child was not stopped with a syscall stop: " << status; | |
| struct regs regs; | |
| ASSERT_TRUE(regs_read(®s, child_pid)); | |
| bool is_opening_urandom = false; | |
| bool is_socket_call = false; | |
| bool is_socket_read = false; | |
| uint64_t socket_read_bytes = 0; | |
| // force_result is unset 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 the given value should be injected on return. | |
| std::optional<int> force_result; | |
| switch (regs.syscall) { | |
| case __NR_getrandom: | |
| if (flags & NO_GETRANDOM) { | |
| force_result = -ENOSYS; | |
| } else if (flags & GETRANDOM_ERROR) { | |
| force_result = -EINVAL; | |
| } else if (flags & GETRANDOM_NOT_READY) { | |
| if (regs.args[2] & GRND_NONBLOCK) { | |
| force_result = -EAGAIN; | |
| } | |
| } | |
| out_trace->push_back( | |
| Event::GetRandom(/*length=*/regs.args[1], /*flags=*/regs.args[2])); | |
| break; | |
| case __NR_openat: | |
| #if defined(OPENSSL_X86_64) | |
| case __NR_open: | |
| #endif | |
| { | |
| uintptr_t filename_ptr = | |
| (regs.syscall == __NR_openat) ? regs.args[1] : regs.args[0]; | |
| const std::string filename = | |
| get_string_from_remote(child_pid, filename_ptr); | |
| if (filename.find("/dev/__properties__/") == 0) { | |
| // Android may try opening these files as part of SELinux support. | |
| // They are ignored here. | |
| } else { | |
| out_trace->push_back(Event::Open(filename)); | |
| } | |
| is_opening_urandom = (filename == "/dev/urandom"); | |
| if (is_opening_urandom && (flags & NO_URANDOM)) { | |
| force_result = -ENOENT; | |
| } | |
| break; | |
| } | |
| case __NR_read: { | |
| const int read_fd = regs.args[0]; | |
| if (urandom_fd >= 0 && urandom_fd == read_fd) { | |
| out_trace->push_back(Event::UrandomRead(/*length=*/regs.args[2])); | |
| if (flags & URANDOM_ERROR) { | |
| force_result = -EINVAL; | |
| } | |
| } else if (sock_fd >= 0 && sock_fd == read_fd) { | |
| uint64_t length = regs.args[2]; | |
| out_trace->push_back(Event::SocketRead(length)); | |
| if (flags & SOCKET_READ_ERROR) { | |
| force_result = -EINVAL; | |
| } else { | |
| is_socket_read = true; | |
| socket_read_bytes = length; | |
| if (flags & SOCKET_READ_SHORT) { | |
| ASSERT_GT(socket_read_bytes, 0u); | |
| socket_read_bytes--; | |
| flags &= ~SOCKET_READ_SHORT; | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| case __NR_close: { | |
| if (sock_fd >= 0 && static_cast<int>(regs.args[0]) == sock_fd) { | |
| out_trace->push_back(Event::SocketClose()); | |
| sock_fd = -1; | |
| } | |
| break; | |
| } | |
| case __NR_socket: { | |
| const int family = regs.args[0]; | |
| const int type = regs.args[1]; | |
| if (family == AF_UNIX && type == SOCK_STREAM) { | |
| out_trace->push_back(Event::Socket()); | |
| is_socket_call = true; | |
| if (flags & SOCKET_ERROR) { | |
| force_result = -EINVAL; | |
| } | |
| } | |
| break; | |
| } | |
| case __NR_connect: { | |
| const int connect_fd = regs.args[0]; | |
| if (sock_fd >= 0 && connect_fd == sock_fd) { | |
| out_trace->push_back(Event::Connect()); | |
| if (flags & CONNECT_ERROR) { | |
| force_result = -EINVAL; | |
| } else { | |
| // The test system might not have an entropy daemon running so | |
| // inject a success result. | |
| force_result = 0; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| if (force_result.has_value()) { | |
| ASSERT_TRUE(regs_break_syscall(child_pid, ®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 (force_result.has_value()) { | |
| ASSERT_TRUE(regs_set_ret(child_pid, force_result.value())); | |
| } else if (is_opening_urandom) { | |
| ASSERT_TRUE(regs_read(®s, child_pid)); | |
| urandom_fd = regs.ret; | |
| } else if (is_socket_call) { | |
| ASSERT_TRUE(regs_read(®s, child_pid)); | |
| sock_fd = regs.ret; | |
| } else if (is_socket_read) { | |
| // Simulate a response from the entropy daemon since it might not be | |
| // running on the current system. | |
| uint8_t entropy[kDaemonWriteLength]; | |
| ASSERT_LE(socket_read_bytes, sizeof(entropy)); | |
| for (size_t i = 0; i < sizeof(entropy); i++) { | |
| entropy[i] = i & 0xff; | |
| } | |
| memcpy_to_remote(child_pid, regs.args[1], entropy, socket_read_bytes); | |
| ASSERT_TRUE(regs_set_ret(child_pid, socket_read_bytes)); | |
| } | |
| } | |
| } | |
| // 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 bssl::CRYPTO_get_fork_generation() != 0; | |
| } | |
| static bool AppendDaemonEvents(std::vector<Event> *events, unsigned flags) { | |
| events->push_back(Event::Socket()); | |
| if (flags & SOCKET_ERROR) { | |
| return false; | |
| } | |
| bool ret = false; | |
| events->push_back(Event::Connect()); | |
| if (flags & CONNECT_ERROR) { | |
| goto out; | |
| } | |
| events->push_back(Event::SocketRead(kDaemonWriteLength)); | |
| if (flags & SOCKET_READ_ERROR) { | |
| goto out; | |
| } | |
| if (flags & SOCKET_READ_SHORT) { | |
| events->push_back(Event::SocketRead(1)); | |
| } | |
| ret = true; | |
| out: | |
| events->push_back(Event::SocketClose()); | |
| return ret; | |
| } | |
| // 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) { | |
| std::vector<Event> ret; | |
| bool daemon_failed = false; | |
| if (have_fork_detection() && kUsesDaemon) { | |
| daemon_failed = !AppendDaemonEvents(&ret, flags); | |
| } | |
| // Probe for getrandom support | |
| ret.push_back(Event::GetRandom(1, GRND_NONBLOCK)); | |
| std::function<bool(size_t)> sysrand; | |
| if (flags & NO_GETRANDOM) { | |
| if (kIsFIPS) { | |
| // 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](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; | |
| } | |
| sysrand = [&ret](size_t len) { | |
| ret.push_back(Event::GetRandom(len, 0)); | |
| return true; | |
| }; | |
| } | |
| if (daemon_failed && !sysrand(48)) { | |
| return ret; | |
| } | |
| const size_t kAdditionalDataLength = 32; | |
| if (!have_rdrand()) { | |
| if (!have_fork_detection()) { | |
| if (!sysrand(kAdditionalDataLength)) { | |
| return ret; | |
| } | |
| if (kUsesDaemon && !AppendDaemonEvents(&ret, flags) && !sysrand(48)) { | |
| return ret; | |
| } | |
| } | |
| if ( // Initialise thread-local DRBG. In FIPS mode, the jitter source | |
| // will be used, but we cannot observe that. Either way, | |
| // CTR_DRBG_SEED_LEN will be drawn from sysrand, either as the | |
| // additional data in FIPS mode, or as the seed otherwise. | |
| !sysrand(CTR_DRBG_SEED_LEN) || | |
| // Second additional data, when other fork-safety measures have failed. | |
| (!have_fork_detection() && !sysrand(kAdditionalDataLength))) { | |
| return ret; | |
| } | |
| } else if ( | |
| // First additional data, when other fork-safety measures have failed. If | |
| // fast RDRAND isn't available then we use OS entropy. | |
| (!have_fast_rdrand() && !have_fork_detection() && | |
| !sysrand(kAdditionalDataLength)) || | |
| // OS entropy for the seed, or personalisation data if RDRAND was used. | |
| !sysrand(CTR_DRBG_SEED_LEN) || | |
| // Second additional data, when other fork-safety measures have failed. | |
| (!have_fast_rdrand() && !have_fork_detection() && | |
| !sysrand(kAdditionalDataLength))) { | |
| return ret; | |
| } | |
| return ret; | |
| } | |
| // Tests that `TestFunctionPRNGModel` is a correct model for the code in | |
| // urandom.c, at least to the limits of the `Event` type. | |
| TEST(URandomTest, Test) { | |
| char buf[256]; | |
| // Some Android systems lack getrandom. | |
| uint8_t scratch[1]; | |
| const bool has_getrandom = | |
| (syscall(__NR_getrandom, scratch, sizeof(scratch), GRND_NONBLOCK) != -1 || | |
| errno != ENOSYS); | |
| #define TRACE_FLAG(flag) \ | |
| snprintf(buf, sizeof(buf), #flag ": %d", (flags & flag) != 0); \ | |
| SCOPED_TRACE(buf); | |
| for (unsigned flags = 0; flags < NEXT_FLAG; flags++) { | |
| if (!kUsesDaemon && (flags & (SOCKET_ERROR | CONNECT_ERROR | | |
| SOCKET_READ_ERROR | SOCKET_READ_SHORT))) { | |
| // These cases are meaningless unless the code will try to use the entropy | |
| // daemon. | |
| continue; | |
| } | |
| if (!has_getrandom && !(flags & NO_GETRANDOM)) { | |
| continue; | |
| } | |
| TRACE_FLAG(NO_GETRANDOM); | |
| TRACE_FLAG(NO_URANDOM); | |
| TRACE_FLAG(GETRANDOM_NOT_READY); | |
| TRACE_FLAG(GETRANDOM_ERROR); | |
| TRACE_FLAG(URANDOM_ERROR); | |
| TRACE_FLAG(SOCKET_ERROR); | |
| TRACE_FLAG(CONNECT_ERROR); | |
| TRACE_FLAG(SOCKET_READ_ERROR); | |
| TRACE_FLAG(SOCKET_READ_SHORT); | |
| const std::vector<Event> expected_trace = TestFunctionPRNGModel(flags); | |
| 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); | |
| } | |
| } | |
| } | |
| } // namespace | |
| BSSL_NAMESPACE_END | |
| int main(int argc, char **argv) { | |
| ::testing::InitGoogleTest(&argc, argv); | |
| if (getenv("BORINGSSL_IGNORE_MADV_WIPEONFORK")) { | |
| bssl::CRYPTO_fork_detect_force_madv_wipeonfork_for_testing(0); | |
| } else { | |
| bssl::CRYPTO_fork_detect_force_madv_wipeonfork_for_testing(1); | |
| } | |
| return RUN_ALL_TESTS(); | |
| } | |
| #else | |
| int main(int argc, char **argv) { | |
| printf("PASS\n"); | |
| return 0; | |
| } | |
| #endif // (X86_64 || AARCH64) && !SHARED_LIBRARY && !UNSAFE_DETERMINISTIC_MODE |