Commit 10487060 authored by Kenton Varda's avatar Kenton Varda
parent 26bcceda
...@@ -10,6 +10,7 @@ Scott Purdy <scott@fer.io>: kj/std iostream interface ...@@ -10,6 +10,7 @@ Scott Purdy <scott@fer.io>: kj/std iostream interface
Bryan Borham <bjboreham@gmail.com>: Initial MSVC support Bryan Borham <bjboreham@gmail.com>: Initial MSVC support
Philip Quinn <p@partylemon.com>: cmake build and other assorted bits Philip Quinn <p@partylemon.com>: cmake build and other assorted bits
Brian Taylor <el.wubo@gmail.com>: emacs syntax highlighting Brian Taylor <el.wubo@gmail.com>: emacs syntax highlighting
Ben Laurie <ben@links.org>: discovered and responsibly disclosed security bugs
This file does not list people who maintain their own Cap'n Proto This file does not list people who maintain their own Cap'n Proto
implementations as separate projects. Those people are awesome too! :) implementations as separate projects. Those people are awesome too! :)
...@@ -117,6 +117,13 @@ public: ...@@ -117,6 +117,13 @@ public:
KJ_ALWAYS_INLINE(bool containsInterval(const void* from, const void* to)); KJ_ALWAYS_INLINE(bool containsInterval(const void* from, const void* to));
KJ_ALWAYS_INLINE(bool amplifiedRead(WordCount virtualAmount));
// Indicates that the reader should pretend that `virtualAmount` additional data was read even
// though no actual pointer was traversed. This is used e.g. when reading a struct list pointer
// where the element sizes are zero -- the sender could set the list size arbitrarily high and
// cause the receiver to iterate over this list even though the message itself is small, so we
// need to defend agaisnt DoS attacks based on this.
inline Arena* getArena(); inline Arena* getArena();
inline SegmentId getSegmentId(); inline SegmentId getSegmentId();
...@@ -367,6 +374,10 @@ inline bool SegmentReader::containsInterval(const void* from, const void* to) { ...@@ -367,6 +374,10 @@ inline bool SegmentReader::containsInterval(const void* from, const void* to) {
arena); arena);
} }
inline bool SegmentReader::amplifiedRead(WordCount virtualAmount) {
return readLimiter->canRead(virtualAmount, arena);
}
inline Arena* SegmentReader::getArena() { return arena; } inline Arena* SegmentReader::getArena() { return arena; }
inline SegmentId SegmentReader::getSegmentId() { return id; } inline SegmentId SegmentReader::getSegmentId() { return id; }
inline const word* SegmentReader::getStartPtr() { return ptr.begin(); } inline const word* SegmentReader::getStartPtr() { return ptr.begin(); }
......
...@@ -1410,6 +1410,36 @@ TEST(Encoding, Has) { ...@@ -1410,6 +1410,36 @@ TEST(Encoding, Has) {
EXPECT_TRUE(root.asReader().hasInt32List()); EXPECT_TRUE(root.asReader().hasInt32List());
} }
TEST(Encoding, VoidListAmplification) {
MallocMessageBuilder builder;
builder.initRoot<test::TestAnyPointer>().getAnyPointerField().initAs<List<Void>>(1u << 28);
auto segments = builder.getSegmentsForOutput();
EXPECT_EQ(1, segments.size());
EXPECT_LT(segments[0].size(), 16); // quite small for such a big list!
SegmentArrayMessageReader reader(builder.getSegmentsForOutput());
auto root = reader.getRoot<test::TestAnyPointer>().getAnyPointerField();
EXPECT_NONFATAL_FAILURE(root.getAs<List<TestAllTypes>>());
MallocMessageBuilder copy;
EXPECT_NONFATAL_FAILURE(copy.setRoot(reader.getRoot<AnyPointer>()));
}
TEST(Encoding, EmptyStructListAmplification) {
MallocMessageBuilder builder;
builder.initRoot<test::TestAnyPointer>().getAnyPointerField()
.initAs<List<test::TestEmptyStruct>>(1u << 28);
auto segments = builder.getSegmentsForOutput();
EXPECT_EQ(1, segments.size());
EXPECT_LT(segments[0].size(), 16); // quite small for such a big list!
SegmentArrayMessageReader reader(builder.getSegmentsForOutput());
auto root = reader.getRoot<test::TestAnyPointer>().getAnyPointerField();
EXPECT_NONFATAL_FAILURE(root.getAs<List<TestAllTypes>>());
}
TEST(Encoding, Constants) { TEST(Encoding, Constants) {
EXPECT_EQ(VOID, test::TestConstants::VOID_CONST); EXPECT_EQ(VOID, test::TestConstants::VOID_CONST);
EXPECT_EQ(true, test::TestConstants::BOOL_CONST); EXPECT_EQ(true, test::TestConstants::BOOL_CONST);
......
...@@ -308,6 +308,11 @@ struct WireHelpers { ...@@ -308,6 +308,11 @@ struct WireHelpers {
return segment == nullptr || segment->containsInterval(start, end); return segment == nullptr || segment->containsInterval(start, end);
} }
static KJ_ALWAYS_INLINE(bool amplifiedRead(SegmentReader* segment, WordCount virtualAmount)) {
// If segment is null, this is an unchecked message, so we don't do read limiter checks.
return segment == nullptr || segment->amplifiedRead(virtualAmount);
}
static KJ_ALWAYS_INLINE(word* allocate( static KJ_ALWAYS_INLINE(word* allocate(
WirePointer*& ref, SegmentBuilder*& segment, WordCount amount, WirePointer*& ref, SegmentBuilder*& segment, WordCount amount,
WirePointer::Kind kind, BuilderArena* orphanArena)) { WirePointer::Kind kind, BuilderArena* orphanArena)) {
...@@ -1675,6 +1680,15 @@ struct WireHelpers { ...@@ -1675,6 +1680,15 @@ struct WireHelpers {
goto useDefault; goto useDefault;
} }
if (wordsPerElement * (1 * ELEMENTS) == 0 * WORDS) {
// Watch out for lists of zero-sized structs, which can claim to be arbitrarily large
// without having sent actual data.
KJ_REQUIRE(amplifiedRead(srcSegment, elementCount * (1 * WORDS / ELEMENTS)),
"Message contains amplified list pointer.") {
goto useDefault;
}
}
return setListPointer(dstSegment, dst, return setListPointer(dstSegment, dst,
ListReader(srcSegment, ptr, elementCount, wordsPerElement * BITS_PER_WORD, ListReader(srcSegment, ptr, elementCount, wordsPerElement * BITS_PER_WORD,
tag->structRef.dataSize.get() * BITS_PER_WORD, tag->structRef.dataSize.get() * BITS_PER_WORD,
...@@ -1693,6 +1707,15 @@ struct WireHelpers { ...@@ -1693,6 +1707,15 @@ struct WireHelpers {
goto useDefault; goto useDefault;
} }
if (elementSize == ElementSize::VOID) {
// Watch out for lists of void, which can claim to be arbitrarily large without having
// sent actual data.
KJ_REQUIRE(amplifiedRead(srcSegment, elementCount * (1 * WORDS / ELEMENTS)),
"Message contains amplified list pointer.") {
goto useDefault;
}
}
return setListPointer(dstSegment, dst, return setListPointer(dstSegment, dst,
ListReader(srcSegment, ptr, elementCount, step, dataSize, pointerCount, elementSize, ListReader(srcSegment, ptr, elementCount, step, dataSize, pointerCount, elementSize,
nestingLimit - 1), nestingLimit - 1),
...@@ -1931,6 +1954,15 @@ struct WireHelpers { ...@@ -1931,6 +1954,15 @@ struct WireHelpers {
goto useDefault; goto useDefault;
} }
if (wordsPerElement * (1 * ELEMENTS) == 0 * WORDS) {
// Watch out for lists of zero-sized structs, which can claim to be arbitrarily large
// without having sent actual data.
KJ_REQUIRE(amplifiedRead(segment, size * (1 * WORDS / ELEMENTS)),
"Message contains amplified list pointer.") {
goto useDefault;
}
}
if (checkElementSize) { if (checkElementSize) {
// If a struct list was not expected, then presumably a non-struct list was upgraded to a // If a struct list was not expected, then presumably a non-struct list was upgraded to a
// struct list. We need to manipulate the pointer to point at the first field of the // struct list. We need to manipulate the pointer to point at the first field of the
...@@ -1988,14 +2020,24 @@ struct WireHelpers { ...@@ -1988,14 +2020,24 @@ struct WireHelpers {
BitCount dataSize = dataBitsPerElement(ref->listRef.elementSize()) * ELEMENTS; BitCount dataSize = dataBitsPerElement(ref->listRef.elementSize()) * ELEMENTS;
WirePointerCount pointerCount = WirePointerCount pointerCount =
pointersPerElement(ref->listRef.elementSize()) * ELEMENTS; pointersPerElement(ref->listRef.elementSize()) * ELEMENTS;
ElementCount elementCount = ref->listRef.elementCount();
auto step = (dataSize + pointerCount * BITS_PER_POINTER) / ELEMENTS; auto step = (dataSize + pointerCount * BITS_PER_POINTER) / ELEMENTS;
KJ_REQUIRE(boundsCheck(segment, ptr, ptr + WordCount wordCount = roundBitsUpToWords(ElementCount64(elementCount) * step);
roundBitsUpToWords(ElementCount64(ref->listRef.elementCount()) * step)), KJ_REQUIRE(boundsCheck(segment, ptr, ptr + wordCount),
"Message contains out-of-bounds list pointer.") { "Message contains out-of-bounds list pointer.") {
goto useDefault; goto useDefault;
} }
if (elementSize == ElementSize::VOID) {
// Watch out for lists of void, which can claim to be arbitrarily large without having sent
// actual data.
KJ_REQUIRE(amplifiedRead(segment, elementCount * (1 * WORDS / ELEMENTS)),
"Message contains amplified list pointer.") {
goto useDefault;
}
}
if (checkElementSize) { if (checkElementSize) {
if (elementSize == ElementSize::BIT && expectedElementSize != ElementSize::BIT) { if (elementSize == ElementSize::BIT && expectedElementSize != ElementSize::BIT) {
KJ_FAIL_REQUIRE( KJ_FAIL_REQUIRE(
...@@ -2025,7 +2067,7 @@ struct WireHelpers { ...@@ -2025,7 +2067,7 @@ struct WireHelpers {
} }
} }
return ListReader(segment, ptr, ref->listRef.elementCount(), step, return ListReader(segment, ptr, elementCount, step,
dataSize, pointerCount, elementSize, nestingLimit - 1); dataSize, pointerCount, elementSize, nestingLimit - 1);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment