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>
This commit is contained in:
Allen Webb 2018-08-21 12:11:38 -07:00 committed by chrome-bot
parent b343c963b3
commit a5e1a639e5
10 changed files with 1024 additions and 60 deletions

View File

@ -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}

View File

@ -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"
#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"
#define NVMEM_TPM_SIZE ((sizeof((struct nvmem_partition *)0)->buffer) \
- NVMEM_CR50_SIZE)
using protobuf_mutator::libfuzzer::LoadProtoInput;
extern "C" uint32_t nvmem_user_sizes[NVMEM_NUM_USERS] = {
NVMEM_TPM_SIZE,
NVMEM_CR50_SIZE
};
namespace {
constexpr size_t kBufferAlignment = alignof(pw_request_t) >
alignof(pw_response_t)
? alignof(pw_request_t)
: alignof(pw_response_t);
} // namespace
extern "C" void rand_bytes(void *buffer, size_t len)
{
size_t x = 0;
extern "C" uint32_t nvmem_user_sizes[NVMEM_NUM_USERS] = {NVMEM_TPM_SIZE,
NVMEM_CR50_SIZE};
for (; x < len; ++x)
((uint8_t *)buffer)[x] = rand();
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) {}
void InitializeFuzzerRun() {
memset(__host_flash, 0xff, sizeof(__host_flash));
nvmem_init();
nvmem_enable_commits();
initvars();
srand(0);
}
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);
}
}
/* Prevent this from being stack allocated. */
static uint8_t tpm_io_buffer[PW_MAX_MESSAGE_SIZE];
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;
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;
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;
}
InitializeFuzzerRun();
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;
}

31
fuzz/cr50_fuzz.proto Normal file
View File

@ -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;
}

View File

@ -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
/******************************************************************************/

130
fuzz/mem_hash_tree.cc Normal file
View File

@ -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;
}
}

57
fuzz/mem_hash_tree.h Normal file
View File

@ -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

View File

@ -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;
}
}

474
fuzz/pinweaver_model.cc Normal file
View File

@ -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*/);
}

123
fuzz/pinweaver_model.h Normal file
View File

@ -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

56
fuzz/span.h Normal file
View File

@ -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