summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Zolnierz <nicholas.zolnierz@mongodb.com>2018-04-10 16:40:41 -0400
committerNick Zolnierz <nicholas.zolnierz@mongodb.com>2018-04-26 19:10:55 -0400
commit7cd83e5486cfb3383c2e4e5671ae9602be297547 (patch)
tree50241dbde52f940fb033211371d06501da8a44e4
parentc799c6c67537e0d6f65003c141b444897ad9e408 (diff)
downloadmongo-7cd83e5486cfb3383c2e4e5671ae9602be297547.tar.gz
SERVER-34399: $changeStream with invalid resume token crashes the server
-rw-r--r--src/mongo/db/pipeline/resume_token.cpp8
-rw-r--r--src/mongo/db/pipeline/resume_token_test.cpp55
-rw-r--r--src/mongo/db/storage/key_string.cpp92
-rw-r--r--src/mongo/db/storage/key_string.h13
-rw-r--r--src/mongo/db/storage/key_string_test.cpp72
5 files changed, 194 insertions, 46 deletions
diff --git a/src/mongo/db/pipeline/resume_token.cpp b/src/mongo/db/pipeline/resume_token.cpp
index e602379dd0c..ded14a8be02 100644
--- a/src/mongo/db/pipeline/resume_token.cpp
+++ b/src/mongo/db/pipeline/resume_token.cpp
@@ -169,10 +169,10 @@ ResumeTokenData ResumeToken::getData() const {
// We validate the type at parse time.
MONGO_UNREACHABLE;
}
- auto internalBson = KeyString::toBson(static_cast<const char*>(keyStringBinData.data),
- keyStringBinData.length,
- Ordering::make(BSONObj()),
- typeBits);
+ auto internalBson = KeyString::toBsonSafe(static_cast<const char*>(keyStringBinData.data),
+ keyStringBinData.length,
+ Ordering::make(BSONObj()),
+ typeBits);
BSONObjIterator i(internalBson);
ResumeTokenData result;
diff --git a/src/mongo/db/pipeline/resume_token_test.cpp b/src/mongo/db/pipeline/resume_token_test.cpp
index 6f7eaf4bbb4..84273b7a05e 100644
--- a/src/mongo/db/pipeline/resume_token_test.cpp
+++ b/src/mongo/db/pipeline/resume_token_test.cpp
@@ -26,14 +26,18 @@
* it in the license file.
*/
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
+
#include "mongo/db/pipeline/resume_token.h"
#include <algorithm>
#include <boost/optional/optional_io.hpp>
+#include <random>
#include "mongo/db/pipeline/document.h"
#include "mongo/db/pipeline/document_source_change_stream.h"
#include "mongo/unittest/unittest.h"
+#include "mongo/util/log.h"
namespace mongo {
@@ -162,8 +166,7 @@ TEST(ResumeToken, TestMissingTypebitsOptimization) {
ASSERT_EQ(BSONType::NumberInt, rtNoTypeBitsData.documentKey["_id"].getType());
}
-
-TEST(ResumeToken, CorruptTokens) {
+TEST(ResumeToken, FailsToParseForInvalidTokenFormats) {
// Missing document.
ASSERT_THROWS(ResumeToken::parse(Document()), AssertionException);
// Missing data field.
@@ -195,34 +198,56 @@ TEST(ResumeToken, CorruptTokens) {
ResumeToken::parse(Document{{"_data"_sd, goodData},
{"_typeBits", BSONBinData(goodData.data, 0, newUUID)}}),
AssertionException);
+}
+TEST(ResumeToken, FailsToDecodeInvalidKeyStringBinData) {
+ Timestamp ts(1010, 4);
+ ResumeTokenData tokenData;
+ tokenData.clusterTime = ts;
+ auto goodTokenDocBinData = ResumeToken(tokenData).toDocument(Format::kBinData);
+ auto goodData = goodTokenDocBinData["_data"].getBinData();
const unsigned char zeroes[] = {0, 0, 0, 0, 0};
const unsigned char nonsense[] = {165, 85, 77, 86, 255};
+
// Data of correct type, but empty. This won't fail until we try to decode the data.
auto emptyToken =
ResumeToken::parse(Document{{"_data"_sd, BSONBinData(zeroes, 0, BinDataGeneral)}});
- ASSERT_THROWS(emptyToken.getData(), AssertionException);
- emptyToken = ResumeToken::parse(Document{{"_data"_sd, "string"_sd}});
- ASSERT_THROWS(emptyToken.getData(), AssertionException);
+ ASSERT_THROWS_CODE(emptyToken.getData(), AssertionException, 40649);
+
+ auto incorrectType = ResumeToken::parse(Document{{"_data"_sd, "string"_sd}});
+ ASSERT_THROWS_CODE(incorrectType.getData(), AssertionException, ErrorCodes::FailedToParse);
// Data of correct type with a bunch of zeros.
- auto zeroesToken =
- ResumeToken::parse(Document{{"_data"_sd, BSONBinData(zeroes, 5, BinDataGeneral)}});
- ASSERT_THROWS(emptyToken.getData(), AssertionException);
+ auto zeroesToken = ResumeToken::parse(
+ Document{{"_data"_sd, BSONBinData(zeroes, sizeof(zeroes), BinDataGeneral)}});
+ ASSERT_THROWS_CODE(zeroesToken.getData(), AssertionException, 50811);
zeroesToken = ResumeToken::parse(Document{{"_data"_sd, "00000"_sd}});
- ASSERT_THROWS(emptyToken.getData(), AssertionException);
+ ASSERT_THROWS_CODE(zeroesToken.getData(), AssertionException, ErrorCodes::FailedToParse);
// Data of correct type with a bunch of nonsense.
- auto nonsenseToken =
- ResumeToken::parse(Document{{"_data"_sd, BSONBinData(nonsense, 5, BinDataGeneral)}});
- ASSERT_THROWS(emptyToken.getData(), AssertionException);
+ auto nonsenseToken = ResumeToken::parse(
+ Document{{"_data"_sd, BSONBinData(nonsense, sizeof(nonsense), BinDataGeneral)}});
+ ASSERT_THROWS_CODE(nonsenseToken.getData(), AssertionException, 50811);
nonsenseToken = ResumeToken::parse(Document{{"_data"_sd, "nonsense"_sd}});
- ASSERT_THROWS(emptyToken.getData(), AssertionException);
+ ASSERT_THROWS_CODE(nonsenseToken.getData(), AssertionException, ErrorCodes::FailedToParse);
// Valid data, bad typeBits; note that an all-zeros typebits is valid so it is not tested here.
auto badTypeBitsToken = ResumeToken::parse(
- Document{{"_data"_sd, goodData}, {"_typeBits", BSONBinData(nonsense, 5, BinDataGeneral)}});
- ASSERT_THROWS(badTypeBitsToken.getData(), AssertionException);
+ Document{{"_data"_sd, goodData},
+ {"_typeBits", BSONBinData(nonsense, sizeof(nonsense), BinDataGeneral)}});
+ ASSERT_THROWS_CODE(badTypeBitsToken.getData(), AssertionException, ErrorCodes::Overflow);
+
+ const unsigned char invalidString[] = {
+ 60, // CType::kStringLike
+ 55, // Non-null terminated
+ };
+ auto invalidStringToken = ResumeToken::parse(
+ Document{{"_data"_sd, BSONBinData(invalidString, sizeof(invalidString), BinDataGeneral)}});
+ ASSERT_THROWS_WITH_CHECK(
+ invalidStringToken.getData(), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), 50816);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Failed to find null terminator in string");
+ });
}
TEST(ResumeToken, WrongVersionToken) {
diff --git a/src/mongo/db/storage/key_string.cpp b/src/mongo/db/storage/key_string.cpp
index 0685576dabc..2e62f8489a5 100644
--- a/src/mongo/db/storage/key_string.cpp
+++ b/src/mongo/db/storage/key_string.cpp
@@ -250,7 +250,7 @@ T readType(BufReader* reader, bool inverted) {
StringData readCString(BufReader* reader) {
const char* start = static_cast<const char*>(reader->pos());
const char* end = static_cast<const char*>(memchr(start, 0x0, reader->remaining()));
- invariant(end);
+ uassert(50816, "Failed to find null terminator in string.", end);
size_t actualBytes = end - start;
reader->skip(1 + actualBytes);
return StringData(start, actualBytes);
@@ -282,7 +282,7 @@ StringData readCStringWithNuls(BufReader* reader, std::string* scratch) {
string readInvertedCString(BufReader* reader) {
const char* start = static_cast<const char*>(reader->pos());
const char* end = static_cast<const char*>(memchr(start, 0xFF, reader->remaining()));
- invariant(end);
+ uassert(50817, "Failed to find '0xFF' in inverted string.", end);
size_t actualBytes = end - start;
string s(start, actualBytes);
for (size_t i = 0; i < s.size(); i++) {
@@ -307,7 +307,7 @@ string readInvertedCStringWithNuls(BufReader* reader) {
const char* start = static_cast<const char*>(reader->pos());
const char* end = static_cast<const char*>(memchr(start, 0xFF, reader->remaining()));
- invariant(end);
+ uassert(50820, "Failed to find '0xFF' in inverted string.", end);
size_t actualBytes = end - start;
out.append(start, actualBytes);
@@ -1122,7 +1122,9 @@ Decimal128 readDecimalContinuation(BufReader* reader, bool inverted, Decimal128
uint64_t continuation = endian::bigToNative(readType<uint64_t>(reader, inverted));
num = num.normalize();
num = num.add(Decimal128(num.isNegative(), num.getBiasedExponent(), 0, continuation), &flags);
- invariant(!(Decimal128::hasFlag(flags, Decimal128::kInexact)));
+ uassert(50815,
+ "Unexpected inexact flag set after Decimal addition.",
+ !(Decimal128::hasFlag(flags, Decimal128::kInexact)));
return num;
}
@@ -1182,7 +1184,9 @@ void toBsonValue(uint8_t ctype,
if (originalType == TypeBits::kString) {
*stream << readInvertedCStringWithNuls(reader);
} else {
- dassert(originalType == TypeBits::kSymbol);
+ uassert(50827,
+ "Expected original type to be Symbol.",
+ originalType == TypeBits::kSymbol);
*stream << BSONSymbol(readInvertedCStringWithNuls(reader));
}
@@ -1191,7 +1195,9 @@ void toBsonValue(uint8_t ctype,
if (originalType == TypeBits::kString) {
*stream << readCStringWithNuls(reader, &scratch);
} else {
- dassert(originalType == TypeBits::kSymbol);
+ uassert(50828,
+ "Expected original type to be Symbol.",
+ originalType == TypeBits::kSymbol);
*stream << BSONSymbol(readCStringWithNuls(reader, &scratch));
}
}
@@ -1302,7 +1308,9 @@ void toBsonValue(uint8_t ctype,
if (type == TypeBits::kDouble) {
*stream << std::numeric_limits<double>::quiet_NaN();
} else {
- invariant(type == TypeBits::kDecimal && version == KeyString::Version::V1);
+ uassert(50819,
+ "Invalid type bits for numeric NaN",
+ type == TypeBits::kDecimal && version == KeyString::Version::V1);
*stream << Decimal128::kPositiveNaN;
}
break;
@@ -1361,7 +1369,9 @@ void toBsonValue(uint8_t ctype,
bin = isNegative ? -std::numeric_limits<double>::infinity()
: std::numeric_limits<double>::infinity();
} else { // Huge decimal number, directly output
- invariant(originalType == TypeBits::kDecimal);
+ uassert(50818,
+ "Invalid type bits for decimal number.",
+ originalType == TypeBits::kDecimal);
uint64_t highbits = encoded & ~(1ULL << 63);
uint64_t lowbits = endian::bigToNative(readType<uint64_t>(reader, inverted));
Decimal128 dec(Decimal128::Value{lowbits, highbits});
@@ -1378,10 +1388,14 @@ void toBsonValue(uint8_t ctype,
*stream << bin;
} else if (originalType == TypeBits::kLong) {
// This can only happen for a single number.
- invariant(bin == static_cast<double>(std::numeric_limits<long long>::min()));
+ uassert(50821,
+ "Unexpected value for large number.",
+ bin == static_cast<double>(std::numeric_limits<long long>::min()));
*stream << std::numeric_limits<long long>::min();
} else {
- invariant(originalType == TypeBits::kDecimal && version != KeyString::Version::V0);
+ uassert(50826,
+ "Unexpected type of large number.",
+ originalType == TypeBits::kDecimal && version != KeyString::Version::V0);
const auto roundAwayFromZero = isNegative ? Decimal128::kRoundTowardNegative
: Decimal128::kRoundTowardPositive;
Decimal128 dec(bin, Decimal128::kRoundTo34Digits, roundAwayFromZero);
@@ -1406,7 +1420,9 @@ void toBsonValue(uint8_t ctype,
if (version == KeyString::Version::V0) {
// for these, the raw double was stored intact, including sign bit.
- invariant(originalType == TypeBits::kDouble);
+ uassert(50812,
+ "Invalid type bits for small number.",
+ originalType == TypeBits::kDouble);
double d;
memcpy(&d, &encoded, sizeof(d));
*stream << d;
@@ -1438,12 +1454,14 @@ void toBsonValue(uint8_t ctype,
double scaledBin;
memcpy(&scaledBin, &encoded, sizeof(scaledBin));
if (originalType == TypeBits::kDouble) {
- invariant(!hasDecimalContinuation);
+ uassert(50822, "Unexpected decimal continuation.", !hasDecimalContinuation);
double bin = scaledBin * kTinyDoubleExponentDownshiftFactor;
*stream << (isNegative ? -bin : bin);
break;
}
- invariant(originalType == TypeBits::kDecimal && hasDecimalContinuation);
+ uassert(50823,
+ "Expected decimal continuation.",
+ originalType == TypeBits::kDecimal && hasDecimalContinuation);
// If the actual double would be subnormal, scale in decimal domain.
Decimal128 dec;
@@ -1474,13 +1492,17 @@ void toBsonValue(uint8_t ctype,
double bin;
memcpy(&bin, &encoded, sizeof(bin));
if (originalType == TypeBits::kDouble) {
- invariant(dcm == KeyString::kDCMEqualToDouble);
+ uassert(50824,
+ "Decimal contuation mismatch.",
+ dcm == KeyString::kDCMEqualToDouble);
*stream << (isNegative ? -bin : bin);
break;
}
// Deal with decimal cases
- invariant(originalType == TypeBits::kDecimal);
+ uassert(50825,
+ "Unexpected type for small number, expected decimal.",
+ originalType == TypeBits::kDecimal);
Decimal128 dec;
switch (dcm) {
case KeyString::kDCMEqualToDoubleRoundedUpTo15Digits:
@@ -1562,14 +1584,16 @@ void toBsonValue(uint8_t ctype,
*stream << adjustDecimalExponent(typeBits, Decimal128(integerPart));
break;
default:
- MONGO_UNREACHABLE;
+ uasserted(50831, "Unexpected type for positive int.");
}
break;
}
// KeyString V0: anything fractional is a double
if (version == KeyString::Version::V0) {
- invariant(originalType == TypeBits::kDouble);
+ uassert(50832,
+ "Expected type double for fractional part.",
+ originalType == TypeBits::kDouble);
const uint64_t exponent = (64 - countLeadingZeros64(integerPart)) - 1;
const size_t fractionalBits = (52 - exponent);
const size_t fractionalBytes = (fractionalBits + 7) / 8;
@@ -1614,7 +1638,7 @@ void toBsonValue(uint8_t ctype,
: KeyString::kDCMHasContinuationLargerThanDoubleRoundedUpTo15Digits;
// Deal with decimal cases
- invariant(originalType == TypeBits::kDecimal);
+ uassert(50810, "Expected type Decimal.", originalType == TypeBits::kDecimal);
Decimal128 dec;
switch (dcm) {
case KeyString::kDCMEqualToDoubleRoundedUpTo15Digits:
@@ -1637,7 +1661,7 @@ void toBsonValue(uint8_t ctype,
break;
}
default:
- MONGO_UNREACHABLE;
+ uasserted(50811, str::stream() << "Unknown type: " << ctype);
}
}
@@ -1936,7 +1960,7 @@ Decimal128 adjustDecimalExponent(TypeBits::Reader* typeBits, Decimal128 num) {
// possible, we must be able to scale up. Scaling always must be exact and not change the value.
const uint32_t kMaxExpAdjust = 33;
const uint32_t kMaxExpIncrementForZeroHighCoefficient = 19;
- dassert(!num.isZero());
+ uassert(50829, "Expected non-zero number for decimal.", !num.isZero());
const uint32_t origExp = num.getBiasedExponent();
const uint8_t storedBits = typeBits->readDecimalExponent();
@@ -1958,16 +1982,22 @@ Decimal128 adjustDecimalExponent(TypeBits::Reader* typeBits, Decimal128 num) {
// Increase exponent and decrease (right shift) coefficient.
uint32_t flags = Decimal128::SignalingFlag::kNoFlag;
auto quantized = num.quantize(Decimal128(0, highExp, 0, 1), &flags);
- invariant(flags == Decimal128::SignalingFlag::kNoFlag); // must be exact
+ uassert(50813,
+ "Unexpected signaling flag for Decimal quantization.",
+ flags == Decimal128::SignalingFlag::kNoFlag); // must be exact
num = quantized;
} else {
// Decrease exponent and increase (left shift) coefficient.
uint32_t lowExp = highExp - (1U << KeyString::TypeBits::kStoredDecimalExponentBits);
- invariant(lowExp >= origExp - kMaxExpAdjust);
+ uassert(50814,
+ "Unexpected exponent values after adjusting Decimal.",
+ lowExp >= origExp - kMaxExpAdjust);
num = num.add(Decimal128(0, lowExp, 0, 0));
}
- dassert((num.getBiasedExponent() & KeyString::TypeBits::kStoredDecimalExponentMask) ==
- (highExp & KeyString::TypeBits::kStoredDecimalExponentMask));
+ uassert(50830,
+ "Unexpected biased exponent in decimal.",
+ (num.getBiasedExponent() & KeyString::TypeBits::kStoredDecimalExponentMask) ==
+ (highExp & KeyString::TypeBits::kStoredDecimalExponentMask));
return num;
}
@@ -1996,7 +2026,10 @@ size_t KeyString::getKeySize(const char* buffer,
return (len - (remainingBytes + 1));
}
-BSONObj KeyString::toBson(const char* buffer, size_t len, Ordering ord, const TypeBits& typeBits) {
+BSONObj KeyString::toBsonSafe(const char* buffer,
+ size_t len,
+ Ordering ord,
+ const TypeBits& typeBits) {
BSONObjBuilder builder;
BufReader reader(buffer, len);
TypeBits::Reader typeBitsReader(typeBits);
@@ -2018,6 +2051,13 @@ BSONObj KeyString::toBson(const char* buffer, size_t len, Ordering ord, const Ty
return builder.obj();
}
+BSONObj KeyString::toBson(const char* buffer,
+ size_t len,
+ Ordering ord,
+ const TypeBits& typeBits) noexcept {
+ return toBsonSafe(buffer, len, ord, typeBits);
+}
+
BSONObj KeyString::toBson(StringData data, Ordering ord, const TypeBits& typeBits) {
return toBson(data.rawData(), data.size(), ord, typeBits);
}
@@ -2180,7 +2220,7 @@ uint8_t KeyString::TypeBits::Reader::readBit() {
const uint8_t offsetInByte = _curBit % 8;
_curBit++;
- dassert(byte <= _typeBits.getSizeByte());
+ uassert(50615, "Invalid size byte.", byte <= _typeBits.getSizeByte());
return (_typeBits._buf[byte] & (1 << offsetInByte)) ? 1 : 0;
}
diff --git a/src/mongo/db/storage/key_string.h b/src/mongo/db/storage/key_string.h
index e9ed78a9cf6..a144a5096aa 100644
--- a/src/mongo/db/storage/key_string.h
+++ b/src/mongo/db/storage/key_string.h
@@ -305,7 +305,18 @@ public:
Ordering ord,
const TypeBits& typeBits);
static BSONObj toBson(StringData data, Ordering ord, const TypeBits& types);
- static BSONObj toBson(const char* buffer, size_t len, Ordering ord, const TypeBits& types);
+ /**
+ * Decodes the given KeyString buffer into it's BSONObj representation. This is marked as
+ * noexcept since the assumption is that 'buffer' is a valid KeyString buffer and this method
+ * is not expected to throw.
+ *
+ * If the buffer provided may not be valid, use the 'safe' version instead.
+ */
+ static BSONObj toBson(const char* buffer,
+ size_t len,
+ Ordering ord,
+ const TypeBits& types) noexcept;
+ static BSONObj toBsonSafe(const char* buffer, size_t len, Ordering ord, const TypeBits& types);
/**
* Decodes a RecordId from the end of a buffer.
diff --git a/src/mongo/db/storage/key_string_test.cpp b/src/mongo/db/storage/key_string_test.cpp
index 8efd5ff7197..aa0974bd486 100644
--- a/src/mongo/db/storage/key_string_test.cpp
+++ b/src/mongo/db/storage/key_string_test.cpp
@@ -50,6 +50,7 @@
#include "mongo/stdx/functional.h"
#include "mongo/stdx/future.h"
#include "mongo/stdx/memory.h"
+#include "mongo/unittest/death_test.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/hex.h"
#include "mongo/util/log.h"
@@ -1178,6 +1179,68 @@ TEST_F(KeyStringTest, KeyWithTooManyTypeBitsCausesUassert) {
ASSERT_THROWS_CODE(key.resetToKey(obj, ONE_ASCENDING), DBException, ErrorCodes::KeyTooLong);
}
+TEST_F(KeyStringTest, ToBsonSafeShouldNotTerminate) {
+ KeyString::TypeBits typeBits(KeyString::Version::V1);
+
+ const char invalidString[] = {
+ 60, // CType::kStringLike
+ 55, // Non-null terminated
+ };
+ ASSERT_THROWS_CODE(
+ KeyString::toBsonSafe(invalidString, sizeof(invalidString), ALL_ASCENDING, typeBits),
+ AssertionException,
+ 50816);
+
+ const char invalidNumber[] = {
+ 43, // CType::kNumericPositive1ByteInt
+ 1, // Encoded integer part, least significant bit indicates there's a fractional part.
+ 0, // Since the integer part is 1 byte, the next 7 bytes are expected to be the fractional
+ // part and are needed to prevent the BufReader from overflowing.
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ };
+ ASSERT_THROWS_CODE(
+ KeyString::toBsonSafe(invalidNumber, sizeof(invalidNumber), ALL_ASCENDING, typeBits),
+ AssertionException,
+ 50810);
+}
+
+TEST_F(KeyStringTest, RandomizedInputsForToBsonSafe) {
+ std::mt19937 gen(newSeed());
+ std::uniform_int_distribution<> randomByte(std::numeric_limits<unsigned char>::min(),
+ std::numeric_limits<unsigned char>::max());
+
+ const auto interestingElements = getInterestingElements(KeyString::Version::V1);
+ for (auto elem : interestingElements) {
+ const KeyString ks(KeyString::Version::V1, elem, ALL_ASCENDING);
+
+ char* ksBuffer = (char*)ks.getBuffer();
+
+ // Select a random byte to change, except for the first byte as it will likely become an
+ // invalid CType and not test anything interesting.
+ ksBuffer[randomByte(gen) % ks.getSize() + 1] = randomByte(gen);
+
+ try {
+ KeyString::toBsonSafe(ksBuffer, ks.getSize(), ALL_ASCENDING, ks.getTypeBits());
+ } catch (const AssertionException&) {
+ // The expectation is that the randomized buffer is likely an invalid KeyString,
+ // however attempting to decode it should fail gracefully.
+ }
+
+ // Retest with descending.
+ try {
+ KeyString::toBsonSafe(ksBuffer, ks.getSize(), ONE_DESCENDING, ks.getTypeBits());
+ } catch (const AssertionException&) {
+ // The expectation is that the randomized buffer is likely an invalid KeyString,
+ // however attempting to decode it should fail gracefully.
+ }
+ }
+}
+
namespace {
const uint64_t kMinPerfMicros = 20 * 1000;
const uint64_t kMinPerfSamples = 50 * 1000;
@@ -1330,3 +1393,12 @@ TEST_F(KeyStringTest, DecimalFromUniformDoublePerf) {
}
perfTest(version, numbers);
}
+
+DEATH_TEST(KeyStringTest, ToBsonPromotesAssertionsToTerminate, "terminate() called") {
+ const char invalidString[] = {
+ 60, // CType::kStringLike
+ 55, // Non-null terminated
+ };
+ KeyString::TypeBits typeBits(KeyString::Version::V1);
+ KeyString::toBson(invalidString, sizeof(invalidString), ALL_ASCENDING, typeBits);
+}