Fix OPENSSL_ia32cap parsing. This environment variable can be used to tweak the CPUID values and is reportedly used by `rr` to disable RDRAND support. This broke in 85145fdc91, however, when two values were given. Change-Id: I5ac8ce863f18242bfd9f37872c628e7e487052d5 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/79407 Reviewed-by: David Benjamin <davidben@google.com> Auto-Submit: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/cpu_intel.cc b/crypto/cpu_intel.cc index 4116012..dc83c9b 100644 --- a/crypto/cpu_intel.cc +++ b/crypto/cpu_intel.cc
@@ -17,7 +17,9 @@ #if !defined(OPENSSL_NO_ASM) && \ (defined(OPENSSL_X86) || defined(OPENSSL_X86_64)) +#include <errno.h> #include <inttypes.h> +#include <limits.h> #include <stdlib.h> #include <string.h> @@ -89,8 +91,10 @@ } // handle_cpu_env applies the value from |in| to the CPUID values in |out[0]| -// and |out[1]|. See the comment in |OPENSSL_cpuid_setup| about this. -static void handle_cpu_env(uint32_t *out, const char *in) { +// and |out[1]|. See the comment in |OPENSSL_cpuid_setup| about this. The +// |is_last| argument specifies whether the value is at the end of the string. +// Otherwise it may be followed by a colon. +static void handle_cpu_env(uint32_t *out, const char *in, bool is_last) { const int invert_op = in[0] == '~'; const int or_op = in[0] == '|'; const int skip_first_byte = invert_op || or_op; @@ -99,9 +103,13 @@ const char *start = in + skip_first_byte; char *end; + errno = 0; + // We need to parse 64-bit values with `strtoull`. + static_assert(sizeof(unsigned long long) == sizeof(uint64_t)); unsigned long long v = strtoull(start, &end, base); - if (end == start || *end != '\0') { + if (end == start || (*end != '\0' && (is_last || *end != ':')) || + (v == ULLONG_MAX && errno == ERANGE)) { return; } @@ -124,11 +132,11 @@ uint32_t num_ids = eax; - int is_intel = ebx == 0x756e6547 /* Genu */ && // - edx == 0x49656e69 /* ineI */ && // + int is_intel = ebx == 0x756e6547 /* Genu */ && // + edx == 0x49656e69 /* ineI */ && // ecx == 0x6c65746e /* ntel */; - int is_amd = ebx == 0x68747541 /* Auth */ && // - edx == 0x69746e65 /* enti */ && // + int is_amd = ebx == 0x68747541 /* Auth */ && // + edx == 0x69746e65 /* enti */ && // ecx == 0x444d4163 /* cAMD */; uint32_t extended_features[2] = {0}; @@ -267,10 +275,10 @@ // The first value determines OPENSSL_ia32cap_P[0] and [1]. The second [2] // and [3]. - handle_cpu_env(&OPENSSL_ia32cap_P[0], env1); + handle_cpu_env(&OPENSSL_ia32cap_P[0], env1, /*is_last=*/false); env2 = strchr(env1, ':'); if (env2 != NULL) { - handle_cpu_env(&OPENSSL_ia32cap_P[2], env2 + 1); + handle_cpu_env(&OPENSSL_ia32cap_P[2], env2 + 1, /*is_last=*/true); } }
diff --git a/crypto/crypto_test.cc b/crypto/crypto_test.cc index fe5a2f6..3fa9d36 100644 --- a/crypto/crypto_test.cc +++ b/crypto/crypto_test.cc
@@ -17,16 +17,25 @@ #include <string> -#include <openssl/base.h> #include <openssl/aead.h> -#include <openssl/crypto.h> +#include <openssl/base.h> #include <openssl/cipher.h> +#include <openssl/crypto.h> #include <openssl/mem.h> #include <gtest/gtest.h> #include "internal.h" +#if (defined(OPENSSL_X86) || defined(OPENSSL_X86_64)) && \ + defined(OPENSSL_LINUX) && !defined(BORINGSSL_SHARED_LIBRARY) +#define TEST_CPUID_ENVVAR + +#include <unistd.h> +#include <sys/wait.h> +#include <errno.h> + +#endif // Test that OPENSSL_VERSION_NUMBER and OPENSSL_VERSION_TEXT are consistent. // Node.js parses the version out of OPENSSL_VERSION_TEXT instead of using @@ -159,9 +168,7 @@ } #if defined(BORINGSSL_FIPS) && !defined(OPENSSL_ASAN) -TEST(Crypto, OnDemandIntegrityTest) { - BORINGSSL_integrity_test(); -} +TEST(Crypto, OnDemandIntegrityTest) { BORINGSSL_integrity_test(); } #endif OPENSSL_DEPRECATED static void DeprecatedFunction() {} @@ -172,3 +179,46 @@ DeprecatedFunction(); } OPENSSL_END_ALLOW_DEPRECATED + +#if defined(TEST_CPUID_ENVVAR) +TEST(Crypto, CPUIDEnvVariable) { + constexpr auto is_rdrand_set = []() -> bool { + return OPENSSL_get_ia32cap(1) & 0x40000000; + }; + + // This test execs itself and sets `CPUIDEnvVariable` in the child's + // environment. So, if that's set, this is the child process. + constexpr uint8_t kSuccessExitStatus = 81; + if (getenv("CPUIDEnvVariable")) { + _exit(is_rdrand_set() ? 1 : kSuccessExitStatus); + } + + // If RDRAND isn't actually supported on this system, then this test is moot. + if (!is_rdrand_set()) { + GTEST_SKIP(); + } + + // Fork off a child process and set the `OPENSSL_ia32cap` environment + // variable such that RDRAND should _not_ be supported in the child. + const pid_t pid = fork(); + if (pid == 0) { + const char *kArgs[] = {"/proc/self/exe", + "--gtest_filter=Crypto.CPUIDEnvVariable", nullptr}; + const char *kEnv[] = {"OPENSSL_ia32cap=~0x4000000000000000:0", + "CPUIDEnvVariable=1", nullptr}; + execve(kArgs[0], const_cast<char **>(kArgs), const_cast<char **>(kEnv)); + _exit(1); + } + + ASSERT_GT(pid, 0); + pid_t waited; + int status; + do { + waited = waitpid(pid, &status, 0); + } while (waited == -1 && errno == EINTR); + + EXPECT_EQ(waited, pid); + EXPECT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), kSuccessExitStatus); +} +#endif