fuzz-test.c++ 9.05 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

22
#ifndef _GNU_SOURCE
Ivan Shynkarenka's avatar
Ivan Shynkarenka committed
23 24 25
#define _GNU_SOURCE
#endif

26 27 28 29 30
#include <capnp/test-import.capnp.h>
#include <capnp/test-import2.capnp.h>
#include "message.h"
#include "serialize.h"
#include <kj/test.h>
31
#include <stdlib.h>
32
#include <kj/miniposix.h>
33 34 35 36 37 38
#include "test-util.h"

namespace capnp {
namespace _ {  // private
namespace {

39 40 41 42 43 44
bool skipFuzzTest() {
  if (getenv("CAPNP_SKIP_FUZZ_TEST") != nullptr) {
    KJ_LOG(WARNING, "Skipping test because CAPNP_SKIP_FUZZ_TEST is set in environment.");
    return true;
  } else {
    return false;
45
  }
46
}
47

48 49 50 51 52 53 54 55 56 57
class DisableStackTraces: public kj::ExceptionCallback {
  // This test generates a lot of exceptions. Performing a backtrace on each one can be slow,
  // especially on Windows (where it is very, very slow). So, disable them.

public:
  StackTraceMode stackTraceMode() override {
    return StackTraceMode::NONE;
  }
};

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
uint64_t traverse(AnyPointer::Reader reader);
uint64_t traverse(AnyStruct::Reader reader);
uint64_t traverse(AnyList::Reader reader);

template <typename ListType>
uint64_t traverseList(ListType list) {
  // Traverse in reverse order in order to trigger segfaults before exceptions if we go
  // out-of-bounds.

  uint64_t result = 0;
  for (size_t i = list.size(); i != 0; i--) {
    result += traverse(list[i-1]);
  }
  return result;
}

uint64_t traverse(AnyStruct::Reader reader) {
  uint64_t result = 0;
  for (byte b: reader.getDataSection()) {
    result += b;
  }
  result += traverseList(reader.getPointerSection());
  return result;
}

uint64_t traverse(AnyList::Reader reader) {
  uint64_t result = 0;
  switch (reader.getElementSize()) {
    case ElementSize::VOID: break;
    case ElementSize::BIT:         for (auto e: reader.as<List<bool    >>()) result += e; break;
    case ElementSize::BYTE:        for (auto e: reader.as<List<uint8_t >>()) result += e; break;
    case ElementSize::TWO_BYTES:   for (auto e: reader.as<List<uint16_t>>()) result += e; break;
    case ElementSize::FOUR_BYTES:  for (auto e: reader.as<List<uint32_t>>()) result += e; break;
    case ElementSize::EIGHT_BYTES: for (auto e: reader.as<List<uint64_t>>()) result += e; break;
    case ElementSize::POINTER:
      traverseList(reader.as<List<AnyPointer>>());
      break;
    case ElementSize::INLINE_COMPOSITE:
      traverseList(reader.as<List<AnyStruct>>());
      break;
  }
  return result;
}

uint64_t traverse(AnyPointer::Reader reader) {
  if (reader.isStruct()) {
    return traverse(reader.getAs<AnyStruct>());
  } else if (reader.isList()) {
    return traverse(reader.getAs<AnyList>());
  } else {
    return 0;
  }
}

112
template <typename Checker>
113
void traverseCatchingExceptions(kj::ArrayPtr<const word> data) {
114 115 116
  // Try traversing through Checker.
  kj::runCatchingExceptions([&]() {
    FlatArrayMessageReader reader(data);
117
    KJ_ASSERT(Checker::check(reader) != 0) { break; }
118 119 120
  });

  // Try traversing through AnyPointer.
121 122
  kj::runCatchingExceptions([&]() {
    FlatArrayMessageReader reader(data);
123
    KJ_ASSERT(traverse(reader.getRoot<AnyPointer>()) != 0) { break; }
124 125
  });

126 127 128
  // Try counting the size..
  kj::runCatchingExceptions([&]() {
    FlatArrayMessageReader reader(data);
129
    KJ_ASSERT(reader.getRoot<AnyPointer>().targetSize().wordCount != 0) { break; }
130 131 132
  });

  // Try copying into a builder, and if that works, traversing it with Checker.
133 134 135 136 137
  static word buffer[8192];
  kj::runCatchingExceptions([&]() {
    FlatArrayMessageReader reader(data);
    MallocMessageBuilder copyBuilder(buffer);
    copyBuilder.setRoot(reader.getRoot<AnyPointer>());
138
    KJ_ASSERT(Checker::check(copyBuilder) != 0) { break; }
139 140 141
  });
}

142
template <typename Checker>
143 144
void fuzz(kj::ArrayPtr<word> data, uint flipCount, uint startAt, uint endAt) {
  if (flipCount == 0) {
145
    traverseCatchingExceptions<Checker>(data);
146 147 148 149 150
  } else {
    for (uint i = startAt; i < endAt; i++) {
      byte bit = 1u << (i % 8);
      byte old = data.asBytes()[i / 8];
      data.asBytes()[i / 8] |= bit;
151
      fuzz<Checker>(data, flipCount - 1, i + 1, endAt);
152
      data.asBytes()[i / 8] &= ~bit;
153
      fuzz<Checker>(data, flipCount - 1, i + 1, endAt);
154
      data.asBytes()[i / 8] = bit;
155
      fuzz<Checker>(data, flipCount - 1, i + 1, endAt);
156 157 158 159 160
      data.asBytes()[i / 8] = old;
    }
  }
}

161 162 163 164 165 166 167 168 169 170 171
struct StructChecker {
  template <typename ReaderOrBuilder>
  static uint check(ReaderOrBuilder& message) {
    uint result = 0;
    for (auto c: message.template getRoot<TestAllTypes>().getTextField()) {
      result += c;
    }
    return result;
  }
};

172
KJ_TEST("fuzz-test struct pointer") {
173
  if (skipFuzzTest()) return;
174
  DisableStackTraces disableStackTraces;
175

176
  MallocMessageBuilder builder;
177
  builder.getRoot<TestAllTypes>().setTextField("foo");
178
  KJ_ASSERT(builder.getSegmentsForOutput().size() == 1);
179
  fuzz<StructChecker>(messageToFlatArray(builder), 2, 64, 192);
180 181
}

182 183 184 185 186 187 188 189 190 191 192 193
struct ListChecker {
  template <typename ReaderOrBuilder>
  static uint check(ReaderOrBuilder& message) {
    uint result = 0;
    for (auto e: message.template getRoot<List<uint32_t>>()) {
      result += e;
    }
    return result;
  }
};

KJ_TEST("fuzz-test list pointer") {
194
  if (skipFuzzTest()) return;
195
  DisableStackTraces disableStackTraces;
196

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
  MallocMessageBuilder builder;
  auto list = builder.getRoot<AnyPointer>().initAs<List<uint32_t>>(2);
  list.set(0, 12345);
  list.set(1, 67890);
  fuzz<ListChecker>(messageToFlatArray(builder), 2, 64, 192);
}

struct StructListChecker {
  template <typename ReaderOrBuilder>
  static uint check(ReaderOrBuilder& message) {
    uint result = 0;
    auto l = message.template getRoot<List<TestAllTypes>>();
    for (size_t i = l.size(); i > 0; i--) {
      for (auto c: l[i-1].getTextField()) {
        result += c;
      }
    }
    return result;
  }
};

218
KJ_TEST("fuzz-test struct list pointer") {
219
  if (skipFuzzTest()) return;
220
  DisableStackTraces disableStackTraces;
221

222 223
  MallocMessageBuilder builder;
  auto list = builder.getRoot<AnyPointer>().initAs<List<test::TestAllTypes>>(2);
224 225
  list[0].setTextField("foo");
  list[1].setTextField("bar");
226 227
  KJ_ASSERT(builder.getSegmentsForOutput().size() == 1);

228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
  fuzz<StructListChecker>(messageToFlatArray(builder), 2, 64, 192);
}

struct TextChecker {
  template <typename ReaderOrBuilder>
  static uint check(ReaderOrBuilder& message) {
    uint result = 0;
    for (auto c: message.template getRoot<Text>()) {
      result += c;
    }
    return result;
  }
};

KJ_TEST("fuzz-test text pointer") {
243
  if (skipFuzzTest()) return;
244
  DisableStackTraces disableStackTraces;
245

246 247 248
  MallocMessageBuilder builder;
  builder.template getRoot<AnyPointer>().setAs<Text>("foo");
  fuzz<TextChecker>(messageToFlatArray(builder), 2, 64, 192);
249 250 251
}

KJ_TEST("fuzz-test far pointer") {
252
  if (skipFuzzTest()) return;
253
  DisableStackTraces disableStackTraces;
254

255 256 257 258 259 260 261
  MallocMessageBuilder builder(1, AllocationStrategy::FIXED_SIZE);
  initTestMessage(builder.getRoot<TestAllTypes>());

  uint segmentCount = builder.getSegmentsForOutput().size();
  uint tableSize = segmentCount / 2 + 1;

  // Fuzz the root far pointer plus its landing pad, which should be in the next word.
262
  fuzz<StructChecker>(messageToFlatArray(builder), 2, tableSize * 64, tableSize * 64 + 128);
263 264 265
}

KJ_TEST("fuzz-test double-far pointer") {
266
  if (skipFuzzTest()) return;
267
  DisableStackTraces disableStackTraces;
268

269 270 271 272 273 274 275 276 277 278 279 280 281
  MallocMessageBuilder builder(1, AllocationStrategy::FIXED_SIZE);

  // Carefully arrange for a double-far pointer to be created.
  auto root = builder.getRoot<AnyPointer>();
  root.adopt(builder.getOrphanage().newOrphanCopy(Text::Reader("foo")));

  // Verify that did what we expected.
  KJ_ASSERT(builder.getSegmentsForOutput().size() == 3);
  KJ_ASSERT(builder.getSegmentsForOutput()[0].size() == 1);  // root pointer
  KJ_ASSERT(builder.getSegmentsForOutput()[1].size() == 1);  // "foo"
  KJ_ASSERT(builder.getSegmentsForOutput()[2].size() == 2);  // double-far landing pad

  // Fuzz the root far pointer.
282
  fuzz<TextChecker>(messageToFlatArray(builder), 2, 64, 128);
283 284

  // Fuzz the landing pad.
285
  fuzz<TextChecker>(messageToFlatArray(builder), 2, 192, 320);
286 287 288 289 290
}

}  // namespace
}  // namespace _ (private)
}  // namespace capnp