Fix QRandomGenerator initialization on AMD CPUs

Some AMD CPUs (e.g. AMD A4-6250J and AMD Ryzen 3000-series) have a
failing random generation instruction, which always returns
0xffffffff, even when generation was "successful".

This code checks if hardware random generator generates four consecutive
equal numbers. If it does, then we probably have a failing one and
should disable it completely.

Change-Id: I38c87920ca2e8cce4143afbff5e453ce3845d11a
Fixes: QTBUG-69423
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
(cherry picked from commit 5839714d986f28412c9f9ed4801d1bf9378f7b51)
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Dmitry Kazakov 2019-09-05 10:23:08 +03:00 committed by Edward Welbourne
parent 4a9292f169
commit d1646b37b1
5 changed files with 91 additions and 49 deletions

View File

@ -90,42 +90,6 @@ DECLSPEC_IMPORT BOOLEAN WINAPI SystemFunction036(PVOID RandomBuffer, ULONG Rando
QT_BEGIN_NAMESPACE
#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND)
static qsizetype qt_random_cpu(void *buffer, qsizetype count) Q_DECL_NOTHROW;
# ifdef Q_PROCESSOR_X86_64
# define _rdrandXX_step _rdrand64_step
# else
# define _rdrandXX_step _rdrand32_step
# endif
static QT_FUNCTION_TARGET(RDRND) qsizetype qt_random_cpu(void *buffer, qsizetype count) Q_DECL_NOTHROW
{
unsigned *ptr = reinterpret_cast<unsigned *>(buffer);
unsigned *end = ptr + count;
while (ptr + sizeof(qregisteruint)/sizeof(*ptr) <= end) {
if (_rdrandXX_step(reinterpret_cast<qregisteruint *>(ptr)) == 0)
goto out;
ptr += sizeof(qregisteruint)/sizeof(*ptr);
}
if (sizeof(*ptr) != sizeof(qregisteruint) && ptr != end) {
if (_rdrand32_step(ptr))
goto out;
++ptr;
}
out:
return ptr - reinterpret_cast<unsigned *>(buffer);
}
#else
static qsizetype qt_random_cpu(void *, qsizetype)
{
return 0;
}
#endif
enum {
// may be "overridden" by a member enum
FillBufferNoexcept = true
@ -366,8 +330,8 @@ Q_NEVER_INLINE void QRandomGenerator::SystemGenerator::generate(quint32 *begin,
}
qsizetype filled = 0;
if (qt_has_hwrng() && (uint(qt_randomdevice_control) & SkipHWRNG) == 0)
filled += qt_random_cpu(buffer, count);
if (qHasHwrng() && (uint(qt_randomdevice_control) & SkipHWRNG) == 0)
filled += qRandomCpu(buffer, count);
if (filled != count && (uint(qt_randomdevice_control) & SkipSystemRNG) == 0) {
qsizetype bytesFilled =

View File

@ -79,14 +79,6 @@ extern Q_CORE_EXPORT QBasicAtomicInteger<uint> qt_randomdevice_control;
enum { qt_randomdevice_control = 0 };
#endif
inline bool qt_has_hwrng()
{
#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND)
return qCpuHasFeature(RDRND);
#else
return false;
#endif
}
QT_END_NAMESPACE

View File

@ -376,6 +376,38 @@ static quint64 detectProcessorFeatures()
features &= ~AllAVX512;
}
#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND)
/**
* Some AMD CPUs (e.g. AMD A4-6250J and AMD Ryzen 3000-series) have a
* failing random generation instruction, which always returns
* 0xffffffff, even when generation was "successful".
*
* This code checks if hardware random generator generates four consecutive
* equal numbers. If it does, then we probably have a failing one and
* should disable it completely.
*
* https://bugreports.qt.io/browse/QTBUG-69423
*/
if (features & CpuFeatureRDRND) {
const qsizetype testBufferSize = 4;
unsigned testBuffer[4] = {};
const qsizetype generated = qRandomCpu(testBuffer, testBufferSize);
if (Q_UNLIKELY(generated == testBufferSize &&
testBuffer[0] == testBuffer[1] &&
testBuffer[1] == testBuffer[2] &&
testBuffer[2] == testBuffer[3])) {
fprintf(stderr, "WARNING: CPU random generator seem to be failing, disable hardware random number generation\n");
fprintf(stderr, "WARNING: RDRND generated: 0x%x 0x%x 0x%x 0x%x\n",
testBuffer[0], testBuffer[1], testBuffer[2], testBuffer[3]);
features &= ~CpuFeatureRDRND;
}
}
#endif
return features;
}
@ -589,4 +621,40 @@ void qDumpCPUFeatures()
puts("");
}
#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND)
# ifdef Q_PROCESSOR_X86_64
# define _rdrandXX_step _rdrand64_step
# else
# define _rdrandXX_step _rdrand32_step
# endif
QT_FUNCTION_TARGET(RDRND) qsizetype qRandomCpu(void *buffer, qsizetype count) Q_DECL_NOTHROW
{
unsigned *ptr = reinterpret_cast<unsigned *>(buffer);
unsigned *end = ptr + count;
int retries = 10;
while (ptr + sizeof(qregisteruint)/sizeof(*ptr) <= end) {
if (_rdrandXX_step(reinterpret_cast<qregisteruint *>(ptr)))
ptr += sizeof(qregisteruint)/sizeof(*ptr);
else if (--retries == 0)
goto out;
}
while (sizeof(*ptr) != sizeof(qregisteruint) && ptr != end) {
bool ok = _rdrand32_step(ptr);
if (!ok && --retries)
continue;
if (ok)
++ptr;
break;
}
out:
return ptr - reinterpret_cast<unsigned *>(buffer);
}
#endif
QT_END_NAMESPACE

View File

@ -346,6 +346,15 @@ extern Q_CORE_EXPORT QBasicAtomicInteger<unsigned> qt_cpu_features[2];
#endif
Q_CORE_EXPORT void qDetectCpuFeatures();
#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND)
Q_CORE_EXPORT qsizetype qRandomCpu(void *, qsizetype) Q_DECL_NOTHROW;
#else
static inline qsizetype qRandomCpu(void *, qsizetype) Q_DECL_NOTHROW
{
return 0;
}
#endif
static inline quint64 qCpuFeatures()
{
quint64 features = qt_cpu_features[0].load();
@ -366,6 +375,15 @@ static inline quint64 qCpuFeatures()
#define qCpuHasFeature(feature) (((qCompilerCpuFeatures & CpuFeature ## feature) == CpuFeature ## feature) \
|| ((qCpuFeatures() & CpuFeature ## feature) == CpuFeature ## feature))
inline bool qHasHwrng()
{
#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND)
return qCpuHasFeature(RDRND);
#else
return false;
#endif
}
#define ALIGNMENT_PROLOGUE_16BYTES(ptr, i, length) \
for (; i < static_cast<int>(qMin(static_cast<quintptr>(length), ((4 - ((reinterpret_cast<quintptr>(ptr) >> 2) & 0x3)) & 0x3))); ++i)

View File

@ -320,7 +320,7 @@ void tst_QRandomGenerator::generate32_data()
QTest::newRow("fixed") << (RandomValue32 & RandomDataMask);
QTest::newRow("global") << 0U;
#ifdef QT_BUILD_INTERNAL
if (qt_has_hwrng())
if (qHasHwrng())
QTest::newRow("hwrng") << uint(UseSystemRNG);
QTest::newRow("system") << uint(UseSystemRNG | SkipHWRNG);
# ifdef HAVE_FALLBACK_ENGINE
@ -755,7 +755,7 @@ void tst_QRandomGenerator::stdUniformIntDistribution_data()
auto newRow = [&](quint32 max) {
#ifdef QT_BUILD_INTERNAL
if (qt_has_hwrng())
if (qHasHwrng())
QTest::addRow("hwrng:%u", max) << uint(UseSystemRNG) << max;
QTest::addRow("system:%u", max) << uint(UseSystemRNG | SkipHWRNG) << max;
# ifdef HAVE_FALLBACK_ENGINE
@ -868,7 +868,7 @@ void tst_QRandomGenerator::stdUniformRealDistribution_data()
auto newRow = [&](double min, double sup) {
#ifdef QT_BUILD_INTERNAL
if (qt_has_hwrng())
if (qHasHwrng())
QTest::addRow("hwrng:%g-%g", min, sup) << uint(UseSystemRNG) << min << sup;
QTest::addRow("system:%g-%g", min, sup) << uint(UseSystemRNG | SkipHWRNG) << min << sup;
# ifdef HAVE_FALLBACK_ENGINE