diff options
author | Allen Webb <allenwebb@google.com> | 2018-08-21 12:11:38 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2018-12-03 12:43:22 -0800 |
commit | a5e1a639e55d1c6382b4d690c6b78f6f85e8fbc9 (patch) | |
tree | 04ea72cd9750bc6b3e792550f7fd9515186a3636 | |
parent | b343c963b38b03df97a1bc57f201e26640c89e47 (diff) | |
download | chrome-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.mk | 12 | ||||
-rw-r--r-- | fuzz/cr50_fuzz.cc | 135 | ||||
-rw-r--r-- | fuzz/cr50_fuzz.proto | 31 | ||||
-rw-r--r-- | fuzz/fuzz_config.h | 4 | ||||
-rw-r--r-- | fuzz/mem_hash_tree.cc | 130 | ||||
-rw-r--r-- | fuzz/mem_hash_tree.h | 57 | ||||
-rw-r--r-- | fuzz/pinweaver/pinweaver.proto | 64 | ||||
-rw-r--r-- | fuzz/pinweaver_model.cc | 474 | ||||
-rw-r--r-- | fuzz/pinweaver_model.h | 123 | ||||
-rw-r--r-- | fuzz/span.h | 56 |
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 |