/* Copyright 2012 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault #include "mongo/platform/basic.h" #include "mongo/base/data_view.h" #include "mongo/bson/bson_validate.h" #include "mongo/db/jsobj.h" #include "mongo/platform/random.h" #include "mongo/unittest/unittest.h" #include "mongo/util/log.h" namespace { using namespace mongo; using std::unique_ptr; using std::endl; void appendInvalidStringElement(const char* fieldName, BufBuilder* bb) { // like a BSONObj string, but without a NUL terminator. bb->appendChar(String); bb->appendStr(fieldName, /*withNUL*/ true); bb->appendNum(4); bb->appendStr("asdf", /*withNUL*/ false); } TEST(BSONValidate, Basic) { BSONObj x; ASSERT_TRUE(x.valid(BSONVersion::kLatest)); x = BSON("x" << 1); ASSERT_TRUE(x.valid(BSONVersion::kLatest)); } TEST(BSONValidate, RandomData) { PseudoRandom r(17); int numValid = 0; int numToRun = 1000; long long jsonSize = 0; for (int i = 0; i < numToRun; i++) { int size = 1234; char* x = new char[size]; DataView(x).write(tagLittleEndian(size)); for (int i = 4; i < size; i++) { x[i] = r.nextInt32(255); } x[size - 1] = 0; BSONObj o(x); ASSERT_EQUALS(size, o.objsize()); if (o.valid(BSONVersion::kLatest)) { numValid++; jsonSize += o.jsonString().size(); ASSERT_OK(validateBSON(o.objdata(), o.objsize(), BSONVersion::kLatest)); } else { ASSERT_NOT_OK(validateBSON(o.objdata(), o.objsize(), BSONVersion::kLatest)); } delete[] x; } log() << "RandomData: didn't crash valid/total: " << numValid << "/" << numToRun << " (want few valid ones)" << " jsonSize: " << jsonSize << endl; } TEST(BSONValidate, MuckingData1) { BSONObj theObject; { BSONObjBuilder b; b.append("name", "eliot was here"); b.append("yippee", "asd"); BSONArrayBuilder a(b.subarrayStart("arr")); for (int i = 0; i < 100; i++) { a.append(BSON("x" << i << "who" << "me" << "asd" << "asd")); } a.done(); b.done(); theObject = b.obj(); } int numValid = 0; int numToRun = 1000; long long jsonSize = 0; for (int i = 4; i < theObject.objsize() - 1; i++) { BSONObj mine = theObject.copy(); char* data = const_cast(mine.objdata()); data[i] = 0xc8U; numToRun++; if (mine.valid(BSONVersion::kLatest)) { numValid++; jsonSize += mine.jsonString().size(); ASSERT_OK(validateBSON(mine.objdata(), mine.objsize(), BSONVersion::kLatest)); } else { ASSERT_NOT_OK(validateBSON(mine.objdata(), mine.objsize(), BSONVersion::kLatest)); } } log() << "MuckingData1: didn't crash valid/total: " << numValid << "/" << numToRun << " (want few valid ones) " << " jsonSize: " << jsonSize << endl; } TEST(BSONValidate, Fuzz) { int64_t seed = time(0); log() << "BSONValidate Fuzz random seed: " << seed << endl; PseudoRandom randomSource(seed); BSONObj original = BSON("one" << 3 << "two" << 5 << "three" << BSONObj() << "four" << BSON("five" << BSON("six" << 11)) << "seven" << BSON_ARRAY("a" << "bb" << "ccc" << 5) << "eight" << BSONDBRef("rrr", OID("01234567890123456789aaaa")) << "_id" << OID("deadbeefdeadbeefdeadbeef") << "nine" << BSONBinData("\x69\xb7", 2, BinDataGeneral) << "ten" << Date_t::fromMillisSinceEpoch(44) << "eleven" << BSONRegEx("foooooo", "i")); int32_t fuzzFrequencies[] = {2, 10, 20, 100, 1000}; for (size_t i = 0; i < sizeof(fuzzFrequencies) / sizeof(int32_t); ++i) { int32_t fuzzFrequency = fuzzFrequencies[i]; // Copy the 'original' BSONObj to 'buffer'. unique_ptr buffer(new char[original.objsize()]); memcpy(buffer.get(), original.objdata(), original.objsize()); // Randomly flip bits in 'buffer', with probability determined by 'fuzzFrequency'. The // first four bytes, representing the size of the object, are excluded from bit // flipping. for (int32_t byteIdx = 4; byteIdx < original.objsize(); ++byteIdx) { for (int32_t bitIdx = 0; bitIdx < 8; ++bitIdx) { if (randomSource.nextInt32(fuzzFrequency) == 0) { reinterpret_cast(buffer[byteIdx]) ^= (1U << bitIdx); } } } BSONObj fuzzed(buffer.get()); // There is no assert here because there is no other BSON validator oracle // to compare outputs against (BSONObj::valid() is a wrapper for validateBSON()). // Thus, the reason for this test is to ensure that validateBSON() doesn't trip // any ASAN or UBSAN check when fed fuzzed input. validateBSON(fuzzed.objdata(), fuzzed.objsize(), BSONVersion::kLatest).isOK(); } } TEST(BSONValidateFast, Empty) { BSONObj x; ASSERT_OK(validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest)); } TEST(BSONValidateFast, RegEx) { BSONObjBuilder b; b.appendRegex("foo", "i"); BSONObj x = b.obj(); ASSERT_OK(validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest)); } TEST(BSONValidateFast, Simple0) { BSONObj x; ASSERT_OK(validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest)); x = BSON("foo" << 17 << "bar" << "eliot"); ASSERT_OK(validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest)); } TEST(BSONValidateFast, Simple2) { char buf[64]; for (int i = 1; i <= JSTypeMax; i++) { BSONObjBuilder b; sprintf(buf, "foo%d", i); b.appendMinForType(buf, i); sprintf(buf, "bar%d", i); b.appendMaxForType(buf, i); BSONObj x = b.obj(); ASSERT_OK(validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest)); } } TEST(BSONValidateFast, Simple3) { BSONObjBuilder b; char buf[64]; for (int i = 1; i <= JSTypeMax; i++) { sprintf(buf, "foo%d", i); b.appendMinForType(buf, i); sprintf(buf, "bar%d", i); b.appendMaxForType(buf, i); } BSONObj x = b.obj(); ASSERT_OK(validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest)); } TEST(BSONValidateFast, NestedObject) { BSONObj x = BSON("a" << 1 << "b" << BSON("c" << 2 << "d" << BSONArrayBuilder().obj() << "e" << BSON_ARRAY("1" << 2 << 3))); ASSERT_OK(validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest)); ASSERT_NOT_OK(validateBSON(x.objdata(), x.objsize() / 2, BSONVersion::kLatest)); } TEST(BSONValidateFast, ErrorWithId) { BufBuilder bb; BSONObjBuilder ob(bb); ob.append("_id", 1); appendInvalidStringElement("not_id", &bb); const BSONObj x = ob.done(); const Status status = validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest); ASSERT_NOT_OK(status); ASSERT_EQUALS( status.reason(), "not null terminated string in element with field name 'not_id' in object with _id: 1"); } TEST(BSONValidateFast, ErrorBeforeId) { BufBuilder bb; BSONObjBuilder ob(bb); appendInvalidStringElement("not_id", &bb); ob.append("_id", 1); const BSONObj x = ob.done(); const Status status = validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest); ASSERT_NOT_OK(status); ASSERT_EQUALS(status.reason(), "not null terminated string in element with field name 'not_id' in object with " "unknown _id"); } TEST(BSONValidateFast, ErrorNoId) { BufBuilder bb; BSONObjBuilder ob(bb); appendInvalidStringElement("not_id", &bb); const BSONObj x = ob.done(); const Status status = validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest); ASSERT_NOT_OK(status); ASSERT_EQUALS(status.reason(), "not null terminated string in element with field name 'not_id' in object with " "unknown _id"); } TEST(BSONValidateFast, ErrorIsInId) { BufBuilder bb; BSONObjBuilder ob(bb); appendInvalidStringElement("_id", &bb); const BSONObj x = ob.done(); const Status status = validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest); ASSERT_NOT_OK(status); ASSERT_EQUALS( status.reason(), "not null terminated string in element with field name '_id' in object with unknown _id"); } TEST(BSONValidateFast, NonTopLevelId) { BufBuilder bb; BSONObjBuilder ob(bb); ob.append("not_id1", BSON("_id" << "not the real _id")); appendInvalidStringElement("not_id2", &bb); const BSONObj x = ob.done(); const Status status = validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest); ASSERT_NOT_OK(status); ASSERT_EQUALS(status.reason(), "not null terminated string in element with field name 'not_id2' in object with " "unknown _id"); } TEST(BSONValidateFast, StringHasSomething) { BufBuilder bb; BSONObjBuilder ob(bb); bb.appendChar(String); bb.appendStr("x", /*withNUL*/ true); bb.appendNum(0); const BSONObj x = ob.done(); ASSERT_EQUALS(5 // overhead + 1 // type + 2 // name + 4 // size , x.objsize()); ASSERT_NOT_OK(validateBSON(x.objdata(), x.objsize(), BSONVersion::kLatest)); } TEST(BSONValidateBool, BoolValuesAreValidated) { BSONObjBuilder bob; bob.append("x", false); const BSONObj obj = bob.done(); ASSERT_OK(validateBSON(obj.objdata(), obj.objsize(), BSONVersion::kLatest)); const BSONElement x = obj["x"]; // Legal, because we know that the BufBuilder gave // us back some heap memory, which isn't oringinally const. auto writable = const_cast(x.value()); for (int val = std::numeric_limits::min(); val != (int(std::numeric_limits::max()) + 1); ++val) { *writable = static_cast(val); if ((val == 0) || (val == 1)) { ASSERT_OK(validateBSON(obj.objdata(), obj.objsize(), BSONVersion::kLatest)); } else { ASSERT_NOT_OK(validateBSON(obj.objdata(), obj.objsize(), BSONVersion::kLatest)); } } } } // namespace