summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllen Webb <allenwebb@google.com>2018-08-21 12:11:38 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-12-03 12:43:22 -0800
commita5e1a639e55d1c6382b4d690c6b78f6f85e8fbc9 (patch)
tree04ea72cd9750bc6b3e792550f7fd9515186a3636
parentb343c963b38b03df97a1bc57f201e26640c89e47 (diff)
downloadchrome-ec-a5e1a639e55d1c6382b4d690c6b78f6f85e8fbc9.tar.gz
cr50_fuzz: Add libprotobuf-mutator support.
This uses protocol buffers to model what actions can be taken with pinweaver at a higher level of abstraction than the raw requests to greatly increase the coverage that can be achieved by fuzzing, while still allowing for invalid inputs to be checked. BRANCH=none BUG=chromium:876582 TEST=sudo emerge libprotobuf-mutator && make -j buildfuzztests && ./build/host/cr50_fuzz/cr50_fuzz.exe Change-Id: Ie7ce569650ca06866f277f36eae61df2684de60c Signed-off-by: Allen Webb <allenwebb@google.com> Reviewed-on: https://chromium-review.googlesource.com/1184107 Reviewed-by: Mattias Nissler <mnissler@chromium.org> Reviewed-by: Mike Frysinger <vapier@chromium.org>
-rw-r--r--fuzz/build.mk12
-rw-r--r--fuzz/cr50_fuzz.cc135
-rw-r--r--fuzz/cr50_fuzz.proto31
-rw-r--r--fuzz/fuzz_config.h4
-rw-r--r--fuzz/mem_hash_tree.cc130
-rw-r--r--fuzz/mem_hash_tree.h57
-rw-r--r--fuzz/pinweaver/pinweaver.proto64
-rw-r--r--fuzz/pinweaver_model.cc474
-rw-r--r--fuzz/pinweaver_model.h123
-rw-r--r--fuzz/span.h56
10 files changed, 1025 insertions, 61 deletions
diff --git a/fuzz/build.mk b/fuzz/build.mk
index 5e297b3d66..ea4b18939b 100644
--- a/fuzz/build.mk
+++ b/fuzz/build.mk
@@ -20,8 +20,14 @@ fuzz-test-list-host = cr50_fuzz host_command_fuzz
# Does your object file need to link against cstdlib?
# Yes -> use <obj_name>-rw
# Otherwise use <obj_name>-y
-cr50_fuzz-rw = cr50_fuzz.o
+cr50_fuzz-rw = cr50_fuzz.o pinweaver_model.o mem_hash_tree.o
host_command_fuzz-y = host_command_fuzz.o
-$(out)/cr50_fuzz.exe: $(out)/cryptoc/libcryptoc.a
-$(out)/cr50_fuzz.exe: LDFLAGS_EXTRA+=-lcrypto
+$(out)/RW/fuzz/cr50_fuzz.o: $(out)/gen/fuzz/cr50_fuzz.pb.h
+$(out)/RW/fuzz/cr50_fuzz.o: CPPFLAGS+=${LIBPROTOBUF_MUTATOR_CFLAGS}
+
+$(out)/cr50_fuzz.exe: $(out)/cryptoc/libcryptoc.a \
+ $(out)/gen/fuzz/cr50_fuzz.pb.o \
+ $(out)/gen/fuzz/pinweaver/pinweaver.pb.o \
+
+$(out)/cr50_fuzz.exe: LDFLAGS_EXTRA+=-lcrypto ${LIBPROTOBUF_MUTATOR_LDLIBS}
diff --git a/fuzz/cr50_fuzz.cc b/fuzz/cr50_fuzz.cc
index 90b8350fc2..dcd80e93e9 100644
--- a/fuzz/cr50_fuzz.cc
+++ b/fuzz/cr50_fuzz.cc
@@ -1,78 +1,97 @@
-/* Copyright 2018 The Chromium OS Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- *
- * Fuzzer for the TPM2 and vendor specific Cr50 commands.
- */
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Fuzzer for the TPM2 and vendor specific Cr50 commands.
#include <unistd.h>
#include <cstdint>
-#include <cstdlib>
#include <cstring>
+#include <unordered_map>
+#include <vector>
+
+#include <src/libfuzzer/libfuzzer_macro.h>
+#include <src/mutator.h>
#define HIDE_EC_STDLIB
-#include "fuzz_config.h"
-#include "nvmem.h"
-#include "nvmem_vars.h"
-#include "persistence.h"
-#include "pinweaver.h"
-
-#define NVMEM_TPM_SIZE ((sizeof((struct nvmem_partition *)0)->buffer) \
- - NVMEM_CR50_SIZE)
-
-extern "C" uint32_t nvmem_user_sizes[NVMEM_NUM_USERS] = {
- NVMEM_TPM_SIZE,
- NVMEM_CR50_SIZE
-};
-
-extern "C" void rand_bytes(void *buffer, size_t len)
-{
- size_t x = 0;
-
- for (; x < len; ++x)
- ((uint8_t *)buffer)[x] = rand();
+#include "chip/host/persistence.h"
+#include "fuzz/cr50_fuzz.pb.h"
+#include "fuzz/fuzz_config.h"
+#include "fuzz/pinweaver_model.h"
+#include "fuzz/span.h"
+#include "include/nvmem.h"
+#include "include/nvmem_vars.h"
+#include "include/pinweaver.h"
+
+using protobuf_mutator::libfuzzer::LoadProtoInput;
+
+namespace {
+constexpr size_t kBufferAlignment = alignof(pw_request_t) >
+ alignof(pw_response_t)
+ ? alignof(pw_request_t)
+ : alignof(pw_response_t);
+} // namespace
+
+extern "C" uint32_t nvmem_user_sizes[NVMEM_NUM_USERS] = {NVMEM_TPM_SIZE,
+ NVMEM_CR50_SIZE};
+
+extern "C" void rand_bytes(void* data, size_t len) {
+ size_t x = 0;
+
+ uint8_t* buffer = reinterpret_cast<uint8_t*>(data);
+ for (; x < len; ++x) {
+ buffer[x] = rand();
+ }
}
-extern "C" void get_storage_seed(void *buf, size_t *len)
-{
- memset(buf, 0x77, *len);
+extern "C" void get_storage_seed(void* buf, size_t* len) {
+ memset(buf, 0x77, *len);
}
extern "C" uint8_t get_current_pcr_digest(const uint8_t bitmask[2],
- uint8_t sha256_of_selected_pcr[32])
-{
- memset(sha256_of_selected_pcr, 0, 32);
- return 0;
+ uint8_t sha256_of_selected_pcr[32]) {
+ memset(sha256_of_selected_pcr, 0, 32);
+ return 0;
}
-extern "C" void run_test(void)
-{
-}
+// Needed for test targets to build.
+extern "C" void run_test(void) {}
-static void assign_pw_field_from_bytes(const uint8_t *data, unsigned int size,
- uint8_t *destination, size_t dest_size)
-{
- if (size >= dest_size) {
- memcpy(destination, data, dest_size);
- } else {
- memcpy(destination, data, size);
- memset(destination + size, 0, dest_size - size);
- }
+void InitializeFuzzerRun() {
+ memset(__host_flash, 0xff, sizeof(__host_flash));
+ nvmem_init();
+ nvmem_enable_commits();
+ initvars();
+ srand(0);
}
-/* Prevent this from being stack allocated. */
-static uint8_t tpm_io_buffer[PW_MAX_MESSAGE_SIZE];
+DEFINE_CUSTOM_PROTO_MUTATOR_IMPL(false, fuzz::Cr50FuzzerInput)
+DEFINE_CUSTOM_PROTO_CROSSOVER_IMPL(false, fuzz::Cr50FuzzerInput)
+
+extern "C" int test_fuzz_one_input(const uint8_t* data, unsigned int size) {
+ fuzz::Cr50FuzzerInput input;
+ if (!LoadProtoInput(false, data, size, &input)) {
+ return 0;
+ }
-extern "C" int test_fuzz_one_input(const uint8_t *data, unsigned int size)
-{
- struct merkle_tree_t merkle_tree = {};
- struct pw_request_t *request = (struct pw_request_t *)tpm_io_buffer;
- struct pw_response_t *response = (struct pw_response_t *)tpm_io_buffer;
+ InitializeFuzzerRun();
- memset(__host_flash, 0xff, sizeof(__host_flash));
- pinweaver_init();
- assign_pw_field_from_bytes(data, size, tpm_io_buffer, sizeof(tpm_io_buffer));
- pw_handle_request(&merkle_tree, request, response);
- return 0;
+ PinweaverModel pinweaver_model;
+ alignas(kBufferAlignment) uint8_t buffer[PW_MAX_MESSAGE_SIZE] = {};
+ fuzz::span<uint8_t> buffer_view(buffer, sizeof(buffer));
+ for (const fuzz::Cr50SubAction& action : input.sub_actions()) {
+ switch (action.sub_action_case()) {
+ case fuzz::Cr50SubAction::kRandomBytes:
+ fuzz::CopyWithPadding(action.random_bytes().value(), buffer_view, 0);
+ pinweaver_model.SendBuffer(buffer_view);
+ break;
+ case fuzz::Cr50SubAction::kPinweaver:
+ pinweaver_model.ApplyRequest(action.pinweaver(), buffer_view);
+ break;
+ case fuzz::Cr50SubAction::SUB_ACTION_NOT_SET:
+ break;
+ }
+ }
+ return 0;
}
diff --git a/fuzz/cr50_fuzz.proto b/fuzz/cr50_fuzz.proto
new file mode 100644
index 0000000000..0291eacd88
--- /dev/null
+++ b/fuzz/cr50_fuzz.proto
@@ -0,0 +1,31 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto3";
+
+package fuzz;
+
+import public "fuzz/pinweaver/pinweaver.proto";
+
+message RandomBytes {
+ bytes value = 1;
+}
+
+message Cr50SubAction {
+ // Allows a logical representation of an action (PinWeaver) or a literal
+ // representation (RandomBytes). The logical representation fills out the
+ // expected values of particular fields when they are empty or not part of the
+ // proto so that the fuzzer can reach parts of the code without having to
+ // brute force an HMAC. The literal representation allows for the fuzzer to
+ // represent inputs that cannot be represented with the logical
+ // representation.
+ oneof sub_action {
+ RandomBytes random_bytes = 1;
+ pinweaver.Request pinweaver = 2;
+ }
+}
+
+message Cr50FuzzerInput {
+ repeated Cr50SubAction sub_actions = 1;
+}
diff --git a/fuzz/fuzz_config.h b/fuzz/fuzz_config.h
index 9a64166fa2..bcf7284ac4 100644
--- a/fuzz/fuzz_config.h
+++ b/fuzz/fuzz_config.h
@@ -61,6 +61,10 @@ enum nvmem_users {
NVMEM_NUM_USERS
};
#endif
+
+#define NVMEM_TPM_SIZE \
+ (sizeof(((nvmem_partition *)(0))->buffer) - NVMEM_CR50_SIZE)
+
#define CONFIG_FLASH_NVMEM_VARS_USER_NUM NVMEM_CR50
/******************************************************************************/
diff --git a/fuzz/mem_hash_tree.cc b/fuzz/mem_hash_tree.cc
new file mode 100644
index 0000000000..15c9de4142
--- /dev/null
+++ b/fuzz/mem_hash_tree.cc
@@ -0,0 +1,130 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuzz/mem_hash_tree.h"
+
+#include <algorithm>
+#include <cassert>
+
+MemHashTree::MemHashTree() : bits_per_level_(0), height_(0) {}
+
+bool MemHashTree::GetLeaf(uint64_t label, fuzz::span<uint8_t> leaf_hash) const {
+ assert(leaf_hash.size() >= SHA256_DIGEST_SIZE);
+ auto itr = hash_tree_.find(MaskedLabel(label, 0));
+ if (itr == hash_tree_.end()) {
+ std::fill(leaf_hash.begin(), leaf_hash.end(), 0);
+ return false;
+ }
+
+ std::copy(itr->second.begin(), itr->second.end(), leaf_hash.begin());
+ return true;
+}
+
+size_t MemHashTree::GetPath(uint64_t label,
+ fuzz::span<uint8_t> path_hashes) const {
+ uint8_t fan_out = 1 << bits_per_level_;
+ uint8_t num_siblings = fan_out - 1;
+ assert(path_hashes.size() >= num_siblings * height_ * SHA256_DIGEST_SIZE);
+ // num_siblings and child_index_mask have the same value, but were named
+ // differently to help convey how they are used.
+ uint64_t child_index_mask = fan_out - 1;
+ uint64_t shifted_parent_label = label;
+ uint8_t* dest_itr = path_hashes.begin();
+ for (uint8_t level = 0; level < height_; ++level) {
+ uint8_t label_index = shifted_parent_label & child_index_mask;
+ shifted_parent_label &= ~child_index_mask;
+ for (uint8_t index = 0; index < fan_out; ++index) {
+ // Only include hashes for sibling nodes.
+ if (index == label_index) {
+ continue;
+ }
+ auto src_itr =
+ hash_tree_.find(MaskedLabel(shifted_parent_label | index, level));
+ if (src_itr == hash_tree_.end()) {
+ std::copy(empty_node_hashes_[level].begin(),
+ empty_node_hashes_[level].end(), dest_itr);
+ } else {
+ std::copy(src_itr->second.begin(), src_itr->second.end(), dest_itr);
+ }
+ dest_itr += SHA256_DIGEST_SIZE;
+ }
+ shifted_parent_label = shifted_parent_label >> bits_per_level_;
+ }
+ return dest_itr - path_hashes.begin();
+}
+
+void MemHashTree::UpdatePath(uint64_t label,
+ fuzz::span<const uint8_t> path_hash) {
+ std::array<uint8_t, SHA256_DIGEST_SIZE> hash;
+ if (path_hash.empty()) {
+ std::fill(hash.begin(), hash.end(), 0);
+ hash_tree_.erase(MaskedLabel(label, 0));
+ } else {
+ assert(path_hash.size() == SHA256_DIGEST_SIZE);
+ std::copy(path_hash.begin(), path_hash.end(), hash.begin());
+ hash_tree_[MaskedLabel(label, 0)] = hash;
+ }
+
+ uint8_t fan_out = 1 << bits_per_level_;
+ uint64_t child_index_mask = fan_out - 1;
+ uint64_t shifted_parent_label = label;
+ for (int level = 0; level < height_; ++level) {
+ shifted_parent_label &= ~child_index_mask;
+
+ LITE_SHA256_CTX ctx;
+ DCRYPTO_SHA256_init(&ctx, 1);
+ int empty_nodes = 0;
+ for (int index = 0; index < fan_out; ++index) {
+ auto itr =
+ hash_tree_.find(MaskedLabel(shifted_parent_label | index, level));
+ if (itr == hash_tree_.end()) {
+ HASH_update(&ctx, empty_node_hashes_[level].data(),
+ empty_node_hashes_[level].size());
+ ++empty_nodes;
+ } else {
+ HASH_update(&ctx, itr->second.data(), itr->second.size());
+ }
+ }
+ shifted_parent_label = shifted_parent_label >> bits_per_level_;
+
+ const uint8_t* temp = HASH_final(&ctx);
+ std::copy(temp, temp + SHA256_DIGEST_SIZE, hash.begin());
+ MaskedLabel node_key(shifted_parent_label, level + 1);
+ if (empty_nodes == fan_out) {
+ hash_tree_.erase(node_key);
+ } else {
+ hash_tree_[node_key] = hash;
+ }
+ }
+}
+
+void MemHashTree::Reset() {
+ bits_per_level_ = 0;
+ height_ = 0;
+ empty_node_hashes_.clear();
+ hash_tree_.clear();
+}
+
+void MemHashTree::Reset(uint8_t bits_per_level, uint8_t height) {
+ bits_per_level_ = bits_per_level;
+ height_ = height;
+ hash_tree_.clear();
+ empty_node_hashes_.resize(height);
+
+ std::array<uint8_t, SHA256_DIGEST_SIZE> hash;
+ std::fill(hash.begin(), hash.end(), 0);
+ empty_node_hashes_[0] = hash;
+
+ uint8_t fan_out = 1 << bits_per_level;
+ for (int level = 1; level < height; ++level) {
+ LITE_SHA256_CTX ctx;
+ DCRYPTO_SHA256_init(&ctx, 1);
+ for (int index = 0; index < fan_out; ++index) {
+ HASH_update(&ctx, hash.data(), hash.size());
+ }
+ const uint8_t* temp = HASH_final(&ctx);
+ std::copy(temp, temp + SHA256_DIGEST_SIZE, hash.begin());
+ empty_node_hashes_[level] = hash;
+ }
+}
diff --git a/fuzz/mem_hash_tree.h b/fuzz/mem_hash_tree.h
new file mode 100644
index 0000000000..daf1b14b2b
--- /dev/null
+++ b/fuzz/mem_hash_tree.h
@@ -0,0 +1,57 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef __FUZZ_MEM_HASH_TREE_H
+#define __FUZZ_MEM_HASH_TREE_H
+#include <unistd.h>
+
+#include <array>
+#include <cstdint>
+#include <unordered_map>
+#include <vector>
+
+#include "board/host/dcrypto.h"
+#include "fuzz/span.h"
+
+// MaskedLabel.first is the label path, this is shifted to the right by the
+// (bits_per_level * level)
+// MaskedLabel.second is the level of the label (0 for leaf, height for root)
+typedef std::pair<uint64_t, uint8_t> MaskedLabel;
+
+namespace std {
+template <>
+struct hash<MaskedLabel> {
+ size_t operator()(const MaskedLabel& lbl) const {
+ static const auto hash_first = hash<uint64_t>();
+ static const auto hash_second = hash<uint8_t>();
+ return hash_first(lbl.first) * hash_second(lbl.second);
+ }
+};
+} // namespace std
+
+class MemHashTree {
+ public:
+ MemHashTree();
+
+ bool GetLeaf(uint64_t label, fuzz::span<uint8_t> leaf_hash) const;
+ // Writes the result to |path_hashes| and returns the size in bytes of the
+ // returned path for use in serializers that report how much buffer was used.
+ size_t GetPath(uint64_t label, fuzz::span<uint8_t> path_hashes) const;
+ // Updates the hashes in the path of the specified leaf. If |path_hash| is
+ // empty, the entry in hash_tree_ is deleted representing an empty leaf.
+ void UpdatePath(uint64_t label, fuzz::span<const uint8_t> path_hash);
+
+ void Reset();
+ void Reset(uint8_t bits_per_level, uint8_t height);
+
+ private:
+ uint8_t bits_per_level_;
+ uint8_t height_;
+
+ // Only contains hashes for non empty paths in the tree.
+ std::unordered_map<MaskedLabel, std::array<uint8_t, 32>> hash_tree_;
+ std::vector<std::array<uint8_t, 32>> empty_node_hashes_;
+};
+
+#endif // __FUZZ_MEM_HASH_TREE_H
diff --git a/fuzz/pinweaver/pinweaver.proto b/fuzz/pinweaver/pinweaver.proto
new file mode 100644
index 0000000000..40e74f71de
--- /dev/null
+++ b/fuzz/pinweaver/pinweaver.proto
@@ -0,0 +1,64 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto3";
+
+package fuzz.pinweaver;
+
+import public "google/protobuf/wrappers.proto";
+
+message ResetTree {
+ uint32 bits_per_level = 1;
+ uint32 height = 2;
+}
+
+message InsertLeaf {
+ uint64 label = 1;
+ bytes delay_schedule = 2;
+ bytes low_entropy_secret = 3;
+ bytes high_entropy_secret = 4;
+ bytes reset_secret = 5;
+ bytes path_hashes = 6;
+}
+
+message RemoveLeaf {
+ uint64 label = 1;
+ bytes leaf_hmac = 2;
+ bytes path_hashes = 3;
+}
+
+message TryAuth {
+ uint64 label = 1;
+ bytes low_entropy_secret = 2;
+ bytes unimported_leaf_data = 3;
+}
+
+message ResetAuth {
+ uint64 label = 1;
+ bytes reset_secret = 2;
+ bytes unimported_leaf_data = 3;
+}
+
+message GetLog {
+ uint32 index_of_root = 1;
+}
+
+message LogReplay {
+ uint32 index_of_root = 1;
+ bytes unimported_leaf_data = 2;
+}
+
+message Request {
+ // A work around to provide the has_version() function.
+ google.protobuf.UInt32Value version = 1;
+ oneof request {
+ ResetTree reset_tree = 2;
+ InsertLeaf insert_leaf = 3;
+ RemoveLeaf remove_leaf = 4;
+ TryAuth try_auth = 5;
+ ResetAuth reset_auth = 6;
+ GetLog get_log = 7;
+ LogReplay log_replay = 8;
+ }
+}
diff --git a/fuzz/pinweaver_model.cc b/fuzz/pinweaver_model.cc
new file mode 100644
index 0000000000..43618e1fa4
--- /dev/null
+++ b/fuzz/pinweaver_model.cc
@@ -0,0 +1,474 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuzz/pinweaver_model.h"
+
+#include "board/host/dcrypto.h"
+
+namespace {
+
+struct pw_request_t* SerializeCommon(const fuzz::pinweaver::Request& pinweaver,
+ pw_message_type_t message_type,
+ fuzz::span<uint8_t> buffer) {
+ struct pw_request_t* request =
+ reinterpret_cast<struct pw_request_t*>(buffer.begin());
+ if (pinweaver.has_version()) {
+ request->header.version = pinweaver.version().value();
+ } else {
+ request->header.version = PW_PROTOCOL_VERSION;
+ }
+ request->header.type = message_type;
+ return request;
+}
+
+void CheckBuffer(fuzz::span<uint8_t> buffer) {
+ uintptr_t ptr = reinterpret_cast<uintptr_t>(buffer.begin());
+ assert(ptr % alignof(pw_request_t) == 0);
+ assert(ptr % alignof(pw_response_t) == 0);
+}
+
+} // namespace
+
+//******************************************************************************
+// Public member functions.
+//******************************************************************************
+
+PinweaverModel::PinweaverModel() {
+ Reset();
+}
+
+void PinweaverModel::SendBuffer(fuzz::span<uint8_t> buffer) {
+ assert(sizeof(pw_request_t) <= buffer.size());
+ assert(sizeof(pw_response_t) <= buffer.size());
+ CheckBuffer(buffer);
+ pw_request_t* request = reinterpret_cast<pw_request_t*>(buffer.begin());
+ pw_response_t* response = reinterpret_cast<pw_response_t*>(buffer.begin());
+ pw_handle_request(&merkle_tree_, request, response);
+}
+
+size_t PinweaverModel::SerializeRequest(
+ const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const {
+ assert(buffer.size() >= PW_MAX_MESSAGE_SIZE);
+ CheckBuffer(buffer);
+ switch (pinweaver.request_case()) {
+ case fuzz::pinweaver::Request::kResetTree:
+ return SerializeResetTree(pinweaver, buffer);
+ case fuzz::pinweaver::Request::kInsertLeaf:
+ return SerializeInsertLeaf(pinweaver, buffer);
+ case fuzz::pinweaver::Request::kRemoveLeaf:
+ return SerializeRemoveLeaf(pinweaver, buffer);
+ case fuzz::pinweaver::Request::kTryAuth:
+ return SerializeTryAuth(pinweaver, buffer);
+ case fuzz::pinweaver::Request::kResetAuth:
+ return SerializeResetAuth(pinweaver, buffer);
+ case fuzz::pinweaver::Request::kGetLog:
+ return SerializeGetLog(pinweaver, buffer);
+ case fuzz::pinweaver::Request::kLogReplay:
+ return SerializeLogReplay(pinweaver, buffer);
+ case fuzz::pinweaver::Request::REQUEST_NOT_SET:
+ break;
+ }
+ return 0;
+}
+
+uint32_t PinweaverModel::ApplyRequest(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) {
+ SerializeRequest(pinweaver, buffer);
+ LeafData leaf_data;
+
+ // Size and alignment of buffer are checked in SerializeRequest().
+ pw_request_t* request = reinterpret_cast<pw_request_t*>(buffer.begin());
+ pw_response_t* response = reinterpret_cast<pw_response_t*>(buffer.begin());
+
+ if (pinweaver.request_case() == fuzz::pinweaver::Request::kInsertLeaf) {
+ pw_request_insert_leaf_t& insert = request->data.insert_leaf;
+ std::copy(insert.low_entropy_secret,
+ insert.low_entropy_secret + PW_SECRET_SIZE,
+ leaf_data.low_entropy_secret.begin());
+ std::copy(insert.reset_secret, insert.reset_secret + PW_SECRET_SIZE,
+ leaf_data.reset_secret.begin());
+ }
+
+ pw_handle_request(&merkle_tree_, request, response);
+ if (response->header.result_code != EC_SUCCESS &&
+ pinweaver.request_case() != fuzz::pinweaver::Request::kTryAuth) {
+ return response->header.result_code;
+ }
+
+ switch (pinweaver.request_case()) {
+ case fuzz::pinweaver::Request::kResetTree:
+ ApplyResetTree();
+ break;
+ case fuzz::pinweaver::Request::kInsertLeaf:
+ ApplyInsertLeaf(pinweaver, *response, &leaf_data);
+ break;
+ case fuzz::pinweaver::Request::kRemoveLeaf:
+ ApplyRemoveLeaf(pinweaver, *response);
+ break;
+ case fuzz::pinweaver::Request::kTryAuth:
+ ApplyTryAuth(pinweaver, *response);
+ break;
+ case fuzz::pinweaver::Request::kResetAuth:
+ ApplyResetAuth(pinweaver, *response);
+ break;
+ // GetLog and LogReplay have no side-effects so the model doesn't need
+ // to be updated.
+ case fuzz::pinweaver::Request::kGetLog:
+ case fuzz::pinweaver::Request::kLogReplay:
+ case fuzz::pinweaver::Request::REQUEST_NOT_SET:
+ break;
+ }
+ return response->header.result_code;
+}
+
+void PinweaverModel::Reset() {
+ memset(&merkle_tree_, 0, sizeof(merkle_tree_));
+ leaf_metadata_.clear();
+ mem_hash_tree_.Reset();
+ root_history_.clear();
+};
+
+//******************************************************************************
+// Private static fields.
+//******************************************************************************
+
+constexpr uint8_t PinweaverModel::kNullRootHash[PW_HASH_SIZE];
+
+//******************************************************************************
+// Private member functions.
+//******************************************************************************
+
+void PinweaverModel::GetHmac(const std::string& fuzzer_hmac,
+ uint64_t label,
+ fuzz::span<uint8_t> hmac) const {
+ assert(hmac.size() == PW_HASH_SIZE);
+ if (!fuzzer_hmac.empty()) {
+ fuzz::CopyWithPadding(fuzzer_hmac, hmac, 0);
+ return;
+ }
+ mem_hash_tree_.GetLeaf(label, hmac);
+}
+
+size_t PinweaverModel::CopyMetadata(
+ uint64_t label,
+ const LeafData& leaf_data,
+ unimported_leaf_data_t* unimported_leaf_data,
+ fuzz::span<uint8_t> buffer) const {
+ const std::vector<uint8_t>& data = leaf_data.wrapped_data;
+ memcpy(unimported_leaf_data, data.data(), data.size());
+
+ fuzz::span<uint8_t> path_hashes(
+ reinterpret_cast<uint8_t*>(unimported_leaf_data) + data.size(),
+ buffer.end());
+ return data.size() + mem_hash_tree_.GetPath(label, path_hashes);
+}
+
+size_t PinweaverModel::GetMetadata(uint64_t label,
+ unimported_leaf_data_t* unimported_leaf_data,
+ fuzz::span<uint8_t> buffer) const {
+ auto itr = leaf_metadata_.find(label);
+ if (itr == leaf_metadata_.end()) {
+ assert(buffer.size() >= sizeof(wrapped_leaf_data_t));
+ std::fill(buffer.begin(), buffer.begin() + sizeof(wrapped_leaf_data_t), 0);
+ return sizeof(wrapped_leaf_data_t);
+ }
+ return CopyMetadata(label, itr->second, unimported_leaf_data, buffer);
+}
+
+size_t PinweaverModel::GetPath(const std::string& fuzzer_hashes,
+ uint64_t label,
+ fuzz::span<uint8_t> path_hashes) const {
+ if (!fuzzer_hashes.empty()) {
+ return fuzz::CopyWithPadding(fuzzer_hashes, path_hashes, 0);
+ }
+ return mem_hash_tree_.GetPath(label, path_hashes);
+}
+
+void PinweaverModel::LogRootHash(fuzz::span<const uint8_t> root_hash,
+ uint64_t label) {
+ assert(root_hash.size() == PW_HASH_SIZE);
+ std::pair<std::vector<uint8_t>, uint64_t> entry{
+ {root_hash.begin(), root_hash.end()}, label};
+ if (root_history_.size() == PW_LOG_ENTRY_COUNT) {
+ root_history_.pop_front();
+ }
+ root_history_.emplace_back(std::array<uint8_t, PW_HASH_SIZE>{}, label);
+ std::copy(root_hash.begin(), root_hash.end(),
+ root_history_.back().first.begin());
+}
+
+fuzz::span<const uint8_t> PinweaverModel::GetRootHashFromLog(
+ size_t index) const {
+ if (index >= root_history_.size()) {
+ return fuzz::span<const uint8_t>(kNullRootHash, PW_HASH_SIZE);
+ }
+ return root_history_.rbegin()[index].first;
+}
+
+uint64_t PinweaverModel::GetLabelFromLog(size_t index) const {
+ if (index >= root_history_.size()) {
+ return 0;
+ }
+ return root_history_.rbegin()[index].second;
+}
+
+size_t PinweaverModel::SerializeResetTree(
+ const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const {
+ const fuzz::pinweaver::ResetTree& fuzzer_data = pinweaver.reset_tree();
+ pw_request_t* request = SerializeCommon(pinweaver, {PW_RESET_TREE}, buffer);
+ pw_request_reset_tree_t* req_data = &request->data.reset_tree;
+
+ request->header.data_length = sizeof(*req_data);
+ req_data->bits_per_level.v = fuzzer_data.bits_per_level();
+ req_data->height.v = fuzzer_data.height();
+
+ return request->header.data_length + sizeof(request->header);
+}
+
+size_t PinweaverModel::SerializeInsertLeaf(
+ const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const {
+ const fuzz::pinweaver::InsertLeaf& fuzzer_data = pinweaver.insert_leaf();
+ pw_request_t* request = SerializeCommon(pinweaver, {PW_INSERT_LEAF}, buffer);
+ pw_request_insert_leaf_t* req_data = &request->data.insert_leaf;
+
+ req_data->label.v = fuzzer_data.label();
+ fuzz::CopyWithPadding(
+ fuzzer_data.delay_schedule(),
+ fuzz::span<uint8_t>(reinterpret_cast<uint8_t*>(req_data->delay_schedule),
+ sizeof(req_data->delay_schedule)),
+ 0);
+ fuzz::CopyWithPadding(
+ fuzzer_data.low_entropy_secret(),
+ fuzz::span<uint8_t>(req_data->low_entropy_secret, PW_SECRET_SIZE), 0);
+ fuzz::CopyWithPadding(
+ fuzzer_data.high_entropy_secret(),
+ fuzz::span<uint8_t>(req_data->high_entropy_secret, PW_SECRET_SIZE), 0);
+ fuzz::CopyWithPadding(
+ fuzzer_data.reset_secret(),
+ fuzz::span<uint8_t>(req_data->reset_secret, PW_SECRET_SIZE), 0);
+
+ fuzz::span<uint8_t> path_hashes(
+ reinterpret_cast<uint8_t*>(req_data->path_hashes), buffer.end());
+ size_t path_hash_size =
+ GetPath(fuzzer_data.path_hashes(), fuzzer_data.label(), path_hashes);
+ request->header.data_length = sizeof(*req_data) + path_hash_size;
+
+ return request->header.data_length + sizeof(request->header);
+}
+
+size_t PinweaverModel::SerializeRemoveLeaf(
+ const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const {
+ const fuzz::pinweaver::RemoveLeaf& fuzzer_data = pinweaver.remove_leaf();
+ pw_request_t* request = SerializeCommon(pinweaver, {PW_REMOVE_LEAF}, buffer);
+ pw_request_remove_leaf_t* req_data = &request->data.remove_leaf;
+
+ req_data->leaf_location.v = fuzzer_data.label();
+ GetHmac(fuzzer_data.leaf_hmac(), fuzzer_data.label(),
+ fuzz::span<uint8_t>(req_data->leaf_hmac, PW_HASH_SIZE));
+
+ fuzz::span<uint8_t> path_hashes(
+ reinterpret_cast<uint8_t*>(req_data->path_hashes), buffer.end());
+ size_t path_hash_size =
+ GetPath(fuzzer_data.path_hashes(), fuzzer_data.label(), path_hashes);
+ request->header.data_length = sizeof(*req_data) + path_hash_size;
+
+ return request->header.data_length + sizeof(request->header);
+}
+
+size_t PinweaverModel::SerializeTryAuth(
+ const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const {
+ const fuzz::pinweaver::TryAuth& fuzzer_data = pinweaver.try_auth();
+ pw_request_t* request = SerializeCommon(pinweaver, {PW_TRY_AUTH}, buffer);
+ pw_request_try_auth_t* req_data = &request->data.try_auth;
+
+ request->header.data_length =
+ sizeof(*req_data) - sizeof(req_data->unimported_leaf_data);
+
+ auto itr = leaf_metadata_.find(fuzzer_data.label());
+ if (fuzzer_data.low_entropy_secret().empty() && itr != leaf_metadata_.end()) {
+ const auto& low_entropy_secret = itr->second.low_entropy_secret;
+ std::copy(low_entropy_secret.begin(), low_entropy_secret.end(),
+ req_data->low_entropy_secret);
+ } else {
+ fuzz::CopyWithPadding(
+ fuzzer_data.low_entropy_secret(),
+ fuzz::span<uint8_t>(req_data->low_entropy_secret, PW_SECRET_SIZE), 0);
+ }
+
+ if (fuzzer_data.unimported_leaf_data().empty() &&
+ itr != leaf_metadata_.end()) {
+ request->header.data_length +=
+ CopyMetadata(fuzzer_data.label(), itr->second,
+ &req_data->unimported_leaf_data, buffer);
+ } else {
+ request->header.data_length += fuzz::CopyWithPadding(
+ fuzzer_data.unimported_leaf_data(),
+ fuzz::span<uint8_t>(
+ reinterpret_cast<uint8_t*>(&req_data->unimported_leaf_data),
+ sizeof(wrapped_leaf_data_t)),
+ 0);
+ }
+
+ return request->header.data_length + sizeof(request->header);
+}
+
+size_t PinweaverModel::SerializeResetAuth(
+ const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const {
+ const fuzz::pinweaver::ResetAuth& fuzzer_data = pinweaver.reset_auth();
+ pw_request_t* request = SerializeCommon(pinweaver, {PW_RESET_AUTH}, buffer);
+ pw_request_reset_auth_t* req_data = &request->data.reset_auth;
+
+ request->header.data_length =
+ sizeof(*req_data) - sizeof(req_data->unimported_leaf_data);
+
+ auto itr = leaf_metadata_.find(fuzzer_data.label());
+ if (fuzzer_data.reset_secret().empty() && itr != leaf_metadata_.end()) {
+ const auto& reset_secret = itr->second.reset_secret;
+ std::copy(reset_secret.begin(), reset_secret.end(), req_data->reset_secret);
+ } else {
+ fuzz::CopyWithPadding(
+ fuzzer_data.reset_secret(),
+ fuzz::span<uint8_t>(req_data->reset_secret, PW_SECRET_SIZE), 0);
+ }
+
+ if (fuzzer_data.unimported_leaf_data().empty() &&
+ itr != leaf_metadata_.end()) {
+ request->header.data_length +=
+ CopyMetadata(fuzzer_data.label(), itr->second,
+ &req_data->unimported_leaf_data, buffer);
+ } else {
+ request->header.data_length += fuzz::CopyWithPadding(
+ fuzzer_data.unimported_leaf_data(),
+ fuzz::span<uint8_t>(
+ reinterpret_cast<uint8_t*>(&req_data->unimported_leaf_data),
+ sizeof(wrapped_leaf_data_t)),
+ 0);
+ }
+
+ return request->header.data_length + sizeof(request->header);
+}
+
+size_t PinweaverModel::SerializeGetLog(
+ const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const {
+ const fuzz::pinweaver::GetLog& fuzzer_data = pinweaver.get_log();
+ pw_request_t* request = SerializeCommon(pinweaver, {PW_GET_LOG}, buffer);
+ pw_request_get_log_t* req_data = &request->data.get_log;
+
+ memcpy(req_data->root,
+ GetRootHashFromLog(fuzzer_data.index_of_root()).begin(), PW_HASH_SIZE);
+ request->header.data_length = sizeof(*req_data);
+
+ return request->header.data_length + sizeof(request->header);
+}
+
+size_t PinweaverModel::SerializeLogReplay(
+ const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const {
+ const fuzz::pinweaver::LogReplay& fuzzer_data = pinweaver.log_replay();
+ pw_request_t* request = SerializeCommon(pinweaver, {PW_LOG_REPLAY}, buffer);
+ pw_request_log_replay_t* req_data = &request->data.log_replay;
+
+ memcpy(req_data->log_root,
+ GetRootHashFromLog(fuzzer_data.index_of_root()).begin(), PW_HASH_SIZE);
+ request->header.data_length =
+ sizeof(*req_data) - sizeof(req_data->unimported_leaf_data);
+
+ if (fuzzer_data.unimported_leaf_data().empty()) {
+ request->header.data_length +=
+ GetMetadata(GetLabelFromLog(fuzzer_data.index_of_root()),
+ &req_data->unimported_leaf_data, buffer);
+ } else {
+ request->header.data_length += fuzz::CopyWithPadding(
+ fuzzer_data.unimported_leaf_data(),
+ fuzz::span<uint8_t>(
+ reinterpret_cast<uint8_t*>(&req_data->unimported_leaf_data),
+ sizeof(wrapped_leaf_data_t)),
+ 0);
+ }
+
+ return request->header.data_length + sizeof(request->header);
+}
+
+void PinweaverModel::UpdateMetadata(
+ uint64_t label,
+ const pw_response_header_t& header,
+ const unimported_leaf_data_t* unimported_leaf_data,
+ size_t unimported_leaf_data_length,
+ const LeafData* leaf_data) {
+ LogRootHash(fuzz::span<const uint8_t>(header.root, PW_HASH_SIZE), label);
+ if (unimported_leaf_data) {
+ const uint8_t* data =
+ reinterpret_cast<const uint8_t*>(unimported_leaf_data);
+ LeafData& stored_leaf_data = leaf_metadata_[label];
+ if (leaf_data) {
+ stored_leaf_data = *leaf_data;
+ }
+ stored_leaf_data.wrapped_data.assign(data,
+ data + unimported_leaf_data_length);
+ mem_hash_tree_.UpdatePath(
+ label,
+ fuzz::span<const uint8_t>(unimported_leaf_data->hmac, PW_HASH_SIZE));
+ } else {
+ leaf_metadata_.erase(label);
+ mem_hash_tree_.UpdatePath(label, fuzz::span<const uint8_t>() /*path_hash*/);
+ }
+}
+
+void PinweaverModel::ApplyResetTree() {
+ leaf_metadata_.clear();
+ mem_hash_tree_.Reset(merkle_tree_.bits_per_level.v, merkle_tree_.height.v);
+}
+
+void PinweaverModel::ApplyInsertLeaf(const fuzz::pinweaver::Request& pinweaver,
+ const pw_response_t& response,
+ const LeafData* leaf_data) {
+ const pw_response_insert_leaf_t* resp = &response.data.insert_leaf;
+ size_t unimported_leaf_data_length = response.header.data_length -
+ sizeof(*resp) +
+ sizeof(resp->unimported_leaf_data);
+ UpdateMetadata(pinweaver.insert_leaf().label(), response.header,
+ &resp->unimported_leaf_data, unimported_leaf_data_length,
+ leaf_data);
+}
+
+void PinweaverModel::ApplyRemoveLeaf(const fuzz::pinweaver::Request& pinweaver,
+ const pw_response_t& response) {
+ UpdateMetadata(pinweaver.remove_leaf().label(), response.header,
+ nullptr /*unimported_leaf_data*/,
+ 0 /*unimported_leaf_data_length*/, nullptr /*leaf_data*/);
+}
+
+void PinweaverModel::ApplyTryAuth(const fuzz::pinweaver::Request& pinweaver,
+ const pw_response_t& response) {
+ const pw_response_try_auth_t* resp = &response.data.try_auth;
+
+ if (response.header.result_code != EC_SUCCESS &&
+ response.header.result_code != PW_ERR_LOWENT_AUTH_FAILED) {
+ return;
+ }
+ size_t unimported_leaf_data_length = response.header.data_length -
+ sizeof(*resp) +
+ sizeof(resp->unimported_leaf_data);
+ UpdateMetadata(pinweaver.try_auth().label(), response.header,
+ &resp->unimported_leaf_data, unimported_leaf_data_length,
+ nullptr /*leaf_data*/);
+}
+
+void PinweaverModel::ApplyResetAuth(const fuzz::pinweaver::Request& pinweaver,
+ const pw_response_t& response) {
+ const pw_response_reset_auth_t* resp = &response.data.reset_auth;
+ size_t unimported_leaf_data_length = response.header.data_length -
+ sizeof(*resp) +
+ sizeof(resp->unimported_leaf_data);
+ UpdateMetadata(pinweaver.reset_auth().label(), response.header,
+ &resp->unimported_leaf_data, unimported_leaf_data_length,
+ nullptr /*leaf_data*/);
+}
diff --git a/fuzz/pinweaver_model.h b/fuzz/pinweaver_model.h
new file mode 100644
index 0000000000..84508786f3
--- /dev/null
+++ b/fuzz/pinweaver_model.h
@@ -0,0 +1,123 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Pinweaver specific model to facilitate fuzzing.
+
+#ifndef __FUZZ_PINWEAVER_MODEL_H
+#define __FUZZ_PINWEAVER_MODEL_H
+
+#include <deque>
+#include <memory>
+#include <unordered_map>
+
+#define HIDE_EC_STDLIB
+#include "fuzz/cr50_fuzz.pb.h"
+#include "fuzz/mem_hash_tree.h"
+#include "fuzz/span.h"
+#include "include/pinweaver.h"
+#include "include/pinweaver_types.h"
+
+// Provides enough state tracking to send valid PinWeaver requests. This is
+// necessary because of the authentication dependent fields used by the Merkle
+// tree such as HMACs and a set of sibling path hashes that must be correct to
+// reach some parts of the PinWeaver code.
+class PinweaverModel {
+ public:
+ PinweaverModel();
+
+ void SendBuffer(fuzz::span<uint8_t> buffer);
+
+ // Converts the logical representation of a request used in fuzzing into bytes
+ // that can be processed by the pinweaver code for fuzzing.
+ size_t SerializeRequest(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const;
+
+ // Executes a request in the form of a fuzz::pinweaver::Request proto, and
+ // updates the model, so that future requests will be valid.
+ uint32_t ApplyRequest(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer);
+
+ // Clears any state. This shoudl be called at the beginning of each fuzzing
+ // iteration.
+ void Reset();
+
+ private:
+ static constexpr uint8_t kNullRootHash[PW_HASH_SIZE] = {};
+
+ struct LeafData {
+ std::vector<uint8_t> wrapped_data;
+ std::array<uint8_t, PW_SECRET_SIZE> low_entropy_secret;
+ std::array<uint8_t, PW_SECRET_SIZE> reset_secret;
+ };
+
+ // Functions for retrieving the current state of the metadata.
+ void GetHmac(const std::string& fuzzer_hmac,
+ uint64_t label,
+ fuzz::span<uint8_t> hmac) const;
+ size_t CopyMetadata(uint64_t label,
+ const LeafData& leaf_data,
+ unimported_leaf_data_t* unimported_leaf_data,
+ fuzz::span<uint8_t> buffer) const;
+ size_t GetMetadata(uint64_t label,
+ unimported_leaf_data_t* unimported_leaf_data,
+ fuzz::span<uint8_t> buffer) const;
+ size_t GetPath(const std::string& fuzzer_hashes,
+ uint64_t label,
+ fuzz::span<uint8_t> path_hashes) const;
+
+ // Store copies of the root hash of the Merkle tree, and label of the leaf
+ // associated with a request so that valid get log requests can be generated.
+ void LogRootHash(fuzz::span<const uint8_t> root_hash, uint64_t label);
+ // Retrieve a root hash from the log at the given index.
+ fuzz::span<const uint8_t> GetRootHashFromLog(size_t index) const;
+ // Retrieve a leaf label from the log at the given index.
+ uint64_t GetLabelFromLog(size_t index) const;
+
+ // Helper functions used by SerializePinweaver to convert
+ size_t SerializeResetTree(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const;
+ size_t SerializeInsertLeaf(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const;
+ size_t SerializeRemoveLeaf(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const;
+ size_t SerializeTryAuth(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const;
+ size_t SerializeResetAuth(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const;
+ size_t SerializeGetLog(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const;
+ size_t SerializeLogReplay(const fuzz::pinweaver::Request& pinweaver,
+ fuzz::span<uint8_t> buffer) const;
+
+ // Updates the metadata storage for a particular leaf. |leaf_data| is only
+ // required for insert operations so the metadata, low_entropy_secret,
+ // and reset_secret for the leaf can be retrieved to generate valid
+ // authentication requests.
+ void UpdateMetadata(uint64_t label,
+ const pw_response_header_t& header,
+ const unimported_leaf_data_t* unimported_leaf_data,
+ size_t unimported_leaf_data_length,
+ const LeafData* leaf_data);
+
+ // Helper functions for updating the state when responses are received.
+ void ApplyResetTree();
+ void ApplyInsertLeaf(const fuzz::pinweaver::Request& pinweaver,
+ const pw_response_t& response,
+ const LeafData* leaf_data);
+ void ApplyRemoveLeaf(const fuzz::pinweaver::Request& pinweaver,
+ const pw_response_t& response);
+ void ApplyTryAuth(const fuzz::pinweaver::Request& pinweaver,
+ const pw_response_t& response);
+ void ApplyResetAuth(const fuzz::pinweaver::Request& pinweaver,
+ const pw_response_t& response);
+
+ merkle_tree_t merkle_tree_;
+
+ MemHashTree mem_hash_tree_;
+ std::deque<std::pair<std::array<uint8_t, PW_HASH_SIZE>, uint64_t>>
+ root_history_;
+ std::unordered_map<uint64_t, LeafData> leaf_metadata_;
+};
+
+#endif // __FUZZ_PINWEAVER_MODEL_H
diff --git a/fuzz/span.h b/fuzz/span.h
new file mode 100644
index 0000000000..531df832a3
--- /dev/null
+++ b/fuzz/span.h
@@ -0,0 +1,56 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef __FUZZ_SPAN_H
+#define __FUZZ_SPAN_H
+
+#include <unistd.h>
+
+#include <algorithm>
+
+namespace fuzz {
+
+template <typename T>
+class span {
+ public:
+ typedef T value_type;
+
+ constexpr span() : span<T>(nullptr, nullptr) {}
+ constexpr span(T* begin, size_t size) : begin_(begin), end_(begin + size) {}
+ constexpr span(T* begin, T* end) : begin_(begin), end_(end) {}
+
+ template <class Container>
+ constexpr span(Container& container)
+ : begin_(container.begin()), end_(container.end()){};
+
+ constexpr T* begin() const { return begin_; }
+ constexpr T* end() const { return end_; }
+
+ constexpr T* data() const { return begin_; }
+
+ constexpr bool empty() const { return begin_ == end_; }
+ constexpr size_t size() const { return end_ - begin_; }
+
+ private:
+ T* begin_;
+ T* end_;
+};
+
+template <typename Source, typename Destination>
+size_t CopyWithPadding(Source source,
+ Destination destination,
+ typename Destination::value_type fill_value) {
+ if (source.size() >= destination.size()) {
+ std::copy(source.begin(), source.begin() + destination.size(),
+ destination.begin());
+ return destination.size();
+ }
+ std::copy(source.begin(), source.end(), destination.begin());
+ std::fill(destination.begin() + source.size(), destination.end(), fill_value);
+ return source.size();
+}
+
+} // namespace fuzz
+
+#endif // __FUZZ_SPAN_H