Improve the validation algorithm for binary JSON

Add better boundary checks and catch (hopefully all)
cases where invalid binary JSON could cause crashes.

Change-Id: I206510b7c5e3ba953802a5f46645878e65704ecc
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Lars Knoll 2016-11-02 14:10:47 +01:00 committed by Giuseppe D'Angelo
parent 3c2cb87de2
commit bfaa8925d5
31 changed files with 57 additions and 19 deletions

View File

@ -129,10 +129,12 @@ bool Data::valid() const
return false;
bool res = false;
if (header->root()->is_object)
res = static_cast<Object *>(header->root())->isValid();
Base *root = header->root();
int maxSize = alloc - sizeof(Header);
if (root->is_object)
res = static_cast<Object *>(root)->isValid(maxSize);
else
res = static_cast<Array *>(header->root())->isValid();
res = static_cast<Array *>(root)->isValid(maxSize);
return res;
}
@ -195,9 +197,9 @@ int Object::indexOf(const QString &key, bool *exists)
return min;
}
bool Object::isValid() const
bool Object::isValid(int maxSize) const
{
if (tableOffset + length*sizeof(offset) > size)
if (size > (uint)maxSize || tableOffset + length*sizeof(offset) > size)
return false;
QString lastKey;
@ -206,8 +208,7 @@ bool Object::isValid() const
if (entryOffset + sizeof(Entry) >= tableOffset)
return false;
Entry *e = entryAt(i);
int s = e->size();
if (table()[i] + s > tableOffset)
if (!e->isValid(tableOffset - table()[i]))
return false;
QString key = e->key();
if (key < lastKey)
@ -221,9 +222,9 @@ bool Object::isValid() const
bool Array::isValid() const
bool Array::isValid(int maxSize) const
{
if (tableOffset + length*sizeof(offset) > size)
if (size > (uint)maxSize || tableOffset + length*sizeof(offset) > size)
return false;
for (uint i = 0; i < length; ++i) {
@ -323,12 +324,12 @@ bool Value::isValid(const Base *b) const
int s = usedStorage(b);
if (!s)
return true;
if (s < 0 || offset + s > (int)b->tableOffset)
if (s < 0 || s > (int)b->tableOffset - offset)
return false;
if (type == QJsonValue::Array)
return static_cast<Array *>(base(b))->isValid();
return static_cast<Array *>(base(b))->isValid(s);
if (type == QJsonValue::Object)
return static_cast<Object *>(base(b))->isValid();
return static_cast<Object *>(base(b))->isValid(s);
return true;
}

View File

@ -302,12 +302,19 @@ public:
String(const char *data) { d = (Data *)data; }
struct Data {
qle_int length;
qle_uint length;
qle_ushort utf16[1];
};
Data *d;
int byteSize() const { return sizeof(uint) + sizeof(ushort) * d->length; }
bool isValid(int maxSize) const {
// Check byteSize() <= maxSize, avoiding integer overflow
maxSize -= sizeof(uint);
return maxSize >= 0 && uint(d->length) <= maxSize / sizeof(ushort);
}
inline String &operator=(const QString &str)
{
d->length = str.length();
@ -376,11 +383,16 @@ public:
Latin1String(const char *data) { d = (Data *)data; }
struct Data {
qle_short length;
qle_ushort length;
char latin1[1];
};
Data *d;
int byteSize() const { return sizeof(ushort) + sizeof(char)*(d->length); }
bool isValid(int maxSize) const {
return byteSize() <= maxSize;
}
inline Latin1String &operator=(const QString &str)
{
int len = d->length = str.length();
@ -567,7 +579,7 @@ public:
}
int indexOf(const QString &key, bool *exists);
bool isValid() const;
bool isValid(int maxSize) const;
};
@ -577,7 +589,7 @@ public:
inline Value at(int i) const;
inline Value &operator [](int i);
bool isValid() const;
bool isValid(int maxSize) const;
};
@ -631,12 +643,12 @@ public:
// key
// value data follows key
int size() const {
uint size() const {
int s = sizeof(Entry);
if (value.latinKey)
s += sizeof(ushort) + qFromLittleEndian(*(ushort *) ((const char *)this + sizeof(Entry)));
s += shallowLatin1Key().byteSize();
else
s += sizeof(uint) + sizeof(ushort)*qFromLittleEndian(*(int *) ((const char *)this + sizeof(Entry)));
s += shallowKey().byteSize();
return alignedSize(s);
}
@ -662,6 +674,15 @@ public:
return shallowKey().toString();
}
bool isValid(int maxSize) const {
if (maxSize < (int)sizeof(Entry))
return false;
maxSize -= sizeof(Entry);
if (value.latinKey)
return shallowLatin1Key().isValid(maxSize);
return shallowKey().isValid(maxSize);
}
bool operator ==(const QString &key) const;
inline bool operator !=(const QString &key) const { return !operator ==(key); }
inline bool operator >=(const QString &key) const;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -103,6 +103,7 @@ private Q_SLOTS:
void fromBinary();
void toAndFromBinary_data();
void toAndFromBinary();
void invalidBinaryData();
void parseNumbers();
void parseStrings();
void parseDuplicateKeys();
@ -1779,6 +1780,21 @@ void tst_QtJson::toAndFromBinary()
QVERIFY(doc == outdoc);
}
void tst_QtJson::invalidBinaryData()
{
QDir dir(testDataDir + "/invalidBinaryData");
QFileInfoList files = dir.entryInfoList();
for (int i = 0; i < files.size(); ++i) {
if (!files.at(i).isFile())
continue;
QFile file(files.at(i).filePath());
file.open(QIODevice::ReadOnly);
QByteArray bytes = file.readAll();
QJsonDocument document = QJsonDocument::fromRawData(bytes.constData(), bytes.size());
QVERIFY(document.isNull());
}
}
void tst_QtJson::parseNumbers()
{
{