summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2012-01-03 23:12:42 +0100
committerJamey Hicks <jamey.hicks@nokia.com>2012-01-04 00:40:09 +0100
commitfe48ef62821b14eff9c62451e0943d14cba2d5ae (patch)
tree70013faf6fec9835b0b38ba1c9c418aca5a35547
parent2e605b0d45b0e72cb6060af042417b38668965c3 (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.cpp87
-rw-r--r--src/qjson_p.h31
-rw-r--r--src/qjsondocument.cpp17
-rw-r--r--src/qjsondocument.h2
-rw-r--r--src/qjsonparser.cpp8
-rw-r--r--tests/auto/tst_qtjson.cpp24
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)