Commit 2ca8e411 authored by Kenton Varda's avatar Kenton Varda

Refactor bounds checks to avoid ever creating out-of-bounds pointer values,…

Refactor bounds checks to avoid ever creating out-of-bounds pointer values, which is technically UB even if not dereferenced.
parent 52bc9564
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <vector> #include <vector>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#if !CAPNP_LITE #if !CAPNP_LITE
#include "capability.h" #include "capability.h"
...@@ -48,6 +49,12 @@ void ReadLimiter::unread(WordCount64 amount) { ...@@ -48,6 +49,12 @@ void ReadLimiter::unread(WordCount64 amount) {
} }
} }
void SegmentReader::abortCheckObjectFault() {
KJ_LOG(FATAL, "checkObject()'s parameter is not in-range; this would segfault in opt mode",
"this is a serious bug in Cap'n Proto; please notify security@sandstorm.io");
abort();
}
void SegmentBuilder::throwNotWritable() { void SegmentBuilder::throwNotWritable() {
KJ_FAIL_REQUIRE( KJ_FAIL_REQUIRE(
"Tried to form a Builder to an external data segment referenced by the MessageBuilder. " "Tried to form a Builder to an external data segment referenced by the MessageBuilder. "
......
...@@ -117,7 +117,20 @@ public: ...@@ -117,7 +117,20 @@ public:
inline SegmentReader(Arena* arena, SegmentId id, const word* ptr, SegmentWordCount size, inline SegmentReader(Arena* arena, SegmentId id, const word* ptr, SegmentWordCount size,
ReadLimiter* readLimiter); ReadLimiter* readLimiter);
KJ_ALWAYS_INLINE(bool containsInterval(const void* from, const void* to)); KJ_ALWAYS_INLINE(const word* checkOffset(const word* from, ptrdiff_t offset));
// Adds the given offset to the given pointer, checks that it is still within the bounds of the
// segment, then returns it. Note that the "end" pointer of the segment (which technically points
// to the word after the last in the segment) is considered in-bounds for this purpose, so you
// can't necessarily dereference it. You must call checkObject() next to check that the object
// you want to read is entirely in-bounds.
//
// If `from + offset` is out-of-range, this returns a pointer to the end of the segment. Thus,
// any non-zero-sized object will fail `checkObject()`. We do this instead of throwing to save
// some code footprint.
KJ_ALWAYS_INLINE(bool checkObject(const word* start, WordCountN<31> size));
// Assuming that `start` is in-bounds for this segment (probably checked using `checkOffset()`),
// check that `start + size` is also in-bounds, and hence the whole area in-between is valid.
KJ_ALWAYS_INLINE(bool amplifiedRead(WordCount virtualAmount)); KJ_ALWAYS_INLINE(bool amplifiedRead(WordCount virtualAmount));
// Indicates that the reader should pretend that `virtualAmount` additional data was read even // Indicates that the reader should pretend that `virtualAmount` additional data was read even
...@@ -147,6 +160,9 @@ private: ...@@ -147,6 +160,9 @@ private:
KJ_DISALLOW_COPY(SegmentReader); KJ_DISALLOW_COPY(SegmentReader);
friend class SegmentBuilder; friend class SegmentBuilder;
static void abortCheckObjectFault();
// Called in debug mode in cases that would segfault in opt mode. (Should be impossible!)
}; };
class SegmentBuilder: public SegmentReader { class SegmentBuilder: public SegmentReader {
...@@ -367,18 +383,25 @@ inline SegmentReader::SegmentReader(Arena* arena, SegmentId id, const word* ptr, ...@@ -367,18 +383,25 @@ inline SegmentReader::SegmentReader(Arena* arena, SegmentId id, const word* ptr,
: arena(arena), id(id), ptr(kj::arrayPtr(ptr, unbound(size / WORDS))), : arena(arena), id(id), ptr(kj::arrayPtr(ptr, unbound(size / WORDS))),
readLimiter(readLimiter) {} readLimiter(readLimiter) {}
inline bool SegmentReader::containsInterval(const void* from, const void* to) { inline const word* SegmentReader::checkOffset(const word* from, ptrdiff_t offset) {
uintptr_t start = reinterpret_cast<uintptr_t>(from) - reinterpret_cast<uintptr_t>(ptr.begin()); ptrdiff_t min = ptr.begin() - from;
uintptr_t end = reinterpret_cast<uintptr_t>(to) - reinterpret_cast<uintptr_t>(ptr.begin()); ptrdiff_t max = ptr.end() - from;
uintptr_t bound = ptr.size() * sizeof(capnp::word); if (offset >= min && offset <= max) {
return from + offset;
return start <= bound && end <= bound && start <= end && } else {
readLimiter->canRead( return ptr.end();
intervalLength(reinterpret_cast<const byte*>(from), }
reinterpret_cast<const byte*>(to), }
MAX_SEGMENT_WORDS * BYTES_PER_WORD)
/ BYTES_PER_WORD, inline bool SegmentReader::checkObject(const word* start, WordCountN<31> size) {
arena); auto startOffset = intervalLength(ptr.begin(), start, MAX_SEGMENT_WORDS);
#ifdef KJ_DEBUG
if (startOffset > bounded(ptr.size()) * WORDS) {
abortCheckObjectFault();
}
#endif
return startOffset + size <= bounded(ptr.size()) * WORDS &&
readLimiter->canRead(size, arena);
} }
inline bool SegmentReader::amplifiedRead(WordCount virtualAmount) { inline bool SegmentReader::amplifiedRead(WordCount virtualAmount) {
......
This diff is collapsed.
...@@ -95,7 +95,7 @@ AnyPointer::Reader MessageReader::getRootInternal() { ...@@ -95,7 +95,7 @@ AnyPointer::Reader MessageReader::getRootInternal() {
_::SegmentReader* segment = arena()->tryGetSegment(_::SegmentId(0)); _::SegmentReader* segment = arena()->tryGetSegment(_::SegmentId(0));
KJ_REQUIRE(segment != nullptr && KJ_REQUIRE(segment != nullptr &&
segment->containsInterval(segment->getStartPtr(), segment->getStartPtr() + 1), segment->checkObject(segment->getStartPtr(), ONE * WORDS),
"Message did not contain a root pointer.") { "Message did not contain a root pointer.") {
return AnyPointer::Reader(); return AnyPointer::Reader();
} }
......
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