diff options
author | Tobias Thierer <tobiast@google.com> | 2019-10-19 13:25:11 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-10-19 13:25:11 -0700 |
commit | aff937c24d3ee49d29b0804038f874d80a938979 (patch) | |
tree | 1b54a52ae8096564b711849bb27210da83c8eb6e | |
parent | 002011a51c152b350a2dd22e343f8e32771a4c8a (diff) | |
parent | 6204b54fdd49dfcf1f8e19161e00fb556ee78c4d (diff) | |
download | boringssl-aff937c24d3ee49d29b0804038f874d80a938979.tar.gz |
external/boringssl: Sync to da8caf5b1029b93d482702759058ac993a39bcc5.
am: 6204b54fdd
Change-Id: I7cfe610ced26c166e513c418d2a014598fb514b9
-rw-r--r-- | Android.bp | 23 | ||||
-rw-r--r-- | BORINGSSL_REVISION | 2 | ||||
-rw-r--r-- | src/crypto/CMakeLists.txt | 14 | ||||
-rw-r--r-- | src/crypto/cpu-intel.c | 10 | ||||
-rw-r--r-- | src/crypto/dsa/dsa.c | 4 | ||||
-rw-r--r-- | src/crypto/fipsmodule/bcm.c | 38 | ||||
-rw-r--r-- | src/crypto/fipsmodule/bn/prime.c | 90 | ||||
-rw-r--r-- | src/crypto/fipsmodule/fips_shared.lds | 6 | ||||
-rw-r--r-- | src/crypto/fipsmodule/rand/urandom_test.cc | 471 | ||||
-rw-r--r-- | src/ssl/tls13_client.cc | 12 | ||||
-rw-r--r-- | src/ssl/tls13_server.cc | 6 | ||||
-rw-r--r-- | src/third_party/sike/utils.h | 2 | ||||
-rw-r--r-- | src/util/fipstools/break-hash.go | 6 | ||||
-rw-r--r-- | src/util/fipstools/delocate/delocate.go | 29 | ||||
-rw-r--r-- | src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s | 3 | ||||
-rw-r--r-- | src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s | 15 | ||||
-rw-r--r-- | src/util/generate_build_files.py | 13 |
17 files changed, 639 insertions, 105 deletions
@@ -91,6 +91,7 @@ cc_object { ], sanitize: { address: false, + hwaddress: false, }, target: { android: { @@ -336,15 +337,15 @@ cc_test { // Utility binary for CMVP on-site testing. cc_binary { - name: "test_fips", - host_supported: false, - defaults: [ - "boringssl_flags", - ], - shared_libs: [ - "libcrypto", - ], - srcs: [ - "src/util/fipstools/cavp/test_fips.c", - ], + name: "test_fips", + host_supported: false, + defaults: [ + "boringssl_flags", + ], + shared_libs: [ + "libcrypto", + ], + srcs: [ + "src/util/fipstools/cavp/test_fips.c", + ], } diff --git a/BORINGSSL_REVISION b/BORINGSSL_REVISION index 6661f8e4..3b00600a 100644 --- a/BORINGSSL_REVISION +++ b/BORINGSSL_REVISION @@ -1 +1 @@ -a7a75f208caea8a303615724d4cc5f4e8dfb9695 +da8caf5b1029b93d482702759058ac993a39bcc5 diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index d84e5457..f3e2ca47 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -462,6 +462,20 @@ if(USE_CUSTOM_LIBCXX) target_link_libraries(crypto libcxx) endif() +# urandom_test is a separate binary because it needs to be able to observe the +# PRNG initialisation, which means that it can't have other tests running before +# it does. +add_executable( + urandom_test + + fipsmodule/rand/urandom_test.cc +) + +target_link_libraries(urandom_test test_support_lib boringssl_gtest crypto) + +add_dependencies(urandom_test global_target) +add_dependencies(all_tests urandom_test) + add_executable( crypto_test diff --git a/src/crypto/cpu-intel.c b/src/crypto/cpu-intel.c index 832e9d6d..cc41fc47 100644 --- a/src/crypto/cpu-intel.c +++ b/src/crypto/cpu-intel.c @@ -123,9 +123,17 @@ static uint64_t OPENSSL_xgetbv(uint32_t xcr) { // and |out[1]|. See the comment in |OPENSSL_cpuid_setup| about this. static void handle_cpu_env(uint32_t *out, const char *in) { const int invert = in[0] == '~'; + const int hex = in[invert] == '0' && in[invert+1] == 'x'; + + int sscanf_result; uint64_t v; + if (hex) { + sscanf_result = sscanf(in + invert + 2, "%" PRIx64, &v); + } else { + sscanf_result = sscanf(in + invert, "%" PRIu64, &v); + } - if (!sscanf(in + invert, "%" PRIu64, &v)) { + if (!sscanf_result) { return; } diff --git a/src/crypto/dsa/dsa.c b/src/crypto/dsa/dsa.c index cb419c81..cc98225d 100644 --- a/src/crypto/dsa/dsa.c +++ b/src/crypto/dsa/dsa.c @@ -256,7 +256,7 @@ int DSA_generate_parameters_ex(DSA *dsa, unsigned bits, const uint8_t *seed_in, // Find q. for (;;) { // step 1 - if (!BN_GENCB_call(cb, 0, m++)) { + if (!BN_GENCB_call(cb, BN_GENCB_GENERATED, m++)) { goto err; } @@ -319,7 +319,7 @@ int DSA_generate_parameters_ex(DSA *dsa, unsigned bits, const uint8_t *seed_in, n = (bits - 1) / 160; for (;;) { - if ((counter != 0) && !BN_GENCB_call(cb, 0, counter)) { + if ((counter != 0) && !BN_GENCB_call(cb, BN_GENCB_GENERATED, counter)) { goto err; } diff --git a/src/crypto/fipsmodule/bcm.c b/src/crypto/fipsmodule/bcm.c index f3f66f4b..91b91701 100644 --- a/src/crypto/fipsmodule/bcm.c +++ b/src/crypto/fipsmodule/bcm.c @@ -115,6 +115,26 @@ extern const uint8_t BORINGSSL_bcm_rodata_start[]; extern const uint8_t BORINGSSL_bcm_rodata_end[]; #endif +// assert_within is used to sanity check that certain symbols are within the +// bounds of the integrity check. It checks that start <= symbol < end and +// aborts otherwise. +static void assert_within(const void *start, const void *symbol, + const void *end) { + const uintptr_t start_val = (uintptr_t) start; + const uintptr_t symbol_val = (uintptr_t) symbol; + const uintptr_t end_val = (uintptr_t) end; + + if (start_val <= symbol_val && symbol_val < end_val) { + return; + } + + fprintf( + stderr, + "FIPS module doesn't span expected symbol. Expected %p <= %p < %p\n", + start, symbol, end); + BORINGSSL_FIPS_abort(); +} + #if defined(OPENSSL_ANDROID) && defined(OPENSSL_AARCH64) static void BORINGSSL_maybe_set_module_text_permissions(int permission) { // Android may be compiled in execute-only-memory mode, in which case the @@ -147,11 +167,29 @@ BORINGSSL_bcm_power_on_self_test(void) { // .text section, which triggers the global-buffer overflow detection. const uint8_t *const start = BORINGSSL_bcm_text_start; const uint8_t *const end = BORINGSSL_bcm_text_end; + + assert_within(start, AES_encrypt, end); + assert_within(start, RSA_sign, end); + assert_within(start, RAND_bytes, end); + assert_within(start, EC_GROUP_cmp, end); + assert_within(start, SHA256_Update, end); + assert_within(start, ECDSA_do_verify, end); + assert_within(start, EVP_AEAD_CTX_seal, end); + #if defined(BORINGSSL_SHARED_LIBRARY) const uint8_t *const rodata_start = BORINGSSL_bcm_rodata_start; const uint8_t *const rodata_end = BORINGSSL_bcm_rodata_end; +#else + // In the static build, read-only data is placed within the .text segment. + const uint8_t *const rodata_start = BORINGSSL_bcm_text_start; + const uint8_t *const rodata_end = BORINGSSL_bcm_text_end; #endif + assert_within(rodata_start, kPrimes, rodata_end); + assert_within(rodata_start, des_skb, rodata_end); + assert_within(rodata_start, kP256Params, rodata_end); + assert_within(rodata_start, kPKCS1SigPrefixes, rodata_end); + #if defined(OPENSSL_ANDROID) uint8_t result[SHA256_DIGEST_LENGTH]; const EVP_MD *const kHashFunction = EVP_sha256(); diff --git a/src/crypto/fipsmodule/bn/prime.c b/src/crypto/fipsmodule/bn/prime.c index 9df4f95c..c0c795b1 100644 --- a/src/crypto/fipsmodule/bn/prime.c +++ b/src/crypto/fipsmodule/bn/prime.c @@ -443,6 +443,11 @@ loop: goto err; } + // Interleave |ret| and |t|'s primality tests to avoid paying the full + // iteration count on |ret| only to quickly discover |t| is composite. + // + // TODO(davidben): This doesn't quite work because an iteration count of 1 + // still runs the blinding mechanism. for (i = 0; i < checks; i++) { j = BN_is_prime_fasttest_ex(ret, 1, ctx, 0, NULL); if (j == -1) { @@ -458,7 +463,7 @@ loop: goto loop; } - if (!BN_GENCB_call(cb, i, c1 - 1)) { + if (!BN_GENCB_call(cb, BN_GENCB_PRIME_TEST, i)) { goto err; } // We have a safe prime test pass @@ -669,7 +674,7 @@ int BN_primality_test(int *out_is_probably_prime, const BIGNUM *w, *out_is_probably_prime = BN_is_word(w, prime); return 1; } - if (!BN_GENCB_call(cb, 1, -1)) { + if (!BN_GENCB_call(cb, BN_GENCB_PRIME_TEST, -1)) { return 0; } } @@ -755,7 +760,7 @@ int BN_primality_test(int *out_is_probably_prime, const BIGNUM *w, } // Step 4.7 - if (!BN_GENCB_call(cb, 1, i)) { + if (!BN_GENCB_call(cb, BN_GENCB_PRIME_TEST, i - 1)) { goto err; } } @@ -910,7 +915,7 @@ int BN_enhanced_miller_rabin_primality_test( loop: // Step 4.15 - if (!BN_GENCB_call(cb, 1, i)) { + if (!BN_GENCB_call(cb, BN_GENCB_PRIME_TEST, i - 1)) { goto err; } } @@ -926,80 +931,11 @@ err: } static int probable_prime(BIGNUM *rnd, int bits) { - uint16_t mods[OPENSSL_ARRAY_SIZE(kPrimes)]; - const size_t num_primes = num_trial_division_primes(rnd); - BN_ULONG delta; - BN_ULONG maxdelta = BN_MASK2 - kPrimes[num_primes - 1]; - char is_single_word = bits <= BN_BITS2; - -again: - if (!BN_rand(rnd, bits, BN_RAND_TOP_TWO, BN_RAND_BOTTOM_ODD)) { - return 0; - } - - // we now have a random number 'rnd' to test. - for (size_t i = 1; i < num_primes; i++) { - mods[i] = bn_mod_u16_consttime(rnd, kPrimes[i]); - } - // If bits is so small that it fits into a single word then we - // additionally don't want to exceed that many bits. - if (is_single_word) { - BN_ULONG size_limit; - if (bits == BN_BITS2) { - // Avoid undefined behavior. - size_limit = ~((BN_ULONG)0) - BN_get_word(rnd); - } else { - size_limit = (((BN_ULONG)1) << bits) - BN_get_word(rnd) - 1; - } - if (size_limit < maxdelta) { - maxdelta = size_limit; - } - } - delta = 0; - -loop: - if (is_single_word) { - BN_ULONG rnd_word = BN_get_word(rnd); - - // In the case that the candidate prime is a single word then - // we check that: - // 1) It's greater than kPrimes[i] because we shouldn't reject - // 3 as being a prime number because it's a multiple of - // three. - // 2) That it's not a multiple of a known prime. We don't - // check that rnd-1 is also coprime to all the known - // primes because there aren't many small primes where - // that's true. - for (size_t i = 1; i < num_primes && kPrimes[i] < rnd_word; i++) { - if ((mods[i] + delta) % kPrimes[i] == 0) { - delta += 2; - if (delta > maxdelta) { - goto again; - } - goto loop; - } - } - } else { - for (size_t i = 1; i < num_primes; i++) { - // check that rnd is not a prime and also - // that gcd(rnd-1,primes) == 1 (except for 2) - if (((mods[i] + delta) % kPrimes[i]) <= 1) { - delta += 2; - if (delta > maxdelta) { - goto again; - } - goto loop; - } + do { + if (!BN_rand(rnd, bits, BN_RAND_TOP_TWO, BN_RAND_BOTTOM_ODD)) { + return 0; } - } - - if (!BN_add_word(rnd, delta)) { - return 0; - } - if (BN_num_bits(rnd) != (unsigned)bits) { - goto again; - } - + } while (bn_odd_number_is_obviously_composite(rnd)); return 1; } diff --git a/src/crypto/fipsmodule/fips_shared.lds b/src/crypto/fipsmodule/fips_shared.lds index 6d44abc4..c3db1013 100644 --- a/src/crypto/fipsmodule/fips_shared.lds +++ b/src/crypto/fipsmodule/fips_shared.lds @@ -15,5 +15,11 @@ SECTIONS *(.rela.dyn) *(.data) *(.rel.ro) + *(*.text.*) + *(*.data.*) + + /* This should be included to catch any unexpected rodata subsections, but + it crashes the linker! + *(*.rodata.*) */ } } diff --git a/src/crypto/fipsmodule/rand/urandom_test.cc b/src/crypto/fipsmodule/rand/urandom_test.cc new file mode 100644 index 00000000..117c315b --- /dev/null +++ b/src/crypto/fipsmodule/rand/urandom_test.cc @@ -0,0 +1,471 @@ +/* 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" + + +#if defined(OPENSSL_X86_64) && defined(OPENSSL_LINUX) && \ + !defined(BORINGSSL_SHARED_LIBRARY) + +#include <linux/random.h> +#include <sys/ptrace.h> +#include <sys/syscall.h> +#include <sys/user.h> + +#if defined(OPENSSL_NO_ASM) +static int have_rdrand() { return 0; } +#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)); +} + +// 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 (!sysrand(true, kAdditionalDataLength) || + // Initialise CRNGT. + (is_fips && !sysrand(true, 16)) || + !sysrand(true, kSeedLength) || + // Second entropy draw. + !sysrand(true, kAdditionalDataLength)) { + return ret; + } + } else { + // 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. + if (!sysrand(!is_fips, CTR_DRBG_ENTROPY_LEN)) { + return ret; + } + } + + return ret; +} + +// 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); + 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); + return RUN_ALL_TESTS(); +} + +#else + +int main(int argc, char **argv) { return 0; } + +#endif // X86_64 && LINUX && !SHARED_LIBRARY diff --git a/src/ssl/tls13_client.cc b/src/ssl/tls13_client.cc index 12f4738b..15c18c2e 100644 --- a/src/ssl/tls13_client.cc +++ b/src/ssl/tls13_client.cc @@ -894,10 +894,10 @@ bool tls13_process_new_session_ticket(SSL *ssl, const SSLMessage &msg) { } // Parse out the extensions. - bool have_early_data_info = false; - CBS early_data_info; + bool have_early_data = false; + CBS early_data; const SSL_EXTENSION_TYPE ext_types[] = { - {TLSEXT_TYPE_early_data, &have_early_data_info, &early_data_info}, + {TLSEXT_TYPE_early_data, &have_early_data, &early_data}, }; uint8_t alert = SSL_AD_DECODE_ERROR; @@ -908,9 +908,9 @@ bool tls13_process_new_session_ticket(SSL *ssl, const SSLMessage &msg) { return false; } - if (have_early_data_info) { - if (!CBS_get_u32(&early_data_info, &session->ticket_max_early_data) || - CBS_len(&early_data_info) != 0) { + if (have_early_data) { + if (!CBS_get_u32(&early_data, &session->ticket_max_early_data) || + CBS_len(&early_data) != 0) { ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); return false; diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc index f6a81d45..abacefc7 100644 --- a/src/ssl/tls13_server.cc +++ b/src/ssl/tls13_server.cc @@ -171,10 +171,10 @@ static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) { } if (ssl->enable_early_data) { - CBB early_data_info; + CBB early_data; if (!CBB_add_u16(&extensions, TLSEXT_TYPE_early_data) || - !CBB_add_u16_length_prefixed(&extensions, &early_data_info) || - !CBB_add_u32(&early_data_info, session->ticket_max_early_data) || + !CBB_add_u16_length_prefixed(&extensions, &early_data) || + !CBB_add_u32(&early_data, session->ticket_max_early_data) || !CBB_flush(&extensions)) { return false; } diff --git a/src/third_party/sike/utils.h b/src/third_party/sike/utils.h index cbc83293..bc806da0 100644 --- a/src/third_party/sike/utils.h +++ b/src/third_party/sike/utils.h @@ -9,7 +9,7 @@ #include <openssl/base.h> -#include "../crypto/internal.h" +#include "../../crypto/internal.h" #include "sike.h" // Conversion macro from number of bits to number of bytes diff --git a/src/util/fipstools/break-hash.go b/src/util/fipstools/break-hash.go index 53e1c5cb..8c817d8d 100644 --- a/src/util/fipstools/break-hash.go +++ b/src/util/fipstools/break-hash.go @@ -56,6 +56,10 @@ func do(outPath, inPath string) error { symbols, err := object.Symbols() if err != nil { + fmt.Fprintf(os.Stderr, "%s\nTrying dynamic symbols\n", err) + symbols, err = object.DynamicSymbols() + } + if err != nil { return errors.New("failed to parse symbols: " + err.Error()) } @@ -123,7 +127,7 @@ func do(outPath, inPath string) error { newHash := mac.Sum(nil) fmt.Printf("Found start of module at offset 0x%x (VMA 0x%x):\n", start-textSection.Addr, start) - fmt.Printf(hex.Dump(moduleText[:128])) + fmt.Println(hex.Dump(moduleText[:128])) fmt.Printf("\nHash of module was: %x\n", hashWas) fmt.Printf("Hash of corrupted module is: %x\n", newHash) diff --git a/src/util/fipstools/delocate/delocate.go b/src/util/fipstools/delocate/delocate.go index a43b4286..92b4c31d 100644 --- a/src/util/fipstools/delocate/delocate.go +++ b/src/util/fipstools/delocate/delocate.go @@ -803,6 +803,8 @@ const ( instrCombine // instrThreeArg merges two sources into a destination in some fashion. instrThreeArg + // instrCompare takes two arguments and writes outputs to the flags register. + instrCompare instrOther ) @@ -833,6 +835,11 @@ func classifyInstruction(instr string, args []*node32) instructionType { return instrCombine } + case "cmpq": + if len(args) == 2 { + return instrCompare + } + case "sarxq", "shlxq", "shrxq": if len(args) == 3 { return instrThreeArg @@ -855,6 +862,13 @@ func push(w stringWriter) wrapperFunc { } } +func compare(w stringWriter, instr, a, b string) wrapperFunc { + return func(k func()) { + k() + w.WriteString(fmt.Sprintf("\t%s %s, %s\n", instr, a, b)) + } +} + func (d *delocation) loadFromGOT(w stringWriter, destination, symbol, section string, redzoneCleared bool) wrapperFunc { d.gotExternalsNeeded[symbol+"@"+section] = struct{}{} @@ -1074,7 +1088,7 @@ Args: } classification := classifyInstruction(instructionName, argNodes) - if classification != instrThreeArg && i != 0 { + if classification != instrThreeArg && classification != instrCompare && i != 0 { return nil, errors.New("GOT access must be source operand") } @@ -1091,6 +1105,17 @@ Args: case instrMove: assertNodeType(argNodes[1], ruleRegisterOrConstant) targetReg = d.contents(argNodes[1]) + case instrCompare: + otherSource := d.contents(argNodes[i^1]) + saveRegWrapper, tempReg := saveRegister(d.output, []string{otherSource}) + redzoneCleared = true + wrappers = append(wrappers, saveRegWrapper) + if i == 0 { + wrappers = append(wrappers, compare(d.output, instructionName, tempReg, otherSource)) + } else { + wrappers = append(wrappers, compare(d.output, instructionName, otherSource, tempReg)) + } + targetReg = tempReg case instrTransformingMove: assertNodeType(argNodes[1], ruleRegisterOrConstant) targetReg = d.contents(argNodes[1]) @@ -1114,7 +1139,7 @@ Args: return nil, fmt.Errorf("three-argument instruction has %d arguments", n) } if i != 0 && i != 1 { - return nil, errors.New("GOT access must be from soure operand") + return nil, errors.New("GOT access must be from source operand") } targetReg = d.contents(argNodes[2]) diff --git a/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s b/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s index ccbc0bf3..398032f8 100644 --- a/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s +++ b/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s @@ -44,4 +44,7 @@ foo: vpbroadcastq stderr@GOTPCREL(%rip), %xmm0 vpbroadcastq foo@GOTPCREL(%rip), %xmm0 + cmpq foo@GOTPCREL(%rip), %rax + cmpq %rax, foo@GOTPCREL(%rip) + .comm foobar,64,32 diff --git a/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s b/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s index 273cfe94..e14a71e6 100644 --- a/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s +++ b/src/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s @@ -172,6 +172,21 @@ foo: leaq 128(%rsp), %rsp vpbroadcastq %xmm0, %xmm0 +# WAS cmpq foo@GOTPCREL(%rip), %rax + leaq -128(%rsp), %rsp + pushq %rbx + leaq .Lfoo_local_target(%rip), %rbx + cmpq %rbx, %rax + popq %rbx + leaq 128(%rsp), %rsp +# WAS cmpq %rax, foo@GOTPCREL(%rip) + leaq -128(%rsp), %rsp + pushq %rbx + leaq .Lfoo_local_target(%rip), %rbx + cmpq %rax, %rbx + popq %rbx + leaq 128(%rsp), %rsp + .comm foobar,64,32 .text .loc 1 2 0 diff --git a/src/util/generate_build_files.py b/src/util/generate_build_files.py index 57e5e1b1..8a5df75d 100644 --- a/src/util/generate_build_files.py +++ b/src/util/generate_build_files.py @@ -272,6 +272,8 @@ class Bazel(object): self.PrintVariableSection(out, 'ssl_test_sources', files['ssl_test']) self.PrintVariableSection(out, 'crypto_test_data', files['crypto_test_data']) + self.PrintVariableSection(out, 'urandom_test_sources', + files['urandom_test']) class Eureka(object): @@ -685,6 +687,12 @@ def main(platforms): 'src/crypto/test/file_test_gtest.cc', 'src/crypto/test/gtest_main.cc', ] + # urandom_test.cc is in a separate binary so that it can be test PRNG + # initialisation. + crypto_test_files = [ + file for file in crypto_test_files + if not file.endswith('/urandom_test.cc') + ] ssl_test_files = FindCFiles(os.path.join('src', 'ssl'), OnlyTests) ssl_test_files += [ @@ -692,6 +700,10 @@ def main(platforms): 'src/crypto/test/gtest_main.cc', ] + urandom_test_files = [ + 'src/crypto/fipsmodule/rand/urandom_test.cc', + ] + fuzz_c_files = FindCFiles(os.path.join('src', 'fuzz'), NoTests) ssl_h_files = ( @@ -729,6 +741,7 @@ def main(platforms): 'tool_headers': tool_h_files, 'test_support': test_support_c_files, 'test_support_headers': test_support_h_files, + 'urandom_test': sorted(urandom_test_files), } asm_outputs = sorted(WriteAsmFiles(ReadPerlAsmOperations()).iteritems()) |