QHash security fix (1.5/2): qHash two arguments overload support

Algorithmic complexity attacks against hash tables have been known
since 2003 (cf. [1, 2]), and they have been left unpatched for years
until the 2011 attacks [3] against many libraries /
(reference) implementations of programming languages.

This patch adds a qHash overload taking two arguments: the value to
be hashed, and a uint to be used as a seed for the hash function
itself (support the global QHash seed was added in a previous patch).
The seed itself is not used just yet; instead, 0 is passed.

Compatibility with the one-argument qHash(T) implementation is kept
through a catch-all template.

[1] http://www.cs.rice.edu/~scrosby/hash/CrosbyWallach_UsenixSec2003.pdf
[2] http://perldoc.perl.org/perlsec.html#Algorithmic-Complexity-Attacks
[3] http://www.ocert.org/advisories/ocert-2011-003.html

Task-number: QTBUG-23529
Change-Id: I1d0a84899476d134db455418c8043a349a7e5317
Reviewed-by: João Abecasis <joao.abecasis@nokia.com>
This commit is contained in:
Giuseppe D'Angelo 2012-03-24 08:50:02 +00:00 committed by Qt by Nokia
parent fb20f9c2da
commit 9a77171ccc
7 changed files with 195 additions and 56 deletions

4
dist/changes-5.0.0 vendored
View File

@ -346,6 +346,10 @@ QtCore
* QEvent::AccessibilityPrepare, AccessibilityHelp and AccessibilityDescription removed: * QEvent::AccessibilityPrepare, AccessibilityHelp and AccessibilityDescription removed:
* The enum values simply didn't make sense in the first place and should simply be dropped. * The enum values simply didn't make sense in the first place and should simply be dropped.
* [QTBUG-23529] QHash is now more resilient to a family of denial of service
attacks exploiting algorithmic complexity, by supporting two-arguments overloads
of the qHash() hashing function.
QtGui QtGui
----- -----
* Accessibility has been refactored. The hierachy of accessible objects is implemented via * Accessibility has been refactored. The hierachy of accessible objects is implemented via

View File

@ -155,9 +155,9 @@ inline bool operator==(const Employee &e1, const Employee &e2)
&& e1.dateOfBirth() == e2.dateOfBirth(); && e1.dateOfBirth() == e2.dateOfBirth();
} }
inline uint qHash(const Employee &key) inline uint qHash(const Employee &key, uint seed)
{ {
return qHash(key.name()) ^ key.dateOfBirth().day(); return qHash(key.name(), seed) ^ key.dateOfBirth().day();
} }
#endif // EMPLOYEE_H #endif // EMPLOYEE_H

View File

@ -54,7 +54,7 @@ class Q_CORE_EXPORT QBitArray
{ {
friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QBitArray &); friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QBitArray &);
friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QBitArray &); friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QBitArray &);
friend Q_CORE_EXPORT uint qHash(const QBitArray &key); friend Q_CORE_EXPORT uint qHash(const QBitArray &key, uint seed);
QByteArray d; QByteArray d;
public: public:

View File

@ -84,9 +84,9 @@ QT_BEGIN_NAMESPACE
"a", "aa", "aaa", "aaaa", ... "a", "aa", "aaa", "aaaa", ...
*/ */
static uint hash(const uchar *p, int n) static uint hash(const uchar *p, int n, uint seed)
{ {
uint h = 0; uint h = seed;
while (n--) { while (n--) {
h = (h << 4) + *p++; h = (h << 4) + *p++;
@ -96,9 +96,9 @@ static uint hash(const uchar *p, int n)
return h; return h;
} }
static uint hash(const QChar *p, int n) static uint hash(const QChar *p, int n, uint seed)
{ {
uint h = 0; uint h = seed;
while (n--) { while (n--) {
h = (h << 4) + (*p++).unicode(); h = (h << 4) + (*p++).unicode();
@ -108,25 +108,25 @@ static uint hash(const QChar *p, int n)
return h; return h;
} }
uint qHash(const QByteArray &key) uint qHash(const QByteArray &key, uint seed)
{ {
return hash(reinterpret_cast<const uchar *>(key.constData()), key.size()); return hash(reinterpret_cast<const uchar *>(key.constData()), key.size(), seed);
} }
uint qHash(const QString &key) uint qHash(const QString &key, uint seed)
{ {
return hash(key.unicode(), key.size()); return hash(key.unicode(), key.size(), seed);
} }
uint qHash(const QStringRef &key) uint qHash(const QStringRef &key, uint seed)
{ {
return hash(key.unicode(), key.size()); return hash(key.unicode(), key.size(), seed);
} }
uint qHash(const QBitArray &bitArray) uint qHash(const QBitArray &bitArray, uint seed)
{ {
int m = bitArray.d.size() - 1; int m = bitArray.d.size() - 1;
uint result = hash(reinterpret_cast<const uchar *>(bitArray.d.constData()), qMax(0, m)); uint result = hash(reinterpret_cast<const uchar *>(bitArray.d.constData()), qMax(0, m), seed);
// deal with the last 0 to 7 bits manually, because we can't trust that // deal with the last 0 to 7 bits manually, because we can't trust that
// the padding is initialized to 0 in bitArray.d // the padding is initialized to 0 in bitArray.d
@ -614,17 +614,16 @@ void QHashData::checkSanity()
Returns the hash value for the \a key. Returns the hash value for the \a key.
*/ */
/*! \fn uint qHash(const QByteArray &key) /*! \fn uint qHash(const QByteArray &key, uint seed = 0)
\fn uint qHash(const QBitArray &key) \fn uint qHash(const QBitArray &key, uint seed = 0)
\fn uint qHash(const QString &key, uint seed = 0)
\fn uint qHash(const QStringRef &key, uint seed = 0)
\relates QHash \relates QHash
\since 5.0
Returns the hash value for the \a key. Returns the hash value for the \a key, using \a seed to
*/ seed the calculation.
/*! \fn uint qHash(const QString &key)
\relates QHash
Returns the hash value for the \a key.
*/ */
/*! \fn uint qHash(const T *key) /*! \fn uint qHash(const T *key)
@ -656,8 +655,7 @@ void QHashData::checkSanity()
key. With QHash, the items are arbitrarily ordered. key. With QHash, the items are arbitrarily ordered.
\li The key type of a QMap must provide operator<(). The key \li The key type of a QMap must provide operator<(). The key
type of a QHash must provide operator==() and a global type of a QHash must provide operator==() and a global
hash function called qHash() (see the related non-member hash function called qHash() (see \l{qHash}).
functions).
\endlist \endlist
Here's an example QHash with QString keys and \c int values: Here's an example QHash with QString keys and \c int values:
@ -702,6 +700,15 @@ void QHashData::checkSanity()
To avoid this problem, replace \c hash[i] with \c hash.value(i) To avoid this problem, replace \c hash[i] with \c hash.value(i)
in the code above. in the code above.
Internally, QHash uses a hash table to perform lookups. Unlike Qt
3's \c QDict class, which needed to be initialized with a prime
number, QHash's hash table automatically grows and shrinks to
provide fast lookups without wasting too much memory. You can
still control the size of the hash table by calling reserve() if
you already know approximately how many items the QHash will
contain, but this isn't necessary to obtain good performance. You
can also call capacity() to retrieve the hash table's size.
If you want to navigate through all the (key, value) pairs stored If you want to navigate through all the (key, value) pairs stored
in a QHash, you can use an iterator. QHash provides both in a QHash, you can use an iterator. QHash provides both
\l{Java-style iterators} (QHashIterator and QMutableHashIterator) \l{Java-style iterators} (QHashIterator and QMutableHashIterator)
@ -751,21 +758,15 @@ void QHashData::checkSanity()
QHash's key and value data types must be \l{assignable data QHash's key and value data types must be \l{assignable data
types}. You cannot, for example, store a QWidget as a value; types}. You cannot, for example, store a QWidget as a value;
instead, store a QWidget *. In addition, QHash's key type must instead, store a QWidget *.
provide operator==(), and there must also be a global qHash()
function that returns a hash value for an argument of the key's
type.
Here's a list of the C++ and Qt types that can serve as keys in a \target qHash
QHash: any integer type (char, unsigned long, etc.), any pointer \section2 The qHash() hashing function
type, QChar, QString, and QByteArray. For all of these, the \c
<QHash> header defines a qHash() function that computes an
adequate hash value. If you want to use other types as the key,
make sure that you provide operator==() and a qHash()
implementation.
Example: A QHash's key type has additional requirements other than being an
\snippet doc/src/snippets/code/src_corelib_tools_qhash.cpp 13 assignable data type: it must provide operator==(), and there must also be
a global qHash() function that returns a hash value for an argument of the
key's type.
The qHash() function computes a numeric value based on a key. It The qHash() function computes a numeric value based on a key. It
can use any algorithm imaginable, as long as it always returns can use any algorithm imaginable, as long as it always returns
@ -775,19 +776,56 @@ void QHashData::checkSanity()
attempt to return different hash values for different keys to the attempt to return different hash values for different keys to the
largest extent possible. largest extent possible.
For a key type \c{K}, the qHash function must have one of these signatures:
\code
uint qHash(K key);
uint qHash(const K &key);
uint qHash(K key, uint seed);
uint qHash(const K &key, uint seed);
\endcode
The two-arguments overloads take an unsigned integer that should be used to
seed the calculation of the hash function. This seed is provided by QHash
in order to prevent a family of \l{algorithmic complexity attacks}. If both
a one-argument and a two-arguments overload are defined for a key type,
the latter is used by QHash (note that you can simply define a
two-arguments version, and use a default value for the seed parameter).
Here's a partial list of the C++ and Qt types that can serve as keys in a
QHash: any integer type (char, unsigned long, etc.), any pointer type,
QChar, QString, and QByteArray. For all of these, the \c <QHash> header
defines a qHash() function that computes an adequate hash value. Many other
Qt classes also declare a qHash overload for their type; please refer to
the documentation of each class.
If you want to use other types as the key, make sure that you provide
operator==() and a qHash() implementation.
Example:
\snippet doc/src/snippets/code/src_corelib_tools_qhash.cpp 13
In the example above, we've relied on Qt's global qHash(const In the example above, we've relied on Qt's global qHash(const
QString &) to give us a hash value for the employee's name, and QString &, uint) to give us a hash value for the employee's name, and
XOR'ed this with the day they were born to help produce unique XOR'ed this with the day they were born to help produce unique
hashes for people with the same name. hashes for people with the same name.
Internally, QHash uses a hash table to perform lookups. Unlike Qt \section2 Algorithmic complexity attacks
3's \c QDict class, which needed to be initialized with a prime
number, QHash's hash table automatically grows and shrinks to All hash tables are vulnerable to a particular class of denial of service
provide fast lookups without wasting too much memory. You can attacks, in which the attacker carefully pre-computes a set of different
still control the size of the hash table by calling reserve() if keys that are going to be hashed in the same bucket of a hash table (or
you already know approximately how many items the QHash will even have the very same hash value). The attack aims at getting the
contain, but this isn't necessary to obtain good performance. You worst-case algorithmic behavior (O(n) instead of amortized O(1), see
can also call capacity() to retrieve the hash table's size. \l{Algorithmic Complexity} for the details) when the data is fed into the
table.
In order to avoid this worst-case behavior, the calculation of the hash
value done by qHash() can be salted by a random seed, that nullifies the
attack's extent. This seed is automatically generated by QHash once per
process, and then passed by QHash as the second argument of the
two-arguments overload of the qHash() function.
\sa QHashIterator, QMutableHashIterator, QMap, QSet \sa QHashIterator, QMutableHashIterator, QMap, QSet
*/ */

View File

@ -84,10 +84,10 @@ inline uint qHash(quint64 key)
} }
inline uint qHash(qint64 key) { return qHash(quint64(key)); } inline uint qHash(qint64 key) { return qHash(quint64(key)); }
inline uint qHash(QChar key) { return qHash(key.unicode()); } inline uint qHash(QChar key) { return qHash(key.unicode()); }
Q_CORE_EXPORT uint qHash(const QByteArray &key); Q_CORE_EXPORT uint qHash(const QByteArray &key, uint seed = 0);
Q_CORE_EXPORT uint qHash(const QString &key); Q_CORE_EXPORT uint qHash(const QString &key, uint seed = 0);
Q_CORE_EXPORT uint qHash(const QStringRef &key); Q_CORE_EXPORT uint qHash(const QStringRef &key, uint seed = 0);
Q_CORE_EXPORT uint qHash(const QBitArray &key); Q_CORE_EXPORT uint qHash(const QBitArray &key, uint seed = 0);
#if defined(Q_CC_MSVC) #if defined(Q_CC_MSVC)
#pragma warning( push ) #pragma warning( push )
@ -108,6 +108,8 @@ template <typename T1, typename T2> inline uint qHash(const QPair<T1, T2> &key)
return ((h1 << 16) | (h1 >> 16)) ^ h2; return ((h1 << 16) | (h1 >> 16)) ^ h2;
} }
template<typename T> inline uint qHash(const T &t, uint) { return qHash(t); }
struct Q_CORE_EXPORT QHashData struct Q_CORE_EXPORT QHashData
{ {
struct Node { struct Node {
@ -857,7 +859,7 @@ Q_OUTOFLINE_TEMPLATE typename QHash<Key, T>::Node **QHash<Key, T>::findNode(cons
uint h = 0; uint h = 0;
if (d->numBuckets || ahp) { if (d->numBuckets || ahp) {
h = qHash(akey); h = qHash(akey, 0);
if (ahp) if (ahp)
*ahp = h; *ahp = h;
} }

View File

@ -47,6 +47,7 @@
#include <QtCore/qvariant.h> #include <QtCore/qvariant.h>
#include <QtCore/qstring.h> #include <QtCore/qstring.h>
#include <QtDBus/qdbusmacros.h> #include <QtDBus/qdbusmacros.h>
#include <QtCore/qhash.h>
#ifndef QT_NO_DBUS #ifndef QT_NO_DBUS
@ -55,9 +56,6 @@ QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
// defined in qhash.cpp
Q_CORE_EXPORT uint qHash(const QString &key);
class Q_DBUS_EXPORT QDBusObjectPath class Q_DBUS_EXPORT QDBusObjectPath
{ {
QString m_path; QString m_path;

View File

@ -73,6 +73,7 @@ private slots:
void noNeedlessRehashes(); void noNeedlessRehashes();
void const_shared_null(); void const_shared_null();
void twoArguments_qHash();
}; };
struct Foo { struct Foo {
@ -1203,5 +1204,101 @@ void tst_QHash::const_shared_null()
QVERIFY(!hash2.isDetached()); QVERIFY(!hash2.isDetached());
} }
// This gets set to != 0 in wrong qHash overloads
static int wrongqHashOverload = 0;
struct OneArgumentQHashStruct1 {};
bool operator==(const OneArgumentQHashStruct1 &, const OneArgumentQHashStruct1 &) { return false; }
uint qHash(OneArgumentQHashStruct1) { return 0; }
struct OneArgumentQHashStruct2 {};
bool operator==(const OneArgumentQHashStruct2 &, const OneArgumentQHashStruct2 &) { return false; }
uint qHash(const OneArgumentQHashStruct2 &) { return 0; }
struct OneArgumentQHashStruct3 {};
bool operator==(const OneArgumentQHashStruct3 &, const OneArgumentQHashStruct3 &) { return false; }
uint qHash(OneArgumentQHashStruct3) { return 0; }
uint qHash(OneArgumentQHashStruct3 &, uint) { wrongqHashOverload = 1; return 0; }
struct OneArgumentQHashStruct4 {};
bool operator==(const OneArgumentQHashStruct4 &, const OneArgumentQHashStruct4 &) { return false; }
uint qHash(const OneArgumentQHashStruct4 &) { return 0; }
uint qHash(OneArgumentQHashStruct4 &, uint) { wrongqHashOverload = 1; return 0; }
struct TwoArgumentsQHashStruct1 {};
bool operator==(const TwoArgumentsQHashStruct1 &, const TwoArgumentsQHashStruct1 &) { return false; }
uint qHash(const TwoArgumentsQHashStruct1 &) { wrongqHashOverload = 1; return 0; }
uint qHash(const TwoArgumentsQHashStruct1 &, uint) { return 0; }
struct TwoArgumentsQHashStruct2 {};
bool operator==(const TwoArgumentsQHashStruct2 &, const TwoArgumentsQHashStruct2 &) { return false; }
uint qHash(TwoArgumentsQHashStruct2) { wrongqHashOverload = 1; return 0; }
uint qHash(const TwoArgumentsQHashStruct2 &, uint) { return 0; }
struct TwoArgumentsQHashStruct3 {};
bool operator==(const TwoArgumentsQHashStruct3 &, const TwoArgumentsQHashStruct3 &) { return false; }
uint qHash(const TwoArgumentsQHashStruct3 &) { wrongqHashOverload = 1; return 0; }
uint qHash(TwoArgumentsQHashStruct3, uint) { return 0; }
struct TwoArgumentsQHashStruct4 {};
bool operator==(const TwoArgumentsQHashStruct4 &, const TwoArgumentsQHashStruct4 &) { return false; }
uint qHash(TwoArgumentsQHashStruct4) { wrongqHashOverload = 1; return 0; }
uint qHash(TwoArgumentsQHashStruct4, uint) { return 0; }
/*!
\internal
Check that QHash picks up the right overload.
The best one, for a type T, is the two-args version of qHash:
either uint qHash(T, uint) or uint qHash(const T &, uint).
If neither of these exists, then one between
uint qHash(T) or uint qHash(const T &) must exist
(and it gets selected instead).
*/
void tst_QHash::twoArguments_qHash()
{
QHash<OneArgumentQHashStruct1, int> oneArgHash1;
OneArgumentQHashStruct1 oneArgObject1;
oneArgHash1[oneArgObject1] = 1;
QCOMPARE(wrongqHashOverload, 0);
QHash<OneArgumentQHashStruct2, int> oneArgHash2;
OneArgumentQHashStruct2 oneArgObject2;
oneArgHash2[oneArgObject2] = 1;
QCOMPARE(wrongqHashOverload, 0);
QHash<OneArgumentQHashStruct3, int> oneArgHash3;
OneArgumentQHashStruct3 oneArgObject3;
oneArgHash3[oneArgObject3] = 1;
QCOMPARE(wrongqHashOverload, 0);
QHash<OneArgumentQHashStruct4, int> oneArgHash4;
OneArgumentQHashStruct4 oneArgObject4;
oneArgHash4[oneArgObject4] = 1;
QCOMPARE(wrongqHashOverload, 0);
QHash<TwoArgumentsQHashStruct1, int> twoArgsHash1;
TwoArgumentsQHashStruct1 twoArgsObject1;
twoArgsHash1[twoArgsObject1] = 1;
QCOMPARE(wrongqHashOverload, 0);
QHash<TwoArgumentsQHashStruct2, int> twoArgsHash2;
TwoArgumentsQHashStruct2 twoArgsObject2;
twoArgsHash2[twoArgsObject2] = 1;
QCOMPARE(wrongqHashOverload, 0);
QHash<TwoArgumentsQHashStruct3, int> twoArgsHash3;
TwoArgumentsQHashStruct3 twoArgsObject3;
twoArgsHash3[twoArgsObject3] = 1;
QCOMPARE(wrongqHashOverload, 0);
QHash<TwoArgumentsQHashStruct4, int> twoArgsHash4;
TwoArgumentsQHashStruct4 twoArgsObject4;
twoArgsHash4[twoArgsObject4] = 1;
QCOMPARE(wrongqHashOverload, 0);
}
QTEST_APPLESS_MAIN(tst_QHash) QTEST_APPLESS_MAIN(tst_QHash)
#include "tst_qhash.moc" #include "tst_qhash.moc"