CVE-2015-2312 (published 2017-08-09T18:29Z)

Sandstorm Cap'n Proto before 0.4.1.1 and 0.5.x before 0.5.1.1 allows remote peers to cause a denial of service (CPU and possibly general resource consumption) via a list with a large number of elements.

Severity: HIGH
CVSS v2 base score: 7.8 (AV:N/AC:L/Au:N/C:N/I:N/A:C)
CVSS v3 base score: 7.5, HIGH (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-400
cwe_name: Uncontrolled Resource Consumption
description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
extended_description: 
            Limited resources include memory, file system storage, database connection pool entries, and CPU. If an attacker can trigger the allocation of these limited resources, but the number or size of the resources is not controlled, then the attacker could cause a denial of service that consumes all available resources. This would prevent valid users from accessing the product, and it could potentially have an impact on the surrounding environment. For example, a memory exhaustion attack against an application could slow down the application as well as its host operating system.
            There are at least three distinct scenarios which can commonly lead to resource exhaustion:
               
                  Lack of throttling for the number of allocated resources
                  Losing all references to a resource before reaching the shutdown stage
                  Not closing/returning a resource after processing
               
            Resource exhaustion problems are often result due to an incorrect implementation of the following situations:
               
                  Error conditions and other exceptional circumstances.
                  Confusion over which part of the program is responsible for releasing the resource.
               
         
         

Fix information:
repo_url: https://github.com/capnproto/capnproto
commit_details: repo_url: https://github.com/capnproto/capnproto
author: Kenton Varda
author_date: 2015-02-27 20:23:13-08:00
msg: SECURITY: CPU usage amplification attack.

Details: https://github.com/sandstorm-io/capnproto/tree/master/security-advisories/2014-03-02-0-all-cpu-amplification.md
repository: repo_url: https://github.com/capnproto/capnproto
repo_name: capnproto/capnproto
description: Cap'n Proto serialization/RPC system - core tools and C++ library
repo_language: C++
owner: capnproto
file_changes: filename: CONTRIBUTORS
change_type: ModificationType.MODIFY
diff: @@ -10,6 +10,7 @@ Scott Purdy <scott@fer.io>: kj/std iostream interface
 Bryan Borham <bjboreham@gmail.com>: Initial MSVC support
 Philip Quinn <p@partylemon.com>: cmake build and other assorted bits
 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
 implementations as separate projects.  Those people are awesome too!  :)

programming_language: Markdown
filename: arena.h
change_type: ModificationType.MODIFY
diff: @@ -117,6 +117,13 @@ public:
 
   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 SegmentId getSegmentId();
 
@@ -367,6 +374,10 @@ inline bool SegmentReader::containsInterval(const void* from, const void* to) {
           arena);
 }
 
+inline bool SegmentReader::amplifiedRead(WordCount virtualAmount) {
+  return readLimiter->canRead(virtualAmount, arena);
+}
+
 inline Arena* SegmentReader::getArena() { return arena; }
 inline SegmentId SegmentReader::getSegmentId() { return id; }
 inline const word* SegmentReader::getStartPtr() { return ptr.begin(); }

programming_language: C++
filename: encoding-test.c++
change_type: ModificationType.MODIFY
diff: @@ -1410,6 +1410,36 @@ TEST(Encoding, Has) {
   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) {
   EXPECT_EQ(VOID, test::TestConstants::VOID_CONST);
   EXPECT_EQ(true, test::TestConstants::BOOL_CONST);

programming_language: C++
filename: layout.c++
change_type: ModificationType.MODIFY
diff: @@ -308,6 +308,11 @@ struct WireHelpers {
     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(
       WirePointer*& ref, SegmentBuilder*& segment, WordCount amount,
       WirePointer::Kind kind, BuilderArena* orphanArena)) {
@@ -1675,6 +1680,15 @@ struct WireHelpers {
             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,
               ListReader(srcSegment, ptr, elementCount, wordsPerElement * BITS_PER_WORD,
                          tag->structRef.dataSize.get() * BITS_PER_WORD,
@@ -1693,6 +1707,15 @@ struct WireHelpers {
             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,
               ListReader(srcSegment, ptr, elementCount, step, dataSize, pointerCount, elementSize,
                          nestingLimit - 1),
@@ -1931,6 +1954,15 @@ struct WireHelpers {
         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 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
@@ -1988,14 +2020,24 @@ struct WireHelpers {
       BitCount dataSize = dataBitsPerElement(ref->listRef.elementSize()) * ELEMENTS;
       WirePointerCount pointerCount =
           pointersPerElement(ref->listRef.elementSize()) * ELEMENTS;
+      ElementCount elementCount = ref->listRef.elementCount();
       auto step = (dataSize + pointerCount * BITS_PER_POINTER) / ELEMENTS;
 
-      KJ_REQUIRE(boundsCheck(segment, ptr, ptr +
-                     roundBitsUpToWords(ElementCount64(ref->listRef.elementCount()) * step)),
+      WordCount wordCount = roundBitsUpToWords(ElementCount64(elementCount) * step);
+      KJ_REQUIRE(boundsCheck(segment, ptr, ptr + wordCount),
                  "Message contains out-of-bounds list pointer.") {
         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 (elementSize == ElementSize::BIT && expectedElementSize != ElementSize::BIT) {
           KJ_FAIL_REQUIRE(
@@ -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);
     }
   }

programming_language: C++