diff options
Diffstat (limited to 'src/crypto/fipsmodule/rand/urandom_test.cc')
-rw-r--r-- | src/crypto/fipsmodule/rand/urandom_test.cc | 469 |
1 files changed, 401 insertions, 68 deletions
diff --git a/src/crypto/fipsmodule/rand/urandom_test.cc b/src/crypto/fipsmodule/rand/urandom_test.cc index 0a1d7539..37002f9f 100644 --- a/src/crypto/fipsmodule/rand/urandom_test.cc +++ b/src/crypto/fipsmodule/rand/urandom_test.cc @@ -15,17 +15,22 @@ #include <gtest/gtest.h> #include <stdlib.h> +#include <openssl/ctrdrbg.h> #include <openssl/rand.h> -#include "internal.h" #include "getrandom_fillin.h" +#include "internal.h" -#if defined(OPENSSL_X86_64) && !defined(BORINGSSL_SHARED_LIBRARY) && \ +#if (defined(OPENSSL_X86_64) || defined(OPENSSL_AARCH64)) && \ + !defined(BORINGSSL_SHARED_LIBRARY) && \ !defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE) && defined(USE_NR_getrandom) #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> #include "fork_detect.h" @@ -34,6 +39,21 @@ #define PTRACE_O_EXITKILL (1 << 20) #endif +#if defined(BORINGSSL_FIPS) +static const bool kIsFIPS = true; +#if defined(OPENSSL_ANDROID) +static const bool kUsesDaemon = true; +#else +static const bool kUsesDaemon = false; +#endif +#else +static const bool kIsFIPS = false; +static const bool kUsesDaemon = false; +#endif + +// 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. @@ -45,6 +65,10 @@ struct Event { kOpen, kUrandomRead, kUrandomIoctl, + kSocket, + kConnect, + kSocketRead, + kSocketClose, kAbort, }; @@ -81,6 +105,27 @@ struct Event { 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; @@ -105,6 +150,19 @@ struct Event { case Syscall::kUrandomIoctl: return "ioctl(urandom_fd, RNDGETENTCNT, _)"; + 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()"; } @@ -145,7 +203,193 @@ static const unsigned URANDOM_NOT_READY = 8; static const unsigned GETRANDOM_ERROR = 16; // Reading from /dev/urandom gives |EINVAL|. static const unsigned URANDOM_ERROR = 32; -static const unsigned NEXT_FLAG = 64; +static const unsigned SOCKET_ERROR = 64; +static const unsigned CONNECT_ERROR = 128; +static const unsigned SOCKET_READ_ERROR = 256; +static const unsigned SOCKET_READ_SHORT = 512; +static const unsigned NEXT_FLAG = 1024; + +// 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); + +#if defined(OPENSSL_X86_64) + +struct regs { + uintptr_t syscall; + uintptr_t args[3]; + uintptr_t ret; + struct user_regs_struct regs; +}; + +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) { + // Replacing the syscall number with -1 doesn't work on AArch64 thus we set + // the first argument to -1, which suffices to break the syscalls that we care + // about here. + struct user_regs_struct regs; + memcpy(®s, &orig_regs->regs, sizeof(regs)); + regs.rdi = -1; + return ptrace(PTRACE_SETREGS, child_pid, nullptr, ®s) == 0; +} + +#elif defined(OPENSSL_AARCH64) + +struct regs { + uintptr_t syscall; + uintptr_t args[3]; + uintptr_t ret; + uint64_t regs[9]; +}; + +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, (void *)/*NT_PRSTATUS*/ 1, &io) != + 0) { + return false; + } + + out_regs->syscall = out_regs->regs[8]; + out_regs->ret = out_regs->regs[0]; + out_regs->args[0] = out_regs->regs[0]; + out_regs->args[1] = out_regs->regs[1]; + out_regs->args[2] = out_regs->regs[2]; + + return true; +} + +static bool regs_set(int child_pid, const struct regs *orig_regs, + uint64_t x0_value) { + uint64_t regs[OPENSSL_ARRAY_SIZE(orig_regs->regs)]; + memcpy(regs, orig_regs->regs, sizeof(regs)); + regs[0] = x0_value; + + struct iovec io; + io.iov_base = regs; + io.iov_len = sizeof(regs); + return ptrace(PTRACE_SETREGSET, child_pid, (void *)/*NT_PRSTATUS*/ 1, &io) == + 0; +} + +static bool regs_set_ret(int child_pid, int ret) { + struct regs regs; + return regs_read(®s, child_pid) && regs_set(child_pid, ®s, ret); +} + +static bool regs_break_syscall(int child_pid, const struct regs *orig_regs) { + // Replacing the syscall number with -1 doesn't work on AArch64 thus we set + // the first argument to -1, which suffices to break the syscalls that we care + // about here. + return regs_set(child_pid, orig_regs, -1); +} + +#endif + +// SyscallResult is like std::optional<int>. +// TODO: use std::optional when we can use C++17. +class SyscallResult { + public: + SyscallResult &operator=(int value) { + has_value_ = true; + value_ = value; + return *this; + } + + int value() const { + if (!has_value_) { + abort(); + } + return value_; + } + + bool has_value() const { return has_value_; } + + private: + bool has_value_ = false; + int value_ = 0; +}; + +// 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; + } +} // 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 @@ -184,6 +428,10 @@ static void GetTrace(std::vector<Event> *out_trace, unsigned flags, // 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)); @@ -198,76 +446,130 @@ static void GetTrace(std::vector<Event> *out_trace, unsigned flags, // 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; + struct regs regs; + ASSERT_TRUE(regs_read(®s, child_pid)); bool is_opening_urandom = false; + bool is_socket_call = false; bool is_urandom_ioctl = false; uintptr_t ioctl_output_addr = 0; - // inject_error is zero to indicate that the system call should run + 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 error should be injected on return. - int inject_error = 0; + // should not run and that the given value should be injected on return. + SyscallResult force_result; - switch (syscall_number) { + switch (regs.syscall) { case __NR_getrandom: if (flags & NO_GETRANDOM) { - inject_error = -ENOSYS; + force_result = -ENOSYS; } else if (flags & GETRANDOM_ERROR) { - inject_error = -EINVAL; + force_result = -EINVAL; } else if (flags & GETRANDOM_NOT_READY) { - if (regs.rdx & GRND_NONBLOCK) { - inject_error = -EAGAIN; + if (regs.args[2] & GRND_NONBLOCK) { + force_result = -EAGAIN; } } out_trace->push_back( - Event::GetRandom(/*length=*/regs.rsi, /*flags=*/regs.rdx)); + Event::GetRandom(/*length=*/regs.args[1], /*flags=*/regs.args[2])); break; case __NR_openat: - case __NR_open: { +#if defined(OPENSSL_X86_64) + case __NR_open: +#endif + { // 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); + (regs.syscall == __NR_openat) ? regs.args[1] : regs.args[0]); 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; + force_result = -ENOENT; } break; } case __NR_read: { - const int read_fd = regs.rdi; + const int read_fd = regs.args[0]; if (urandom_fd >= 0 && urandom_fd == read_fd) { - out_trace->push_back(Event::UrandomRead(/*length=*/regs.rdx)); + out_trace->push_back(Event::UrandomRead(/*length=*/regs.args[2])); if (flags & URANDOM_ERROR) { - inject_error = -EINVAL; + 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_ioctl: { - const int ioctl_fd = regs.rdi; + const int ioctl_fd = regs.args[0]; if (urandom_fd >= 0 && ioctl_fd == urandom_fd && - regs.rsi == RNDGETENTCNT) { + regs.args[1] == RNDGETENTCNT) { out_trace->push_back(Event::UrandomIoctl()); is_urandom_ioctl = true; - ioctl_output_addr = regs.rdx; + ioctl_output_addr = regs.args[2]; + } + 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 (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)); + if (force_result.has_value()) { + ASSERT_TRUE(regs_break_syscall(child_pid, ®s)); } ASSERT_EQ(0, ptrace(PTRACE_SYSCALL, child_pid, 0, 0)); @@ -284,15 +586,14 @@ static void GetTrace(std::vector<Event> *out_trace, unsigned flags, // 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)); - } + if (force_result.has_value()) { + ASSERT_TRUE(regs_set_ret(child_pid, force_result.value())); } else if (is_opening_urandom) { - ASSERT_EQ(0, ptrace(PTRACE_GETREGS, child_pid, nullptr, ®s)); - urandom_fd = regs.rax; + 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_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. @@ -305,21 +606,19 @@ static void GetTrace(std::vector<Event> *out_trace, unsigned flags, 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))); + memcpy_to_remote(child_pid, ioctl_output_addr, &result, sizeof(result)); + } 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)); } } } @@ -331,24 +630,46 @@ static void TestFunction() { RAND_bytes(&byte, sizeof(byte)); } -static bool have_fork_detection() { - return CRYPTO_get_fork_generation() != 0; +static bool have_fork_detection() { return 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) { -#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; + const bool used_daemon = kUsesDaemon && AppendDaemonEvents(&ret, flags); + // Probe for getrandom support ret.push_back(Event::GetRandom(1, GRND_NONBLOCK)); std::function<void()> wait_for_entropy; @@ -362,7 +683,7 @@ static std::vector<Event> TestFunctionPRNGModel(unsigned flags) { } wait_for_entropy = [&ret, &urandom_probed, flags] { - if (!is_fips || urandom_probed) { + if (!kIsFIPS || urandom_probed) { return; } @@ -413,14 +734,15 @@ static std::vector<Event> TestFunctionPRNGModel(unsigned flags) { }; } - const size_t kSeedLength = CTR_DRBG_ENTROPY_LEN * (is_fips ? 10 : 1); + const size_t kSeedLength = CTR_DRBG_ENTROPY_LEN * (kIsFIPS ? 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) || + (!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; @@ -433,7 +755,7 @@ static std::vector<Event> TestFunctionPRNGModel(unsigned flags) { // 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) || + !sysrand(!kIsFIPS, CTR_DRBG_ENTROPY_LEN) || // Second entropy draw's additional data. (!have_fast_rdrand() && !have_fork_detection() && !sysrand(false, kAdditionalDataLength))) { @@ -480,12 +802,23 @@ TEST(URandomTest, Test) { 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; + } + 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); + 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); CheckInvariants(expected_trace); @@ -516,5 +849,5 @@ int main(int argc, char **argv) { return 0; } -#endif // X86_64 && !SHARED_LIBRARY && !UNSAFE_DETERMINISTIC_MODE && - // USE_NR_getrandom +#endif // (X86_64 || AARCH64) && !SHARED_LIBRARY && + // !UNSAFE_DETERMINISTIC_MODE && USE_NR_getrandom |