QCryptographicHash: fix UB (data race on concurrent result()) [2nd try]
The previous attempt at fixing QTBUG-110058,
ccad719d2e, 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>
bb10
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…
Reference in New Issue