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

View File

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