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