CVE-2015-2313 (Other, published 2017-08-09T18:29Z)
Description: value: Sandstorm Cap'n Proto before 0.4.1.1 and 0.5.x before 0.5.1.2, when an application invokes the totalSize method on an object reader, allows remote peers to cause a denial of service (CPU consumption) via a crafted small message, which triggers a "tight" for loop.  NOTE: this vulnerability exists because of an incomplete fix for CVE-2015-2312.
Severity: HIGH (CVSS v3 base score: 7.5)
Weakness: CWE-400 \u2014 Uncontrolled Resource Consumption
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.
Repository: https://github.com/capnproto/capnproto
Files changed:
c++/src/capnp/encoding-test.c++
c++/src/capnp/layout.c++
Commit message: SECURITY: Additional CPU amplification case.

Unfortunately, commit 104870608fde3c698483fdef6b97f093fc15685d missed a case of CPU amplification via struct lists with zero-sized elements.

See advisory: https://github.com/sandstorm-io/capnproto/blob/master/security-advisories/2015-03-05-0-c++-addl-cpu-amplification.md


Vulnerable code:
  MallocMessageBuilder builder;
  builder.initRoot<test::TestAnyPointer>().getAnyPointerField()
      .initAs<List<test::TestEmptyStruct>>(1u << 28);
  EXPECT_EQ(1, segments.size());
  EXPECT_LT(segments[0].size(), 16);  // quite small for such a big list!
  auto root = reader.getRoot<test::TestAnyPointer>().getAnyPointerField();
  EXPECT_NONFATAL_FAILURE(root.getAs<List<TestAllTypes>>());
            word* pos = ptr + POINTER_SIZE_IN_WORDS;
            for (uint i = 0; i < count; i++) {
              pos += dataSize;

              for (uint j = 0; j < pointerCount / POINTERS; j++) {
                zeroObject(segment, reinterpret_cast<WirePointer*>(pos));
                pos += POINTER_SIZE_IN_WORDS;
            result.wordCount += wordCount + POINTER_SIZE_IN_WORDS;

            KJ_REQUIRE(elementTag->structRef.wordSize() / ELEMENTS *
                       ElementCount64(count) <= wordCount,
            const word* pos = ptr + POINTER_SIZE_IN_WORDS;
            for (uint i = 0; i < count / ELEMENTS; i++) {
              pos += dataSize;
              for (uint j = 0; j < pointerCount / POINTERS; j++) {
                result += totalSize(segment, reinterpret_cast<const WirePointer*>(pos),
                                    nestingLimit);
                pos += POINTER_SIZE_IN_WORDS;

Fixed code:
  MallocMessageBuilder builder(1024);
  auto listList = builder.initRoot<test::TestAnyPointer>().getAnyPointerField()
      .initAs<List<List<test::TestEmptyStruct>>>(500);

  for (uint i = 0; i < listList.size(); i++) {
    listList.init(i, 1u << 28);
  }
  ASSERT_EQ(1, segments.size());
  auto root = reader.getRoot<test::TestAnyPointer>();
  auto listListReader = root.getAnyPointerField().getAs<List<List<TestAllTypes>>>();
  EXPECT_NONFATAL_FAILURE(listListReader[0]);
  EXPECT_NONFATAL_FAILURE(listListReader[10]);

  EXPECT_EQ(segments[0].size() - 1, root.totalSize().wordCount);
            if (pointerCount > 0 * POINTERS) {
              word* pos = ptr + POINTER_SIZE_IN_WORDS;
              for (uint i = 0; i < count; i++) {
                pos += dataSize;

                for (uint j = 0; j < pointerCount / POINTERS; j++) {
                  zeroObject(segment, reinterpret_cast<WirePointer*>(pos));
                  pos += POINTER_SIZE_IN_WORDS;
                }
            auto actualSize = elementTag->structRef.wordSize() / ELEMENTS * ElementCount64(count);
            KJ_REQUIRE(actualSize <= wordCount,
            // We count the actual size rather than the claimed word count because that's what
            // we'll end up with if we make a copy.
            result.wordCount += actualSize + POINTER_SIZE_IN_WORDS;

            if (pointerCount > 0 * POINTERS) {
              const word* pos = ptr + POINTER_SIZE_IN_WORDS;
              for (uint i = 0; i < count / ELEMENTS; i++) {
                pos += dataSize;
                for (uint j = 0; j < pointerCount / POINTERS; j++) {
                  result += totalSize(segment, reinterpret_cast<const WirePointer*>(pos),
                                      nestingLimit);
                  pos += POINTER_SIZE_IN_WORDS;
                }