Repository: randombit/botan
Pull request #3535 (2023-04-29 19:58:15)

Add env var to clear CPUID bits

coveralls: 
[![Coverage Status](https://coveralls.io/builds/59463184/badge)](https://coveralls.io/builds/59463184)

Coverage: 92.146% (+0.007%) from 92.139% when pulling **2f9ee420ed579888c010f72e946bb754eeed8b9f on jack/clear-cpuid-env-var** into **8a8dec9bbac2bd8179a2842fae9a90747ee5e7cc on master**.


Patch:
diff --git a/doc/api_ref/env_vars.rst b/doc/api_ref/env_vars.rst
index e00ff6e84..50d3d52d1 100644
--- a/doc/api_ref/env_vars.rst
+++ b/doc/api_ref/env_vars.rst
@@ -23,3 +23,8 @@ library. The variables and their behavior are described here.
   will print the text message of the exception to stderr. This is primarily
   intended for debugging.
 
+* ``BOTAN_CLEAR_CPUID``: this variable can be set to a comma-separated list of
+  CPUID fields to ignore. For example setting ``BOTAN_CLEAR_CPUID=avx2,avx512``
+  will cause AVX2 and AVX-512 codepaths to be avoided. Note that disabling basic
+  features (notably NEON or SSE2/SSSE3) can cause other higher level features
+  like AES-NI to also become disabled.
diff --git a/src/lib/utils/cpuid/cpuid.cpp b/src/lib/utils/cpuid/cpuid.cpp
index cd2f62c21..221ad000d 100644
--- a/src/lib/utils/cpuid/cpuid.cpp
+++ b/src/lib/utils/cpuid/cpuid.cpp
@@ -144,6 +144,19 @@ CPUID::CPUID_Data::CPUID_Data()
 
    if(runtime_check_if_big_endian())
       m_processor_features |= CPUID::CPUID_IS_BIG_ENDIAN_BIT;
+
+   std::string clear_cpuid_env;
+   if(OS::read_env_variable(clear_cpuid_env, "BOTAN_CLEAR_CPUID"))
+      {
+      for(const auto& cpuid : split_on(clear_cpuid_env, ','))
+         {
+         for(auto& bit: CPUID::bit_from_string(cpuid))
+            {
+            const uint32_t cleared = ~static_cast<uint32_t>(bit);
+            m_processor_features &= cleared;
+            }
+         }
+      }
    }
 
 std::vector<CPUID::CPUID_bits>
diff --git a/src/lib/utils/cpuid/cpuid.h b/src/lib/utils/cpuid/cpuid.h
index 633077014..f6cd96c81 100644
--- a/src/lib/utils/cpuid/cpuid.h
+++ b/src/lib/utils/cpuid/cpuid.h
@@ -138,7 +138,7 @@ class BOTAN_TEST_API CPUID final
       * Check if the processor supports POWER8 crypto extensions
       */
       static bool has_power_crypto()
-         { return has_cpuid_bit(CPUID_POWER_CRYPTO_BIT); }
+         { return has_altivec() && has_cpuid_bit(CPUID_POWER_CRYPTO_BIT); }
 
       /**
       * Check if the processor supports POWER9 DARN RNG
@@ -165,49 +165,49 @@ class BOTAN_TEST_API CPUID final
       * Check if the processor supports ARMv8 SHA1
       */
       static bool has_arm_sha1()
-         { return has_cpuid_bit(CPUID_ARM_SHA1_BIT); }
+         { return has_neon() && has_cpuid_bit(CPUID_ARM_SHA1_BIT); }
 
       /**
       * Check if the processor supports ARMv8 SHA2
       */
       static bool has_arm_sha2()
-         { return has_cpuid_bit(CPUID_ARM_SHA2_BIT); }
+         { return has_neon() && has_cpuid_bit(CPUID_ARM_SHA2_BIT); }
 
       /**
       * Check if the processor supports ARMv8 AES
       */
       static bool has_arm_aes()
-         { return has_cpuid_bit(CPUID_ARM_AES_BIT); }
+         { return has_neon() && has_cpuid_bit(CPUID_ARM_AES_BIT); }
 
       /**
       * Check if the processor supports ARMv8 PMULL
       */
       static bool has_arm_pmull()
-         { return has_cpuid_bit(CPUID_ARM_PMULL_BIT); }
+         { return has_neon() && has_cpuid_bit(CPUID_ARM_PMULL_BIT); }
 
       /**
       * Check if the processor supports ARMv8 SHA-512
       */
       static bool has_arm_sha2_512()
-         { return has_cpuid_bit(CPUID_ARM_SHA2_512_BIT); }
+         { return has_neon() && has_cpuid_bit(CPUID_ARM_SHA2_512_BIT); }
 
       /**
       * Check if the processor supports ARMv8 SHA-3
       */
       static bool has_arm_sha3()
-         { return has_cpuid_bit(CPUID_ARM_SHA3_BIT); }
+         { return has_neon() && has_cpuid_bit(CPUID_ARM_SHA3_BIT); }
 
       /**
       * Check if the processor supports ARMv8 SM3
       */
       static bool has_arm_sm3()
-         { return has_cpuid_bit(CPUID_ARM_SM3_BIT); }
+         { return has_neon() && has_cpuid_bit(CPUID_ARM_SM3_BIT); }
 
       /**
       * Check if the processor supports ARMv8 SM4
       */
       static bool has_arm_sm4()
-         { return has_cpuid_bit(CPUID_ARM_SM4_BIT); }
+         { return has_neon() && has_cpuid_bit(CPUID_ARM_SM4_BIT); }
 
 #endif
 
@@ -229,7 +229,7 @@ class BOTAN_TEST_API CPUID final
       * Check if the processor supports SSSE3
       */
       static bool has_ssse3()
-         { return has_cpuid_bit(CPUID_SSSE3_BIT); }
+         { return has_sse2() && has_cpuid_bit(CPUID_SSSE3_BIT); }
 
       /**
       * Check if the processor supports AVX2
@@ -249,13 +249,13 @@ class BOTAN_TEST_API CPUID final
       * Check if the processor supports AVX-512 AES (VAES)
       */
       static bool has_avx512_aes()
-         { return has_cpuid_bit(CPUID_AVX512_AES_BIT); }
+         { return has_avx512() && has_cpuid_bit(CPUID_AVX512_AES_BIT); }
 
       /**
       * Check if the processor supports AVX-512 VPCLMULQDQ
       */
       static bool has_avx512_clmul()
-         { return has_cpuid_bit(CPUID_AVX512_CLMUL_BIT); }
+         { return has_avx512() && has_cpuid_bit(CPUID_AVX512_CLMUL_BIT); }
 
       /**
       * Check if the processor supports BMI2 (and BMI1)
@@ -267,19 +267,19 @@ class BOTAN_TEST_API CPUID final
       * Check if the processor supports AES-NI
       */
       static bool has_aes_ni()
-         { return has_cpuid_bit(CPUID_AESNI_BIT); }
+         { return has_ssse3() && has_cpuid_bit(CPUID_AESNI_BIT); }
 
       /**
       * Check if the processor supports CLMUL
       */
       static bool has_clmul()
-         { return has_cpuid_bit(CPUID_CLMUL_BIT); }
+         { return has_ssse3() && has_cpuid_bit(CPUID_CLMUL_BIT); }
 
       /**
       * Check if the processor supports Intel SHA extension
       */
       static bool has_intel_sha()
-         { return has_cpuid_bit(CPUID_SHA_BIT); }
+         { return has_sse2() && has_cpuid_bit(CPUID_SHA_BIT); }
 
       /**
       * Check if the processor supports ADX extension


Test patch:
diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py
index 06a9da691..57715d38f 100755
--- a/src/scripts/test_cli.py
+++ b/src/scripts/test_cli.py
@@ -22,6 +22,7 @@ import ssl
 import subprocess
 import sys
 import tempfile
+import traceback
 import threading
 import time
 from multiprocessing.pool import ThreadPool
@@ -64,7 +65,14 @@ def setup_logging(options):
 def random_port_number():
     return random.randint(1024, 65535)
 
-def test_cli(cmd, cmd_options, expected_output=None, cmd_input=None, expected_stderr=None, use_drbg=True):
+
+def test_cli(cmd, cmd_options,
+             expected_output=None,
+             cmd_input=None,
+             expected_stderr=None,
+             use_drbg=True,
+             extra_env=None):
+    # pylint: disable=too-many-locals
     global TESTS_RUN
 
     TESTS_RUN += 1
@@ -90,7 +98,13 @@ def test_cli(cmd, cmd_options, expected_output=None, cmd_input=None, expected_st
     stderr = None
 
     if cmd_input is None:
-        proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        proc_env = None
+        if extra_env:
+            proc_env = os.environ
+            for (k,v) in extra_env.items():
+                proc_env[k] = v
+
+        proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=proc_env)
         (stdout, stderr) = proc.communicate()
     else:
         proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -769,6 +783,16 @@ def cli_cpuid_tests(_tmp_dir):
         if flag != '' and flag_re.match(flag) is None:
             logging.error('Unexpected CPUID flag name "%s"', flag)
 
+        env = {'BOTAN_CLEAR_CPUID': flag}
+        cpuid_output = test_cli("cpuid", [], None, None, None, True, env)
+        mod_flags = cpuid_output[13:].split(' ')
+
+        for f in mod_flags:
+            if f == flag:
+                logging.error('Clearing CPUID %s did not disable it', flag)
+            if f not in flags:
+                logging.error('Clearing CPUID %s caused flag %s to appear', flag, f)
+
 def cli_cc_enc_tests(_tmp_dir):
     test_cli("cc_encrypt", ["8028028028028029", "pass"], "4308989841607208")
     test_cli("cc_decrypt", ["4308989841607208", "pass"], "8028028028028027")
@@ -1367,6 +1391,7 @@ def run_test(fn_name, fn):
     try:
         fn(tmp_dir)
     except Exception as e: # pylint: disable=broad-except
+        logging.info(traceback.format_exc())
         logging.error("Test %s threw exception: %s", fn_name, e)
 
     shutil.rmtree(tmp_dir)