diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2012-01-03 23:12:42 +0100 |
---|---|---|
committer | Jamey Hicks <jamey.hicks@nokia.com> | 2012-01-04 00:40:09 +0100 |
commit | fe48ef62821b14eff9c62451e0943d14cba2d5ae (patch) | |
tree | 70013faf6fec9835b0b38ba1c9c418aca5a35547 | |
parent | 2e605b0d45b0e72cb6060af042417b38668965c3 (diff) |
Add support for validating the binary data
JsonDocument::isValid() will return false
if the binary data is corrupted in a way that
would cause the application to crash.
Change-Id: I065c1562cd99d6bfc322dd3879c50232c19b170d
Reviewed-by: Jamey Hicks <jamey.hicks@nokia.com>
-rw-r--r-- | src/qjson.cpp | 87 | ||||
-rw-r--r-- | src/qjson_p.h | 31 | ||||
-rw-r--r-- | src/qjsondocument.cpp | 17 | ||||
-rw-r--r-- | src/qjsondocument.h | 2 | ||||
-rw-r--r-- | src/qjsonparser.cpp | 8 | ||||
-rw-r--r-- | tests/auto/tst_qtjson.cpp | 24 |
6 files changed, 161 insertions, 8 deletions
diff --git a/src/qjson.cpp b/src/qjson.cpp index 46e7599..06aaad1 100644 --- a/src/qjson.cpp +++ b/src/qjson.cpp @@ -114,3 +114,90 @@ void Data::compact() alloc = alloc; compactionCounter = 0; } + +void Data::validate() +{ + if (valid != Unchecked) + return; + + if (header->tag != QBJS_Tag || header->version != 1) { + valid = Invalid; + return; + } + + bool res = false; + if (header->root()->is_object) + res = static_cast<Object *>(header->root())->isValid(); + else + res = static_cast<Array *>(header->root())->isValid(); + + valid = res ? Validated : Invalid; +} + +bool Object::isValid() const +{ + if (tableOffset + length*sizeof(offset) > size) + return false; + + for (int i = 0; i < length; ++i) { + offset entryOffset = table()[i]; + if (entryOffset + sizeof(Entry) >= tableOffset) + return false; + Entry *e = entryAt(i); + int s = e->size(); + if (table()[i] + s > tableOffset) + return false; + if (!e->value.isValid(this)) + return false; + } + return true; +} + + +bool Array::isValid() const +{ + if (tableOffset + length*sizeof(offset) > size) + return false; + + for (int i = 0; i < length; ++i) { + if (!at(i).isValid(this)) + return false; + } + return true; +} + +bool Value::isValid(const Base *b) const +{ + int offset = 0; + switch (type) { + case NumberValue: + if (latinOrIntValue) + break; + // fall through + case StringValue: + case ArrayValue: + case ObjectValue: + offset = val; + break; + case NullValue: + case BooleanValue: + default: + break; + } + + if (!offset) + return true; + if (offset + sizeof(uint) >= b->tableOffset) + return false; + + int s = usedStorage(b); + if (!s) + return true; + if (s < 0 || offset + s > (int)b->tableOffset) + return false; + if (type == ArrayValue) + return static_cast<Array *>(objectOrArray(b))->isValid(); + if (type == ObjectValue) + return static_cast<Object *>(objectOrArray(b))->isValid(); + return true; +} diff --git a/src/qjson_p.h b/src/qjson_p.h index df6c48e..7f773f2 100644 --- a/src/qjson_p.h +++ b/src/qjson_p.h @@ -151,9 +151,14 @@ static inline void copyString(char *dest, const QString &str, bool compress) const ushort *uc = (const ushort *)str.unicode(); for (int i = 0; i < str.length(); ++i) *d++ = uc[i]; + while ((quintptr)d & 0x3) + *d++ = 0; } else { *((int *)dest) = str.length(); - memcpy(dest + sizeof(int), str.constData(), str.length()*sizeof(ushort)); + ushort *d = (ushort *)(dest + sizeof(int)); + memcpy(d, str.constData(), str.length()*sizeof(ushort)); + if (str.length() & 1) + d[str.length()] = 0; } } @@ -338,6 +343,8 @@ struct Object : public Base return reinterpret_cast<Entry *>(((char *)this) + table()[i]); } inline int indexOf(const QString &key); + + bool isValid() const; }; @@ -345,6 +352,8 @@ struct Array : public Base { inline Value at(int i) const; inline Value &operator [](int i); + + bool isValid() const; }; @@ -367,7 +376,7 @@ struct Value inline char *data(const Base *b) const { return ((char *)b) + val; } - inline int usedStorage(Base *b) const; + inline int usedStorage(const Base *b) const; bool toBoolean() const; double toNumber(const Base *b) const; @@ -377,6 +386,8 @@ struct Value Latin1String asLatin1String(const Base *b) const; Base *objectOrArray(const Base *b) const; + bool isValid(const Base *b) const; + inline void setNull() { val = 0; latinOrIntValue = 0; @@ -427,6 +438,7 @@ inline Value &Array::operator [](int i) } + struct Entry { Value value; ushort *keyData() const { return (ushort *)((const char *)this + sizeof(Entry) + sizeof(int)); } @@ -485,7 +497,6 @@ inline int Object::indexOf(const QString &key) return -1; } - struct Header { uint tag; // 'qbjs' uint version; // 1 @@ -493,7 +504,7 @@ struct Header { }; -inline int Value::usedStorage(Base *b) const +inline int Value::usedStorage(const Base *b) const { int s = 0; switch (type) { @@ -586,13 +597,19 @@ inline Base *Value::objectOrArray(const Base *b) const } struct Data { + enum Validation { + Unchecked, + Validated, + Invalid + }; + inline Data(char *raw, int a) - : alloc(a), compactionCounter(0), rawData(raw) + : alloc(a), compactionCounter(0), valid(Unchecked), rawData(raw) { ref.store(0); } inline Data(int reserved, ValueType valueType) - : compactionCounter(0), rawData(0) + : compactionCounter(0), valid(Validated), rawData(0) { ref.store(0); @@ -612,6 +629,7 @@ struct Data { QBasicAtomicInt ref; int alloc; int compactionCounter; + Validation valid; union { char *rawData; Header *header; @@ -648,6 +666,7 @@ struct Data { } void compact(); + void validate(); }; } diff --git a/src/qjsondocument.cpp b/src/qjsondocument.cpp index 33a9806..a8776dd 100644 --- a/src/qjsondocument.cpp +++ b/src/qjsondocument.cpp @@ -274,3 +274,20 @@ bool JsonDocument::operator!=(const JsonDocument &other) const { return !(*this == other); } + +bool JsonDocument::isValid() +{ + if (!d) + return false; + + if (d->valid == Data::Unchecked) + // Unchecked, check for validity + d->validate(); + + if (d->valid != Data::Validated) { + if (!d->ref.deref()) + delete d; + d = 0; + } + return d != 0; +} diff --git a/src/qjsondocument.h b/src/qjsondocument.h index 5f6b11b..c7885cc 100644 --- a/src/qjsondocument.h +++ b/src/qjsondocument.h @@ -80,6 +80,8 @@ public: bool operator==(const JsonDocument &other) const; bool operator!=(const JsonDocument &other) const; + bool isValid(); + private: friend class Data; friend class JsonValue; diff --git a/src/qjsonparser.cpp b/src/qjsonparser.cpp index 544e2c0..b72cdaa 100644 --- a/src/qjsonparser.cpp +++ b/src/qjsonparser.cpp @@ -674,7 +674,9 @@ bool QJsonParser::parseString(bool *latin1) if (*latin1) { // write string length *(ushort *)(data + stringPos) = current - outStart - sizeof(ushort); - reserveSpace((4 - current) & 3); + int pos = reserveSpace((4 - current) & 3); + while (pos & 3) + data[pos++] = 0; END; return true; } @@ -712,7 +714,9 @@ bool QJsonParser::parseString(bool *latin1) // write string length *(int *)(data + stringPos) = (current - outStart - sizeof(int))/2; - reserveSpace((4 - current) & 3); + int pos = reserveSpace((4 - current) & 3); + while (pos & 3) + data[pos++] = 0; END; return true; } diff --git a/tests/auto/tst_qtjson.cpp b/tests/auto/tst_qtjson.cpp index d306529..acd0e33 100644 --- a/tests/auto/tst_qtjson.cpp +++ b/tests/auto/tst_qtjson.cpp @@ -88,6 +88,8 @@ private Q_SLOTS: void compactArray(); void compactObject(); + + void validation(); }; TestQtJson::TestQtJson(QObject *parent) : QObject(parent) @@ -1026,6 +1028,28 @@ void TestQtJson::compactObject() } +void TestQtJson::validation() +{ + // this basically tests that we don't crash on corrupt data + QFile file(QLatin1String("test.json")); + file.open(QFile::ReadOnly); + QByteArray testJson = file.readAll(); + + JsonDocument doc = JsonDocument::fromJson(testJson); + + QByteArray binary = doc.toBinaryData(); + + // only test the first 1000 bytes. Testing the full file takes too long + for (int i = 0; i < 1000; ++i) { + QByteArray corrupted = binary; + corrupted[i] = 0xff; + JsonDocument doc = JsonDocument::fromBinaryData(corrupted); + if (!doc.isValid()) + continue; + QByteArray json = doc.toJson(); + } +} + QTEST_MAIN(TestQtJson) |