Fix OOM crashes for huge json documents

Check all places where we reallocate our internal data structure
and return a DocumentTooLarge parse error if we can't get enough
memory.

Change-Id: I006d0170d941837220c7dad0508571b68e2cbfd7
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Kati Kankaanpaa <kati.kankaanpaa@qt.io>
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
This commit is contained in:
Lars Knoll 2016-11-02 09:37:42 +01:00
parent 4b6784b49c
commit 15df60239d
2 changed files with 76 additions and 7 deletions

View File

@ -385,6 +385,8 @@ bool Parser::parseObject()
}
int objectOffset = reserveSpace(sizeof(QJsonPrivate::Object));
if (objectOffset < 0)
return false;
BEGIN << "parseObject pos=" << objectOffset << current << json;
ParsedObject parsedObject(this, objectOffset);
@ -417,6 +419,9 @@ bool Parser::parseObject()
if (parsedObject.offsets.size()) {
int tableSize = parsedObject.offsets.size()*sizeof(uint);
table = reserveSpace(tableSize);
if (table < 0)
return false;
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
memcpy(data + table, parsedObject.offsets.constData(), tableSize);
#else
@ -446,6 +451,8 @@ bool Parser::parseObject()
bool Parser::parseMember(int baseOffset)
{
int entryOffset = reserveSpace(sizeof(QJsonPrivate::Entry));
if (entryOffset < 0)
return false;
BEGIN << "parseMember pos=" << entryOffset;
bool latin1;
@ -469,6 +476,42 @@ bool Parser::parseMember(int baseOffset)
return true;
}
namespace {
struct ValueArray {
static const int prealloc = 128;
ValueArray() : data(stackValues), alloc(prealloc), size(0) {}
~ValueArray() { if (data != stackValues) free(data); }
inline bool grow() {
alloc *= 2;
if (data == stackValues) {
QJsonPrivate::Value *newValues = static_cast<QJsonPrivate::Value *>(malloc(alloc*sizeof(QJsonPrivate::Value)));
if (!newValues)
return false;
memcpy(newValues, data, size*sizeof(QJsonPrivate::Value));
data = newValues;
} else {
data = static_cast<QJsonPrivate::Value *>(realloc(data, alloc*sizeof(QJsonPrivate::Value)));
if (!data)
return false;
}
return true;
}
bool append(const QJsonPrivate::Value &v) {
if (alloc == size && !grow())
return false;
data[size] = v;
++size;
return true;
}
QJsonPrivate::Value stackValues[prealloc];
QJsonPrivate::Value *data;
int alloc;
int size;
};
}
/*
array = begin-array [ value *( value-separator value ) ] end-array
*/
@ -482,8 +525,10 @@ bool Parser::parseArray()
}
int arrayOffset = reserveSpace(sizeof(QJsonPrivate::Array));
if (arrayOffset < 0)
return false;
QVarLengthArray<QJsonPrivate::Value, 64> values;
ValueArray values;
if (!eatSpace()) {
lastError = QJsonParseError::UnterminatedArray;
@ -496,7 +541,10 @@ bool Parser::parseArray()
QJsonPrivate::Value val;
if (!parseValue(&val, arrayOffset))
return false;
values.append(val);
if (!values.append(val)) {
lastError = QJsonParseError::DocumentTooLarge;
return false;
}
char token = nextToken();
if (token == EndArray)
break;
@ -510,20 +558,22 @@ bool Parser::parseArray()
}
}
DEBUG << "size =" << values.size();
DEBUG << "size =" << values.size;
int table = arrayOffset;
// finalize the object
if (values.size()) {
int tableSize = values.size()*sizeof(QJsonPrivate::Value);
if (values.size) {
int tableSize = values.size*sizeof(QJsonPrivate::Value);
table = reserveSpace(tableSize);
memcpy(data + table, values.constData(), tableSize);
if (table < 0)
return false;
memcpy(data + table, values.data, tableSize);
}
QJsonPrivate::Array *a = (QJsonPrivate::Array *)(data + arrayOffset);
a->tableOffset = table - arrayOffset;
a->size = current - arrayOffset;
a->is_object = false;
a->length = values.size();
a->length = values.size;
DEBUG << "current=" << current;
END;
@ -732,6 +782,8 @@ bool Parser::parseNumber(QJsonPrivate::Value *val, int baseOffset)
}
int pos = reserveSpace(sizeof(double));
if (pos < 0)
return false;
qToLittleEndian(ui, reinterpret_cast<uchar *>(data + pos));
if (current - baseOffset >= Value::MaxSize) {
lastError = QJsonParseError::DocumentTooLarge;
@ -850,6 +902,9 @@ bool Parser::parseString(bool *latin1)
// try to write out a latin1 string
int stringPos = reserveSpace(2);
if (stringPos < 0)
return false;
BEGIN << "parse string stringPos=" << stringPos << json;
while (json < end) {
uint ch = 0;
@ -872,6 +927,8 @@ bool Parser::parseString(bool *latin1)
break;
}
int pos = reserveSpace(1);
if (pos < 0)
return false;
DEBUG << " " << ch << (char)ch;
data[pos] = (uchar)ch;
}
@ -887,6 +944,8 @@ bool Parser::parseString(bool *latin1)
// write string length
*(QJsonPrivate::qle_ushort *)(data + stringPos) = ushort(current - outStart - sizeof(ushort));
int pos = reserveSpace((4 - current) & 3);
if (pos < 0)
return false;
while (pos & 3)
data[pos++] = 0;
END;
@ -916,10 +975,14 @@ bool Parser::parseString(bool *latin1)
}
if (QChar::requiresSurrogates(ch)) {
int pos = reserveSpace(4);
if (pos < 0)
return false;
*(QJsonPrivate::qle_ushort *)(data + pos) = QChar::highSurrogate(ch);
*(QJsonPrivate::qle_ushort *)(data + pos + 2) = QChar::lowSurrogate(ch);
} else {
int pos = reserveSpace(2);
if (pos < 0)
return false;
*(QJsonPrivate::qle_ushort *)(data + pos) = (ushort)ch;
}
}
@ -933,6 +996,8 @@ bool Parser::parseString(bool *latin1)
// write string length
*(QJsonPrivate::qle_int *)(data + stringPos) = (current - outStart - sizeof(int))/2;
int pos = reserveSpace((4 - current) & 3);
if (pos < 0)
return false;
while (pos & 3)
data[pos++] = 0;
END;

View File

@ -102,6 +102,10 @@ private:
if (current + space >= dataLength) {
dataLength = 2*dataLength + space;
data = (char *)realloc(data, dataLength);
if (!data) {
lastError = QJsonParseError::DocumentTooLarge;
return -1;
}
}
int pos = current;
current += space;