// 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 #include #include #include #include #include #include "../internal.h" #include "getrandom_fillin.h" #if (defined(OPENSSL_X86_64) || defined(OPENSSL_AARCH64)) && \ !defined(BORINGSSL_SHARED_LIBRARY) && defined(OPENSSL_RAND_URANDOM) && \ defined(USE_NR_getrandom) #include #include #include #include #include #include #include #include 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 &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(data); io.iov_len = len; return ptrace(PTRACE_SETREGSET, child_pid, reinterpret_cast(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(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(aligned_addr), nullptr)); memcpy(&bytes[offset], src, todo); word = CRYPTO_load_u64_le(bytes); } ASSERT_EQ(0, ptrace(PTRACE_POKEDATA, child_pid, reinterpret_cast(aligned_addr), reinterpret_cast(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(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 *out_trace, unsigned flags, std::function 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 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(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 CRYPTO_get_fork_generation() != 0; } static bool AppendDaemonEvents(std::vector *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 TestFunctionPRNGModel(unsigned flags) { std::vector ret; bool getrandom_ready = false; bool used_daemon = false; if (have_fork_detection()) { used_daemon = kUsesDaemon && AppendDaemonEvents(&ret, flags); } // Probe for getrandom support ret.push_back(Event::GetRandom(1, GRND_NONBLOCK)); std::function wait_for_entropy; std::function 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](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 * (kIsFIPS ? 10 : 1); const size_t kAdditionalDataLength = 32; if (!have_rdrand()) { if (!have_fork_detection()) { if (!sysrand(true, kAdditionalDataLength)) { return ret; } used_daemon = kUsesDaemon && AppendDaemonEvents(&ret, flags); } if ( // Initialise CRNGT. (!used_daemon && !sysrand(true, kSeedLength + (kIsFIPS ? 16 : 0))) || // Personalisation draw if the daemon was used. (used_daemon && !sysrand(false, CTR_DRBG_ENTROPY_LEN)) || // 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(!kIsFIPS, 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 &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]; // 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 expected_trace = TestFunctionPRNGModel(flags); CheckInvariants(expected_trace); std::vector actual_trace; GetTrace(&actual_trace, flags, TestFunction); if (expected_trace != actual_trace) { ADD_FAILURE() << "Expected: " << ToString(expected_trace) << "\nFound: " << ToString(actual_trace); } } } } // namespace int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); if (getenv("BORINGSSL_IGNORE_MADV_WIPEONFORK")) { CRYPTO_fork_detect_force_madv_wipeonfork_for_testing(0); } else { 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 && USE_NR_getrandom