From: Sammyalhashe <[email protected]>

Patching libstdc++ random so that it checks if rdrand on amd is broken
before returning a value.

AMD 3000 CPUs seem to have a bug where the RDRAND instruction always
emits all 1s (aka 4294967295).

This commit provides a workaround (upon detecting an AMD cpu) - to try
the random function a number of times. If it continues to return the
same broken consistent value, we confirm the random instruction is buggy
on this cpu and emit a runtime error.

libstdc++-v3/ChangeLog:

        PR libstdc++/100444
        * src/c++11/random.cc (__x86_cpu_sig_ebx): New function.
        (__x86_cpu_supports_rdrand, __x86_cpu_supports_rdseed): New
        functions.
        (__x86_amd_workaround): New function.
        (which_source): Look through __x86_amd_workaround to the
        wrapped function.
        (random_device::_M_init(const string&)): Use new helpers.  Wrap
        RDRAND and RDSEED with __x86_amd_workaround on AMD CPUs.
        (random_device::_M_fini): Do nothing for __x86_amd_workaround.

Signed-off-by: Sammy Al Hashemi <[email protected]>
---
I don't have access to an affected Ryzen 3000 system, so this has not
been tested on the hardware that exhibits the bug.

Tested on x86_64-linux, cross-built and run under `qemu -cpu
EPYC-Rome`. The all-ones failure path was verified with a temporary
fault-injection patch (not included).

If anyone has an affected machine with a pre-fix BIOS, testing would
be appreciated.

 libstdc++-v3/src/c++11/random.cc | 139 +++++++++++++++++++++++--------
 1 file changed, 102 insertions(+), 37 deletions(-)

diff --git a/libstdc++-v3/src/c++11/random.cc b/libstdc++-v3/src/c++11/random.cc
index 502ba8f44c7..a7888a83c62 100644
--- a/libstdc++-v3/src/c++11/random.cc
+++ b/libstdc++-v3/src/c++11/random.cc
@@ -146,6 +146,57 @@ namespace std _GLIBCXX_VISIBILITY(default)
 #endif
 #endif
 
+#if USE_RDRAND || USE_RDSEED
+    // Check availability of cpuid and if supported, return ebx register.
+    // The ebx register can be used to identify the cpu manufacturer.
+    inline unsigned int
+    __x86_cpu_sig_ebx() noexcept
+    {
+      unsigned int ebx;
+      if (__get_cpuid_max(0, &ebx) > 0)
+       return ebx;
+      return 0;
+    }
+
+    inline bool
+    __x86_cpu_supports_rdrand() noexcept
+    {
+#ifdef USE_RDRAND
+      unsigned int eax, ebx, ecx, edx;
+      // CPUID.01H:ECX.RDRAND[bit 30]
+      __cpuid(1, eax, ebx, ecx, edx);
+      return ecx & bit_RDRND;
+#else
+      return false;
+#endif
+    }
+
+    inline bool
+    __x86_cpu_supports_rdseed() noexcept
+    {
+      unsigned int eax, ebx, ecx, edx;
+      // CPUID.(EAX=07H, ECX=0H):EBX.RDSEED[bit 18]
+      __cpuid_count(7, 0, eax, ebx, ecx, edx);
+      return ebx & bit_RDSEED;
+    }
+
+    // Workaround AMD Ryzen 3000 bug, see PR libstdc++/100444
+    unsigned int
+    __x86_amd_workaround(void* fx)
+    {
+      auto f = reinterpret_cast<unsigned int(*)(void*)>(fx);
+      const unsigned int ALL_ONES = 0xffffffff;
+      auto n = f(nullptr);
+      int retries = 10;
+      while (--retries && n == ALL_ONES)
+       n = f(nullptr);
+      if (n == ALL_ONES)
+       std::__throw_runtime_error(__N("random_device: AMD Ryzen 3000 bug "
+                                      "detected, BIOS update is required."));
+      return n;
+    }
+#endif
+
 #ifdef USE_DARN
     unsigned int
     __attribute__((target("cpu=power9")))
@@ -279,6 +330,11 @@ namespace std _GLIBCXX_VISIBILITY(default)
        return darn;
 #endif
 
+#if defined USE_RDRAND || defined USE_RDSEED
+      if (func == &__x86_amd_workaround)
+       return which_source(reinterpret_cast<decltype(func)>(file), nullptr);
+#endif
+
 #ifdef _GLIBCXX_USE_DEV_RANDOM
       if (file != nullptr)
        return device_file;
@@ -412,31 +468,33 @@ namespace std _GLIBCXX_VISIBILITY(default)
 #ifdef USE_RDSEED
     if (which & rdseed)
     {
-      unsigned int eax, ebx, ecx, edx;
-      // Check availability of cpuid and, for now at least, also the
-      // CPU signature for Intel, AMD and Hygon.
-      if (__get_cpuid_max(0, &ebx) > 0
-         && (ebx == signature_INTEL_ebx
-             || ebx == signature_AMD_ebx
-             || ebx == signature_HYGON_ebx))
+      const unsigned int sig_ebx = __x86_cpu_sig_ebx();
+      // For now, we only use RDSEED for Intel, AMD, and Hygon
+      // processors.
+      if (sig_ebx == signature_INTEL_ebx
+         || sig_ebx == signature_AMD_ebx
+         || sig_ebx == signature_HYGON_ebx)
+      {
+       if (__x86_cpu_supports_rdseed())
        {
-         // CPUID.(EAX=07H, ECX=0H):EBX.RDSEED[bit 18]
-         __cpuid_count(7, 0, eax, ebx, ecx, edx);
-         if (ebx & bit_RDSEED)
-           {
-#ifdef USE_RDRAND
-             // CPUID.01H:ECX.RDRAND[bit 30]
-             __cpuid(1, eax, ebx, ecx, edx);
-             if (ecx & bit_RDRND)
-               {
-                 _M_func = &__x86_rdseed_rdrand;
-                 return;
-               }
-#endif
-             _M_func = &__x86_rdseed;
-             return;
-           }
+         if (__x86_cpu_supports_rdrand())
+         {
+           _M_func = &__x86_rdseed_rdrand;
+         }
+         else
+         {
+           _M_func = &__x86_rdseed;
+         }
+
+         // AMD 3000 Workaround
+         if (sig_ebx == signature_AMD_ebx)
+         {
+           _M_file = reinterpret_cast<void*>(_M_func);
+           _M_func = &__x86_amd_workaround;
+         }
+         return;
        }
+      }
       err = unsupported;
     }
 #endif // USE_RDSEED
@@ -444,22 +502,24 @@ namespace std _GLIBCXX_VISIBILITY(default)
 #ifdef USE_RDRAND
     if (which & rdrand)
     {
-      unsigned int eax, ebx, ecx, edx;
-      // Check availability of cpuid and, for now at least, also the
-      // CPU signature for Intel, AMD and Hygon.
-      if (__get_cpuid_max(0, &ebx) > 0
-         && (ebx == signature_INTEL_ebx
-             || ebx == signature_AMD_ebx
-             || ebx == signature_HYGON_ebx))
+      const unsigned int sig_ebx = __x86_cpu_sig_ebx();
+      // For now, we only use RDRAND for Intel, AMD, and Hygon
+      // processors.
+      if (sig_ebx == signature_INTEL_ebx
+         || sig_ebx == signature_AMD_ebx
+         || sig_ebx == signature_HYGON_ebx)
+      {
+       if (__x86_cpu_supports_rdrand())
        {
-         // CPUID.01H:ECX.RDRAND[bit 30]
-         __cpuid(1, eax, ebx, ecx, edx);
-         if (ecx & bit_RDRND)
-           {
-             _M_func = &__x86_rdrand;
-             return;
-           }
+         _M_func = &__x86_rdrand;
+         if (sig_ebx == signature_AMD_ebx)
+         {
+           _M_file = reinterpret_cast<void*>(_M_func);
+           _M_func = &__x86_amd_workaround;
+         }
+         return;
        }
+      }
       err = unsupported;
     }
 #endif // USE_RDRAND
@@ -584,6 +644,11 @@ namespace std _GLIBCXX_VISIBILITY(default)
     if (!_M_file)
       return;
 
+#if USE_RDRAND || USE_RDSEED
+    if (_M_func == &__x86_amd_workaround)
+      return;
+#endif
+
 #if USE_LCG
     if (_M_func == &__lcg)
       {
-- 
2.55.0

Reply via email to