summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Williams <louis.williams@mongodb.com>2019-10-22 19:11:45 +0000
committerevergreen <evergreen@mongodb.com>2019-10-22 19:11:45 +0000
commit565a92344456412acffa67879f962247291bee03 (patch)
treefaf1e4d989323edcfe9e6cd5a1679774f2cf722b
parent517f375f3690c51d77bf8227aed4d1d97359af93 (diff)
downloadmongo-565a92344456412acffa67879f962247291bee03.tar.gz
SERVER-43775 BSON errors should log memory context
When an invalid type is detected in a BSONElement, print the address and surrounding memory in an attempt to provide context around the error.
-rw-r--r--src/mongo/bson/bson_validate_test.cpp13
-rw-r--r--src/mongo/bson/bsonelement.cpp43
2 files changed, 53 insertions, 3 deletions
diff --git a/src/mongo/bson/bson_validate_test.cpp b/src/mongo/bson/bson_validate_test.cpp
index 479977dd930..e7d8e14ad70 100644
--- a/src/mongo/bson/bson_validate_test.cpp
+++ b/src/mongo/bson/bson_validate_test.cpp
@@ -347,4 +347,17 @@ TEST(BSONValidateBool, BoolValuesAreValidated) {
}
}
+TEST(BSONValidateFast, InvalidType) {
+ // Encode an invalid BSON Object with an invalid type, x90.
+ const char* buffer = "\x0c\x00\x00\x00\x90\x41\x00\x10\x00\x00\x00\x00";
+
+ // Constructing the object is fine, but validating should fail.
+ BSONObj obj(buffer);
+
+ // Validate fails.
+ ASSERT_NOT_OK(validateBSON(obj.objdata(), obj.objsize(), BSONVersion::kLatest));
+ ASSERT_THROWS_CODE(obj.woCompare(BSON("A" << 1)), DBException, 10320);
+}
+
+
} // namespace
diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp
index c72e61eb888..707f3042cb4 100644
--- a/src/mongo/bson/bsonelement.cpp
+++ b/src/mongo/bson/bsonelement.cpp
@@ -33,6 +33,7 @@
#include <boost/functional/hash.hpp>
#include <cmath>
+#include <fmt/format.h>
#include "mongo/base/compare_numbers.h"
#include "mongo/base/data_cursor.h"
@@ -49,6 +50,10 @@
#include "mongo/util/string_map.h"
#include "mongo/util/uuid.h"
+#if !defined(__has_feature)
+#define __has_feature(x) 0
+#endif
+
namespace mongo {
using std::dec;
@@ -697,8 +702,40 @@ BSONElement BSONElement::operator[](StringData field) const {
}
namespace {
-MONGO_COMPILER_NOINLINE void msgAssertedBadType [[noreturn]] (int8_t type) {
- msgasserted(10320, str::stream() << "BSONElement: bad type " << (int)type);
+MONGO_COMPILER_NOINLINE void msgAssertedBadType [[noreturn]] (const char* data) {
+ // We intentionally read memory that may be out of the allocated memory's boundary, so do not
+ // do this when the adress sanitizer is enabled. We do this in an attempt to log as much context
+ // about the failure, even if that risks undefined behavior or a segmentation fault.
+#if !__has_feature(address_sanitizer)
+ bool logMemory = true;
+#else
+ bool logMemory = false;
+#endif
+
+ str::stream output;
+ if (!logMemory) {
+ output << fmt::format("BSONElement: bad type {0:d} @ {1:p}", *data, data);
+ } else {
+ // To reduce the risk of a segmentation fault, only print the bytes in the 32-bit aligned
+ // block in which the address is located (i.e. round down to the lowest multiple of 32). The
+ // hope is that it's safe to read memory that may fall within the same cache line. Generate
+ // a mask to zero-out the last bits for a block-aligned address.
+ // Ex: Inverse of 0x1F (32 - 1) looks like 0xFFFFFFE0, and ANDed with the pointer, zeroes
+ // the lowest 5 bits, giving the starting address of a 32-bit block.
+ const size_t blockSize = 32;
+ const size_t mask = ~(blockSize - 1);
+ const char* startAddr =
+ reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(data) & mask);
+ const size_t offset = data - startAddr;
+
+ output << fmt::format(
+ "BSONElement: bad type {0:d} @ {1:p} at offset {2:d} in block: ", *data, data, offset);
+
+ for (size_t i = 0; i < blockSize; i++) {
+ output << fmt::format("{0:#x} ", static_cast<uint8_t>(startAddr[i]));
+ }
+ }
+ msgasserted(10320, output);
}
} // namespace
@@ -747,7 +784,7 @@ int BSONElement::computeSize() const {
int8_t type = *data;
if (MONGO_unlikely(type < 0 || type > JSTypeMax)) {
if (MONGO_unlikely(type != MinKey && type != MaxKey)) {
- msgAssertedBadType(type);
+ msgAssertedBadType(data);
}
// MinKey and MaxKey should be treated the same as Null