QCryptographicHash: fix UB (data race on concurrent result()) [2nd try]
The previous attempt at fixing QTBUG-110058, ccad719d2e935306e601b0f6af5ff2acb7cd272e, was incomplete: - the if (result.isEmpty()) check at the beginning of finalize() was not protected, so it raced against the assignment at the end of finalize(), which was protected - because the mutex was not locked during the finalization of the hash algorithm, two threads could perform this operation simultaneously, which isn't such a bad idea in principle, as it can reduce latency, but for that to work, the losing thread needs to throw away its own work and adopt the work of the other thread, but that wasn't done: both threads would write their result to 'result', just one after the other, but that's still a data race, since the eventual _reader_ of the result cannot be protected (is outside the class). Besides, we don't even know whether the algorithm-specific finalization functions are ok with being called from separate threads on the same context object - in addition, the mutex wasn't necessary when finalize() was called from the static hash() function, as no sharing could possibly take place there (the state is function-local) Fix all of the above by largely reverting the first attempt, dragging the result.isEmpty() check out of finalize() and into resultView() and instead simply holding the mutex over these two calls in resultView(). To see why this is sufficient, consider that resultView() is now idempotent again: the result is written once, the next thread waits and then finds the work done. All following accesses to the result are then reads, which happen-after the write at the end of finalize(). The accesses to 'result' from reset() need no protection, as reset() is a mutable function, and calling a mutable function on a shared QCryptographicHash object is already UB. Only two const functions may be called that way. Pick-to: 6.5 6.4 6.2 5.15 Fixes: QTBUG-110058 Change-Id: Ia8ac095b785519682090801c1012e9dded6d60b2 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
c2cdd7c371
commit
b904de43a5
@ -5,6 +5,7 @@
|
||||
#include <qcryptographichash.h>
|
||||
#include <qiodevice.h>
|
||||
#include <qmutex.h>
|
||||
#include <private/qlocking_p.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
@ -216,7 +217,10 @@ public:
|
||||
|
||||
void reset() noexcept;
|
||||
void addData(QByteArrayView bytes) noexcept;
|
||||
void finalize() noexcept;
|
||||
// when not called from the static hash() function, this function needs to be
|
||||
// called with finalizeMutex held:
|
||||
void finalizeUnchecked() noexcept;
|
||||
// END functions that need to be called with finalizeMutex held
|
||||
QByteArrayView resultView() const noexcept { return result.toByteArrayView(); }
|
||||
static bool supportsAlgorithm(QCryptographicHash::Algorithm method);
|
||||
|
||||
@ -254,7 +258,16 @@ public:
|
||||
blake2s_state blake2sContext;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
|
||||
#ifndef USING_OPENSSL30
|
||||
enum class Sha3Variant
|
||||
{
|
||||
Sha3,
|
||||
Keccak
|
||||
};
|
||||
void sha3Finish(int bitCount, Sha3Variant sha3Variant);
|
||||
#endif
|
||||
#endif
|
||||
class SmallByteArray {
|
||||
std::array<quint8, MaxHashLength> m_data;
|
||||
static_assert(MaxHashLength <= std::numeric_limits<std::uint8_t>::max());
|
||||
@ -272,27 +285,16 @@ public:
|
||||
QByteArrayView toByteArrayView() const noexcept
|
||||
{ return QByteArrayView{m_data.data(), size()}; }
|
||||
};
|
||||
|
||||
// Only write to the result under this mutex
|
||||
// protects result in finalize()
|
||||
QBasicMutex finalizeMutex;
|
||||
SmallByteArray result;
|
||||
|
||||
const QCryptographicHash::Algorithm method;
|
||||
|
||||
#if !defined(QT_CRYPTOGRAPHICHASH_ONLY_SHA1) && !defined(USING_OPENSSL30)
|
||||
enum class Sha3Variant
|
||||
{
|
||||
Sha3,
|
||||
Keccak
|
||||
};
|
||||
void sha3Finish(SmallByteArray *tmpresult, int bitCount, Sha3Variant sha3Variant);
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
|
||||
#ifndef USING_OPENSSL30
|
||||
void QCryptographicHashPrivate::sha3Finish(SmallByteArray *tmpresult, int bitCount,
|
||||
Sha3Variant sha3Variant)
|
||||
void QCryptographicHashPrivate::sha3Finish(int bitCount, Sha3Variant sha3Variant)
|
||||
{
|
||||
/*
|
||||
FIPS 202 §6.1 defines SHA-3 in terms of calculating the Keccak function
|
||||
@ -316,7 +318,7 @@ void QCryptographicHashPrivate::sha3Finish(SmallByteArray *tmpresult, int bitCou
|
||||
*/
|
||||
static const unsigned char sha3FinalSuffix = 0x80;
|
||||
|
||||
tmpresult->resizeForOverwrite(bitCount / 8);
|
||||
result.resizeForOverwrite(bitCount / 8);
|
||||
|
||||
SHA3Context copy = sha3Context;
|
||||
|
||||
@ -328,7 +330,7 @@ void QCryptographicHashPrivate::sha3Finish(SmallByteArray *tmpresult, int bitCou
|
||||
break;
|
||||
}
|
||||
|
||||
sha3Final(©, tmpresult->data());
|
||||
sha3Final(©, result.data());
|
||||
}
|
||||
#endif // !QT_CONFIG(opensslv30)
|
||||
#endif
|
||||
@ -725,44 +727,53 @@ QByteArray QCryptographicHash::result() const
|
||||
*/
|
||||
QByteArrayView QCryptographicHash::resultView() const noexcept
|
||||
{
|
||||
d->finalize();
|
||||
// resultView() is a const function, so concurrent calls are allowed; protect:
|
||||
{
|
||||
const auto lock = qt_scoped_lock(d->finalizeMutex);
|
||||
// check that no other thread already finalizeUnchecked()'ed before us:
|
||||
if (d->result.isEmpty())
|
||||
d->finalizeUnchecked();
|
||||
}
|
||||
// resultView() remains(!) valid even after we dropped the mutex
|
||||
return d->resultView();
|
||||
}
|
||||
|
||||
void QCryptographicHashPrivate::finalize() noexcept
|
||||
{
|
||||
if (!result.isEmpty())
|
||||
return;
|
||||
/*!
|
||||
\internal
|
||||
|
||||
SmallByteArray tmpresult;
|
||||
Must be called with finalizeMutex held (except from static hash() function,
|
||||
where no sharing can take place).
|
||||
*/
|
||||
void QCryptographicHashPrivate::finalizeUnchecked() noexcept
|
||||
{
|
||||
#ifdef USING_OPENSSL30
|
||||
if (method == QCryptographicHash::Blake2b_160 ||
|
||||
method == QCryptographicHash::Blake2b_256 ||
|
||||
method == QCryptographicHash::Blake2b_384) {
|
||||
const auto length = hashLengthInternal(method);
|
||||
blake2b_state copy = blake2bContext;
|
||||
tmpresult.resizeForOverwrite(length);
|
||||
blake2b_final(©, tmpresult.data(), length);
|
||||
result.resizeForOverwrite(length);
|
||||
blake2b_final(©, result.data(), length);
|
||||
} else if (method == QCryptographicHash::Blake2s_128 ||
|
||||
method == QCryptographicHash::Blake2s_160 ||
|
||||
method == QCryptographicHash::Blake2s_224) {
|
||||
const auto length = hashLengthInternal(method);
|
||||
blake2s_state copy = blake2sContext;
|
||||
tmpresult.resizeForOverwrite(length);
|
||||
blake2s_final(©, tmpresult.data(), length);
|
||||
result.resizeForOverwrite(length);
|
||||
blake2s_final(©, result.data(), length);
|
||||
} else if (!initializationFailed) {
|
||||
EVP_MD_CTX_ptr copy = EVP_MD_CTX_ptr(EVP_MD_CTX_new());
|
||||
EVP_MD_CTX_copy_ex(copy.get(), context.get());
|
||||
tmpresult.resizeForOverwrite(EVP_MD_get_size(algorithm.get()));
|
||||
EVP_DigestFinal_ex(copy.get(), tmpresult.data(), nullptr);
|
||||
result.resizeForOverwrite(EVP_MD_get_size(algorithm.get()));
|
||||
EVP_DigestFinal_ex(copy.get(), result.data(), nullptr);
|
||||
}
|
||||
#else
|
||||
switch (method) {
|
||||
case QCryptographicHash::Sha1: {
|
||||
Sha1State copy = sha1Context;
|
||||
tmpresult.resizeForOverwrite(20);
|
||||
result.resizeForOverwrite(20);
|
||||
sha1FinalizeState(©);
|
||||
sha1ToHash(©, tmpresult.data());
|
||||
sha1ToHash(©, result.data());
|
||||
break;
|
||||
}
|
||||
#ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
|
||||
@ -773,52 +784,52 @@ void QCryptographicHashPrivate::finalize() noexcept
|
||||
#else
|
||||
case QCryptographicHash::Md4: {
|
||||
md4_context copy = md4Context;
|
||||
tmpresult.resizeForOverwrite(MD4_RESULTLEN);
|
||||
md4_final(©, tmpresult.data());
|
||||
result.resizeForOverwrite(MD4_RESULTLEN);
|
||||
md4_final(©, result.data());
|
||||
break;
|
||||
}
|
||||
case QCryptographicHash::Md5: {
|
||||
MD5Context copy = md5Context;
|
||||
tmpresult.resizeForOverwrite(16);
|
||||
MD5Final(©, tmpresult.data());
|
||||
result.resizeForOverwrite(16);
|
||||
MD5Final(©, result.data());
|
||||
break;
|
||||
}
|
||||
case QCryptographicHash::Sha224: {
|
||||
SHA224Context copy = sha224Context;
|
||||
tmpresult.resizeForOverwrite(SHA224HashSize);
|
||||
SHA224Result(©, tmpresult.data());
|
||||
result.resizeForOverwrite(SHA224HashSize);
|
||||
SHA224Result(©, result.data());
|
||||
break;
|
||||
}
|
||||
case QCryptographicHash::Sha256: {
|
||||
SHA256Context copy = sha256Context;
|
||||
tmpresult.resizeForOverwrite(SHA256HashSize);
|
||||
SHA256Result(©, tmpresult.data());
|
||||
result.resizeForOverwrite(SHA256HashSize);
|
||||
SHA256Result(©, result.data());
|
||||
break;
|
||||
}
|
||||
case QCryptographicHash::Sha384: {
|
||||
SHA384Context copy = sha384Context;
|
||||
tmpresult.resizeForOverwrite(SHA384HashSize);
|
||||
SHA384Result(©, tmpresult.data());
|
||||
result.resizeForOverwrite(SHA384HashSize);
|
||||
SHA384Result(©, result.data());
|
||||
break;
|
||||
}
|
||||
case QCryptographicHash::Sha512: {
|
||||
SHA512Context copy = sha512Context;
|
||||
tmpresult.resizeForOverwrite(SHA512HashSize);
|
||||
SHA512Result(©, tmpresult.data());
|
||||
result.resizeForOverwrite(SHA512HashSize);
|
||||
SHA512Result(©, result.data());
|
||||
break;
|
||||
}
|
||||
case QCryptographicHash::RealSha3_224:
|
||||
case QCryptographicHash::RealSha3_256:
|
||||
case QCryptographicHash::RealSha3_384:
|
||||
case QCryptographicHash::RealSha3_512: {
|
||||
sha3Finish(&tmpresult, 8 * hashLengthInternal(method), Sha3Variant::Sha3);
|
||||
sha3Finish(8 * hashLengthInternal(method), Sha3Variant::Sha3);
|
||||
break;
|
||||
}
|
||||
case QCryptographicHash::Keccak_224:
|
||||
case QCryptographicHash::Keccak_256:
|
||||
case QCryptographicHash::Keccak_384:
|
||||
case QCryptographicHash::Keccak_512: {
|
||||
sha3Finish(&tmpresult, 8 * hashLengthInternal(method), Sha3Variant::Keccak);
|
||||
sha3Finish(8 * hashLengthInternal(method), Sha3Variant::Keccak);
|
||||
break;
|
||||
}
|
||||
case QCryptographicHash::Blake2b_160:
|
||||
@ -827,8 +838,8 @@ void QCryptographicHashPrivate::finalize() noexcept
|
||||
case QCryptographicHash::Blake2b_512: {
|
||||
const auto length = hashLengthInternal(method);
|
||||
blake2b_state copy = blake2bContext;
|
||||
tmpresult.resizeForOverwrite(length);
|
||||
blake2b_final(©, tmpresult.data(), length);
|
||||
result.resizeForOverwrite(length);
|
||||
blake2b_final(©, result.data(), length);
|
||||
break;
|
||||
}
|
||||
case QCryptographicHash::Blake2s_128:
|
||||
@ -837,18 +848,13 @@ void QCryptographicHashPrivate::finalize() noexcept
|
||||
case QCryptographicHash::Blake2s_256: {
|
||||
const auto length = hashLengthInternal(method);
|
||||
blake2s_state copy = blake2sContext;
|
||||
tmpresult.resizeForOverwrite(length);
|
||||
blake2s_final(©, tmpresult.data(), length);
|
||||
result.resizeForOverwrite(length);
|
||||
blake2s_final(©, result.data(), length);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif // !QT_CONFIG(opensslv30)
|
||||
|
||||
// we're called from a const function, so only write to this->result under
|
||||
// a mutex
|
||||
QMutexLocker locker(&finalizeMutex);
|
||||
result = std::move(tmpresult);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -861,7 +867,7 @@ QByteArray QCryptographicHash::hash(QByteArrayView data, Algorithm method)
|
||||
{
|
||||
QCryptographicHashPrivate hash(method);
|
||||
hash.addData(data);
|
||||
hash.finalize();
|
||||
hash.finalizeUnchecked(); // no mutex needed: no-one but us has access to 'hash'
|
||||
return hash.resultView().toByteArray();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user