Merge changes from topic "snapshot_fuzz"

* changes:
  Enable automatic libsnapshot fuzzer runs
  Also log corpus when aborted through libbase.
  libsnapshot_fuzzer: Fuzz MapUpdateSnapshot.
  libsnapshot_fuzzer: construct valid super partition metadata.
  libsnapshot_fuzzer: Fuzz CreateUpdateSnapshots
  libsnapshot_fuzzer: add additional tests for more APIs
  libsnapshot_fuzzer: use protobuf
  libsnapshot_fuzzer: map super image
This commit is contained in:
Yifan Hong 2020-05-02 04:46:31 +00:00 committed by Gerrit Code Review
commit f4cd49afa4
10 changed files with 751 additions and 383 deletions

View File

@ -254,34 +254,47 @@ cc_fuzz {
native_coverage : true,
srcs: [
// Compile the protobuf definition again with type full.
"android/snapshot/snapshot_fuzz.proto",
"update_engine/update_metadata.proto",
"fuzz_utils.cpp",
"snapshot_fuzz.cpp",
"snapshot_fuzz_utils.cpp",
"fuzz_utils.cpp",
// Compile libsnapshot sources directly to avoid dependency
// to update_metadata-protos
":libsnapshot_sources",
],
static_libs: [
"libbase",
"libcrypto_static",
"libcutils",
"libext2_uuid",
"libext4_utils",
"libfstab",
"libfs_mgr",
"libgtest", // from libsnapshot_test_helpers
"libgmock", // from libsnapshot_test_helpers
"liblog",
"liblp",
"libsnapshot_init", // don't use binder or hwbinder
"libsnapshot_test_helpers",
"libprotobuf-cpp-lite",
"update_metadata-protos",
"libprotobuf-mutator",
],
header_libs: [
"libfiemap_headers",
"libstorage_literals_headers",
],
proto: {
type: "full",
canonical_path_from_root: false,
local_include_dirs: ["."],
},
fuzz_config: {
cc: ["android-virtual-ab+bugs@google.com"],
componentid: 30545,
hotlists: ["1646452"],
fuzz_on_haiku_host: false,
// TODO(b/154633114): set to true to run this automatically.
fuzz_on_haiku_device: false,
fuzz_on_haiku_device: true,
},
}

View File

@ -0,0 +1,103 @@
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
package android.snapshot;
import "update_engine/update_metadata.proto";
// Controls the behavior of IDeviceInfo.
// Next: 6
message FuzzDeviceInfoData {
bool slot_suffix_is_a = 1;
bool is_overlayfs_setup = 2;
bool allow_set_boot_control_merge_status = 3;
bool allow_set_slot_as_unbootable = 4;
bool is_recovery = 5;
}
// Controls the behavior of the test SnapshotManager.
// Next: 2
message FuzzSnapshotManagerData {
bool is_local_image_manager = 1;
}
// A simplified version of CreateLogicalPartitionParams for fuzzing.
// Next: 9
message CreateLogicalPartitionParamsProto {
bool use_correct_super = 1;
string block_device = 2;
bool has_metadata_slot = 3;
uint32 metadata_slot = 4;
string partition_name = 5;
bool force_writable = 6;
int64 timeout_millis = 7;
string device_name = 8;
}
// Mimics the API of ISnapshotManager. Defines one action on the snapshot
// manager.
// Next: 18
message SnapshotManagerActionProto {
message NoArgs {}
message ProcessUpdateStateArgs {
bool has_before_cancel = 1;
bool fail_before_cancel = 2;
}
message CreateLogicalAndSnapshotPartitionsArgs {
bool use_correct_super = 1;
string super = 2;
int64 timeout_millis = 3;
}
message RecoveryCreateSnapshotDevicesArgs {
bool has_metadata_device_object = 1;
bool metadata_mounted = 2;
}
oneof value {
NoArgs begin_update = 1;
NoArgs cancel_update = 2;
bool finished_snapshot_writes = 3;
NoArgs initiate_merge = 4;
ProcessUpdateStateArgs process_update_state = 5;
bool get_update_state = 6;
chromeos_update_engine.DeltaArchiveManifest create_update_snapshots = 7;
CreateLogicalPartitionParamsProto map_update_snapshot = 8;
string unmap_update_snapshot = 9;
NoArgs need_snapshots_in_first_stage_mount = 10;
CreateLogicalAndSnapshotPartitionsArgs create_logical_and_snapshot_partitions = 11;
bool handle_imminent_data_wipe = 12;
NoArgs recovery_create_snapshot_devices = 13;
RecoveryCreateSnapshotDevicesArgs recovery_create_snapshot_devices_with_metadata = 14;
NoArgs dump = 15;
NoArgs ensure_metadata_mounted = 16;
NoArgs get_snapshot_merge_stats_instance = 17;
}
}
// Includes all data that needs to be fuzzed.
message SnapshotFuzzData {
FuzzDeviceInfoData device_info_data = 1;
FuzzSnapshotManagerData manager_data = 2;
// If true:
// - if super_data is empty, create empty super partition metadata.
// - otherwise, create super partition metadata accordingly.
// If false, no valid super partition metadata (it is zeroed)
bool is_super_metadata_valid = 3;
chromeos_update_engine.DeltaArchiveManifest super_data = 4;
// More data used to prep the test before running actions.
reserved 5 to 9999;
repeated SnapshotManagerActionProto actions = 10000;
}

View File

@ -18,8 +18,8 @@ build_normal() (
build_cov() {
pushd $(gettop)
ret=$?
NATIVE_COVERAGE="true" NATIVE_LINE_COVERAGE="true" COVERAGE_PATHS="${PROJECT_PATH}" m ${FUZZ_TARGET}
ret=$?
popd
return ${ret}
}
@ -46,7 +46,7 @@ prepare_host() {
}
# run_snapshot_fuzz -runs=10000
generate_corpse() {
generate_corpus() {
[[ "$@" ]] || { echo "run with -runs=X"; return 1; }
prepare_device &&

View File

@ -22,4 +22,17 @@ void CheckInternal(bool value, std::string_view msg) {
CHECK(value) << msg;
}
const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
const google::protobuf::Descriptor* action_desc) {
CHECK(action_desc);
CHECK(action_desc->oneof_decl_count() == 1)
<< action_desc->oneof_decl_count() << " oneof fields found in " << action_desc->name()
<< "; only one is expected.";
auto* oneof_value_desc = action_desc->oneof_decl(0);
CHECK(oneof_value_desc);
CHECK(oneof_value_desc->name() == "value")
<< "oneof field has name " << oneof_value_desc->name();
return oneof_value_desc;
}
} // namespace android::fuzz

View File

@ -12,256 +12,254 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <optional>
#pragma once
#include <map>
#include <string>
#include <string_view>
#include <vector>
// Generic classes for fuzzing a collection of APIs.
#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>
#include <google/protobuf/repeated_field.h>
// Utilities for using a protobuf definition to fuzz APIs in a class.
// Terms:
// The "fuzzed class" is the C++ class definition whose functions are fuzzed.
// The "fuzzed object" is an instantiated object of the fuzzed class. It is
// typically created and destroyed for each test run.
// An "action" is an operation on the fuzzed object that may mutate its state.
// This typically involves one function call into the fuzzed object.
namespace android::fuzz {
// My custom boolean type -- to avoid conflict with (u)int8_t and char.
struct Bool {
bool value;
operator bool() const { return value; }
};
// Helper for FuzzData.
// A wrapper over an optional const object T. The buffer is maintained elsewhere.
template <typename T>
class Optional {
public:
Optional(const T* ptr) : ptr_(ptr) {}
const T& operator*() const { return *ptr_; }
const T& value() const { return *ptr_; }
bool has_value() const { return ptr_; }
private:
const T* ptr_;
};
// Helper for FuzzData.
// A wrapper over an optional boolean. The boolean is owned by this object.
template <>
class Optional<Bool> {
public:
Optional(std::optional<Bool>&& val) : val_(std::move(val)) {}
const Bool& operator*() const { return *val_; }
const Bool& value() const { return val_.value(); }
bool has_value() const { return val_.has_value(); }
private:
std::optional<Bool> val_;
};
// Helper for FuzzData.
// A view on a raw data buffer. Client is responsible for maintaining the lifetime of the data
// buffer.
class DataView {
public:
DataView(const uint8_t* data, uint64_t size) : data_(data), size_(size) {}
DataView(const void* data, uint64_t size) : DataView(static_cast<const uint8_t*>(data), size) {}
inline uint64_t size() const { return size_; }
inline const uint8_t* data() const { return data_; }
inline bool CanConsume(uint64_t size) { return size_ >= size; }
// Consume the first |size| bytes from |this| and return a DataView object that represents
// the consumed data. Data pointer in |this| is incremented by |size| bytes.
// If not enough bytes, return nullopt.
std::optional<DataView> Consume(uint64_t size) {
if (!CanConsume(size)) return std::nullopt;
DataView ret(data_, size);
size_ -= size;
data_ += size;
return ret;
}
private:
const uint8_t* data_;
uint64_t size_;
};
// A view on the fuzz data. Provides APIs to consume typed objects.
class FuzzData : public DataView {
public:
// Inherit constructors.
using DataView::DataView;
// Consume a data object T and return the pointer (into the buffer). No copy is done.
// If not enough bytes, return nullptr.
template <typename T>
inline Optional<T> Consume() {
auto data_view = DataView::Consume(sizeof(T));
if (!data_view.has_value()) return nullptr;
return reinterpret_cast<const T*>(data_view->data());
}
// To provide enough entropy for booleans, they are consumed bit by bit.
// Hence, the returned value is not indexed into the buffer. See Optional<Bool>.
template <>
Optional<Bool> Consume<Bool>() {
if (!boolean_buffer_.has_value() || boolean_bit_offset_ >= sizeof(*boolean_buffer_)) {
boolean_buffer_ = Consume<uint8_t>();
boolean_bit_offset_ = 0;
}
if (!boolean_buffer_.has_value()) {
return Optional<Bool>(std::nullopt);
}
const auto& byte = *boolean_buffer_;
bool ret = (byte >> boolean_bit_offset_) & 0x1;
boolean_bit_offset_++;
return Optional<Bool>(Bool{ret});
}
private:
// Separate buffer for booleans.
Optional<uint8_t> boolean_buffer_ = nullptr;
uint8_t boolean_bit_offset_ = 0;
};
enum class CallResult {
SUCCESS,
NOT_ENOUGH_DATA,
};
inline bool AllArgsHasValue() {
return true;
}
template <typename T>
inline bool AllArgsHasValue(const Optional<T>& t) {
return t.has_value();
}
template <typename First, typename... Remaining>
inline bool AllArgsHasValue(const Optional<First>& first, const Optional<Remaining>&... remaining) {
return first.has_value() && AllArgsHasValue(remaining...);
}
// Base class of FuzzFunction.
class FuzzFunctionBase {
public:
virtual ~FuzzFunctionBase() = default;
virtual CallResult Call(FuzzData* fuzz_data) const = 0;
};
template <typename T>
class FuzzFunction; // undefined
// A wrapper over a fuzzed function.
template <typename R, typename... Args>
class FuzzFunction<R(Args...)> : public FuzzFunctionBase {
public:
using Function = std::function<R(Args...)>;
FuzzFunction(Function&& function) : function_(std::move(function)) {}
// Eat necessary data in |fuzz_data| and invoke the function.
CallResult Call(FuzzData* fuzz_data) const override {
return CallWithOptionalArgs(fuzz_data->Consume<std::remove_reference_t<Args>>()...);
}
private:
Function function_;
CallResult CallWithOptionalArgs(const Optional<std::remove_reference_t<Args>>&... args) const {
if (!AllArgsHasValue(args...)) {
return CallResult::NOT_ENOUGH_DATA;
}
(void)function_(args.value()...); // ignore returned value
return CallResult::SUCCESS;
}
};
// CHECK(value) << msg
void CheckInternal(bool value, std::string_view msg);
// A collection of FuzzFunction's.
// FunctionsSizeType must be an integral type where
// functions_.size() <= std::numeric_limits<FunctionSizeType>::max().
template <typename FunctionsSizeType>
class FuzzFunctions {
// Get the oneof descriptor inside Action
const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
const google::protobuf::Descriptor* action_desc);
template <typename Class>
using FunctionMapImpl =
std::map<int, std::function<void(Class*, const google::protobuf::Message& action_proto,
const google::protobuf::FieldDescriptor* field_desc)>>;
template <typename Class>
class FunctionMap : public FunctionMapImpl<Class> {
public:
// Subclass should override this to register functions via AddFunction.
FuzzFunctions() = default;
virtual ~FuzzFunctions() = default;
// Eat some amount of data in |fuzz_data| and call one of the |functions_|.
CallResult CallOne(FuzzData* fuzz_data) const {
auto opt_number = fuzz_data->Consume<FunctionsSizeType>();
if (!opt_number.has_value()) {
return CallResult::NOT_ENOUGH_DATA;
}
auto function_index = opt_number.value() % functions_.size();
return functions_[function_index]->Call(fuzz_data);
void CheckEmplace(typename FunctionMapImpl<Class>::key_type key,
typename FunctionMapImpl<Class>::mapped_type&& value) {
auto [it, inserted] = this->emplace(key, std::move(value));
CheckInternal(inserted,
"Multiple implementation registered for tag number " + std::to_string(key));
}
private:
template <typename R, typename... Args>
struct FunctionTraits {
using Function = std::function<R(Args...)>;
};
public:
// There are no deduction guide from lambda to std::function, so the
// signature of the lambda must be specified in the template argument.
// FuzzFunctions provide the following 3 ways to specify the signature of
// the lambda:
// AddFunction<R(Args...)>, e.g. AddFunction<ReturnType(ArgType, ArgType)>
template <typename T>
void AddFunction(std::function<T>&& func) {
functions_.push_back(std::make_unique<FuzzFunction<T>>(std::move(func)));
}
// AddFunction<R, Args...>, e.g. AddFunction<ReturnType, ArgType, ArgType>
template <typename R, typename... Args>
void AddFunction(typename FunctionTraits<R, Args...>::Function&& func) {
functions_.push_back(std::make_unique<FuzzFunction<R(Args...)>>(std::move(func)));
}
// AddFunction<ArgType...>. Equivalent to AddFunction<void, Args...>
template <typename... Args>
void AddFunction(typename FunctionTraits<void, Args...>::Function&& func) {
functions_.push_back(std::make_unique<FuzzFunction<void(Args...)>>(std::move(func)));
}
// Use |fuzz_data| as a guide to call |functions_| until |fuzz_data| is
// depleted. Return
void DepleteData(FuzzData* fuzz_data) const {
CallResult result;
while ((result = CallOne(fuzz_data)) == CallResult::SUCCESS)
;
CheckInternal(result == CallResult::NOT_ENOUGH_DATA,
"result is " + std::to_string(static_cast<int>(result)));
}
protected:
// Helper for subclass to check that size of |functions_| is actually within
// SizeType. Should be called after all functions are registered.
void CheckFunctionsSize() const {
CheckInternal(functions_.size() <= std::numeric_limits<FunctionsSizeType>::max(),
"Need to extend number of bits for function count; there are " +
std::to_string(functions_.size()) + " functions now.");
}
private:
std::vector<std::unique_ptr<FuzzFunctionBase>> functions_;
};
// An object whose APIs are being fuzzed.
template <typename T, typename FunctionsSizeType>
class FuzzObject : public FuzzFunctions<FunctionsSizeType> {
public:
// Not thread-safe; client is responsible for ensuring only one thread calls DepleteData.
void DepleteData(T* obj, FuzzData* fuzz_data) {
obj_ = obj;
FuzzFunctions<FunctionsSizeType>::DepleteData(fuzz_data);
obj_ = nullptr;
template <typename Action>
int CheckConsistency() {
const auto* function_map = Action::GetFunctionMap();
const auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
for (int field_index = 0; field_index < action_value_desc->field_count(); ++field_index) {
const auto* field_desc = action_value_desc->field(field_index);
CheckInternal(function_map->find(field_desc->number()) != function_map->end(),
"Missing impl for function " + field_desc->camelcase_name());
}
return 0;
}
template <typename Action>
void ExecuteActionProto(typename Action::Class* module,
const typename Action::Proto& action_proto) {
static auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
auto* action_refl = Action::Proto::GetReflection();
if (!action_refl->HasOneof(action_proto, action_value_desc)) {
return;
}
protected:
// Helper for subclass to get the module under test in the added functions.
T* get() const {
CheckInternal(obj_ != nullptr, "No module under test is found.");
return obj_;
const auto* field_desc = action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc);
auto number = field_desc->number();
const auto& map = *Action::GetFunctionMap();
auto it = map.find(number);
CheckInternal(it != map.end(), "Missing impl for function " + field_desc->camelcase_name());
const auto& func = it->second;
func(module, action_proto, field_desc);
}
template <typename Action>
void ExecuteAllActionProtos(
typename Action::Class* module,
const google::protobuf::RepeatedPtrField<typename Action::Proto>& action_protos) {
for (const auto& proto : action_protos) {
ExecuteActionProto<Action>(module, proto);
}
}
// Safely cast message to T. Returns a pointer to message if cast successfully, otherwise nullptr.
template <typename T>
const T* SafeCast(const google::protobuf::Message& message) {
if (message.GetDescriptor() != T::GetDescriptor()) {
return nullptr;
}
return static_cast<const T*>(&message);
}
// Cast message to const T&. Abort if type mismatch.
template <typename T>
const T& CheckedCast(const google::protobuf::Message& message) {
const auto* ptr = SafeCast<T>(message);
CheckInternal(ptr, "Cannot cast " + message.GetDescriptor()->name() + " to " +
T::GetDescriptor()->name());
return *ptr;
}
// A templated way to a primitive field from a message using reflection.
template <typename T>
struct PrimitiveGetter;
#define FUZZ_DEFINE_PRIMITIVE_GETTER(type, func_name) \
template <> \
struct PrimitiveGetter<type> { \
static constexpr const auto fp = &google::protobuf::Reflection::func_name; \
}
private:
T* obj_ = nullptr;
FUZZ_DEFINE_PRIMITIVE_GETTER(bool, GetBool);
FUZZ_DEFINE_PRIMITIVE_GETTER(uint32_t, GetUInt32);
FUZZ_DEFINE_PRIMITIVE_GETTER(int32_t, GetInt32);
FUZZ_DEFINE_PRIMITIVE_GETTER(uint64_t, GetUInt64);
FUZZ_DEFINE_PRIMITIVE_GETTER(int64_t, GetInt64);
FUZZ_DEFINE_PRIMITIVE_GETTER(double, GetDouble);
FUZZ_DEFINE_PRIMITIVE_GETTER(float, GetFloat);
// ActionPerformer extracts arguments from the protobuf message, and then call FuzzFunction
// with these arguments.
template <typename FuzzFunction, typename Signature, typename Enabled = void>
struct ActionPerfomer; // undefined
template <typename FuzzFunction, typename MessageProto>
struct ActionPerfomer<
FuzzFunction, void(const MessageProto&),
typename std::enable_if_t<std::is_base_of_v<google::protobuf::Message, MessageProto>>> {
static void Invoke(typename FuzzFunction::Class* module,
const google::protobuf::Message& action_proto,
const google::protobuf::FieldDescriptor* field_desc) {
const MessageProto& arg = CheckedCast<std::remove_reference_t<MessageProto>>(
action_proto.GetReflection()->GetMessage(action_proto, field_desc));
FuzzFunction::ImplBody(module, arg);
}
};
template <typename FuzzFunction, typename Primitive>
struct ActionPerfomer<FuzzFunction, void(Primitive),
typename std::enable_if_t<std::is_arithmetic_v<Primitive>>> {
static void Invoke(typename FuzzFunction::Class* module,
const google::protobuf::Message& action_proto,
const google::protobuf::FieldDescriptor* field_desc) {
Primitive arg = std::invoke(PrimitiveGetter<Primitive>::fp, action_proto.GetReflection(),
action_proto, field_desc);
FuzzFunction::ImplBody(module, arg);
}
};
template <typename FuzzFunction>
struct ActionPerfomer<FuzzFunction, void()> {
static void Invoke(typename FuzzFunction::Class* module, const google::protobuf::Message&,
const google::protobuf::FieldDescriptor*) {
FuzzFunction::ImplBody(module);
}
};
template <typename FuzzFunction>
struct ActionPerfomer<FuzzFunction, void(const std::string&)> {
static void Invoke(typename FuzzFunction::Class* module,
const google::protobuf::Message& action_proto,
const google::protobuf::FieldDescriptor* field_desc) {
std::string scratch;
const std::string& arg = action_proto.GetReflection()->GetStringReference(
action_proto, field_desc, &scratch);
FuzzFunction::ImplBody(module, arg);
}
};
} // namespace android::fuzz
// Fuzz existing C++ class, ClassType, with a collection of functions under the name Action.
//
// Prerequisite: ActionProto must be defined in Protobuf to describe possible actions:
// message FooActionProto {
// message NoArgs {}
// oneof value {
// bool do_foo = 1;
// NoArgs do_bar = 1;
// }
// }
// Use it to fuzz a C++ class Foo by doing the following:
// FUZZ_CLASS(Foo, FooAction)
// After linking functions of Foo to FooAction, execute all actions by:
// FooAction::ExecuteAll(foo_object, action_protos)
#define FUZZ_CLASS(ClassType, Action) \
class Action { \
public: \
using Proto = Action##Proto; \
using Class = ClassType; \
using FunctionMap = android::fuzz::FunctionMap<Class>; \
static FunctionMap* GetFunctionMap() { \
static Action::FunctionMap map; \
return &map; \
} \
static void ExecuteAll(Class* module, \
const google::protobuf::RepeatedPtrField<Proto>& action_protos) { \
[[maybe_unused]] static int consistent = android::fuzz::CheckConsistency<Action>(); \
android::fuzz::ExecuteAllActionProtos<Action>(module, action_protos); \
} \
}
#define FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) Action##_##FunctionName
#define FUZZ_FUNCTION_TAG_NAME(FunctionName) k##FunctionName
// Implement an action defined in protobuf. Example:
// message FooActionProto {
// oneof value {
// bool do_foo = 1;
// }
// }
// class Foo { public: void DoAwesomeFoo(bool arg); };
// FUZZ_OBJECT(FooAction, Foo);
// FUZZ_FUNCTION(FooAction, DoFoo, module, bool arg) {
// module->DoAwesomeFoo(arg);
// }
// The name DoFoo is the camel case name of the action in protobuf definition of FooActionProto.
#define FUZZ_FUNCTION(Action, FunctionName, module, ...) \
class FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) { \
public: \
using Class = Action::Class; \
static void ImplBody(Action::Class*, ##__VA_ARGS__); \
\
private: \
static bool registered_; \
}; \
auto FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::registered_ = ([] { \
auto tag = Action::Proto::ValueCase::FUZZ_FUNCTION_TAG_NAME(FunctionName); \
auto func = \
&::android::fuzz::ActionPerfomer<FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName), \
void(__VA_ARGS__)>::Invoke; \
Action::GetFunctionMap()->CheckEmplace(tag, func); \
return true; \
})(); \
void FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::ImplBody(Action::Class* module, \
##__VA_ARGS__)
// Implement a simple action by linking it to the function with the same name. Example:
// message FooActionProto {
// message NoArgs {}
// oneof value {
// NoArgs do_bar = 1;
// }
// }
// class Foo { public void DoBar(); };
// FUZZ_OBJECT(FooAction, Foo);
// FUZZ_FUNCTION(FooAction, DoBar);
// The name DoBar is the camel case name of the action in protobuf definition of FooActionProto, and
// also the name of the function of Foo.
#define FUZZ_SIMPLE_FUNCTION(Action, FunctionName) \
FUZZ_FUNCTION(Action, FunctionName, module) { (void)module->FunctionName(); }

View File

@ -173,6 +173,7 @@ class ISnapshotManager {
// Map a snapshotted partition for OTA clients to write to. Write-protected regions are
// determined previously in CreateSnapshots.
// |snapshot_path| must not be nullptr.
virtual bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
std::string* snapshot_path) = 0;

View File

@ -21,6 +21,7 @@
#include <tuple>
#include <android-base/logging.h>
#include <src/libfuzzer/libfuzzer_macro.h>
#include <storage_literals/storage_literals.h>
#include "fuzz_utils.h"
@ -31,11 +32,12 @@ using android::base::LogSeverity;
using android::base::SetLogger;
using android::base::StderrLogger;
using android::base::StdioLogger;
using android::fuzz::Bool;
using android::fuzz::FuzzData;
using android::fuzz::FuzzObject;
using android::fs_mgr::CreateLogicalPartitionParams;
using android::fuzz::CheckedCast;
using android::snapshot::SnapshotFuzzData;
using android::snapshot::SnapshotFuzzEnv;
using android::snapshot::SnapshotManagerFuzzData;
using chromeos_update_engine::DeltaArchiveManifest;
using google::protobuf::RepeatedPtrField;
// Avoid linking to libgsi since it needs disk I/O.
namespace android::gsi {
@ -51,49 +53,106 @@ std::string GetDsuSlot(const std::string& install_dir) {
namespace android::snapshot {
class FuzzSnapshotManager : public FuzzObject<ISnapshotManager, uint8_t> {
public:
FuzzSnapshotManager();
};
const SnapshotFuzzData* current_data = nullptr;
FuzzSnapshotManager::FuzzSnapshotManager() {
AddFunction([this]() { (void)get()->BeginUpdate(); });
AddFunction([this]() { (void)get()->CancelUpdate(); });
AddFunction<Bool>([this](Bool wipe) { (void)get()->FinishedSnapshotWrites(wipe); });
AddFunction([this]() { (void)get()->InitiateMerge(); });
AddFunction<Bool, Bool>([this](auto has_before_cancel, auto fail_before_cancel) {
std::function<bool()> before_cancel;
if (has_before_cancel) {
before_cancel = [=]() { return fail_before_cancel; };
}
(void)get()->ProcessUpdateState({}, before_cancel);
});
AddFunction<Bool>([this](auto has_progress_arg) {
double progress;
(void)get()->GetUpdateState(has_progress_arg ? &progress : nullptr);
});
// TODO add CreateUpdateSnapshots according to proto
// TODO add MapUpdateSnapshot
// TODO add UnmapUpdateSnapshot using names from the dictionary
AddFunction([this]() { (void)get()->NeedSnapshotsInFirstStageMount(); });
// TODO add CreateLogicalAndSnapshotPartitions
AddFunction<Bool>([this](const Bool& has_callback) {
std::function<void()> callback;
if (has_callback) {
callback = []() {};
}
(void)get()->HandleImminentDataWipe(callback);
});
AddFunction([this]() { (void)get()->RecoveryCreateSnapshotDevices(); });
// TODO add RecoveryCreateSnapshotDevices with metadata_device arg
AddFunction([this]() {
std::stringstream ss;
(void)get()->Dump(ss);
});
AddFunction([this]() { (void)get()->EnsureMetadataMounted(); });
AddFunction([this]() { (void)get()->GetSnapshotMergeStatsInstance(); });
SnapshotFuzzEnv* GetSnapshotFuzzEnv();
CheckFunctionsSize();
FUZZ_CLASS(ISnapshotManager, SnapshotManagerAction);
using ProcessUpdateStateArgs = SnapshotManagerAction::Proto::ProcessUpdateStateArgs;
using CreateLogicalAndSnapshotPartitionsArgs =
SnapshotManagerAction::Proto::CreateLogicalAndSnapshotPartitionsArgs;
using RecoveryCreateSnapshotDevicesArgs =
SnapshotManagerAction::Proto::RecoveryCreateSnapshotDevicesArgs;
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, BeginUpdate);
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, CancelUpdate);
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, InitiateMerge);
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, NeedSnapshotsInFirstStageMount);
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, RecoveryCreateSnapshotDevices);
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, EnsureMetadataMounted);
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, GetSnapshotMergeStatsInstance);
#define SNAPSHOT_FUZZ_FUNCTION(FunctionName, ...) \
FUZZ_FUNCTION(SnapshotManagerAction, FunctionName, snapshot, ##__VA_ARGS__)
SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool wipe) {
(void)snapshot->FinishedSnapshotWrites(wipe);
}
SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, const ProcessUpdateStateArgs& args) {
std::function<bool()> before_cancel;
if (args.has_before_cancel()) {
before_cancel = [&]() { return args.fail_before_cancel(); };
}
(void)snapshot->ProcessUpdateState({}, before_cancel);
}
SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, bool has_progress_arg) {
double progress;
(void)snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr);
}
SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool has_callback) {
std::function<void()> callback;
if (has_callback) {
callback = []() {};
}
(void)snapshot->HandleImminentDataWipe(callback);
}
SNAPSHOT_FUZZ_FUNCTION(Dump) {
std::stringstream ss;
(void)snapshot->Dump(ss);
}
SNAPSHOT_FUZZ_FUNCTION(CreateUpdateSnapshots, const DeltaArchiveManifest& manifest) {
(void)snapshot->CreateUpdateSnapshots(manifest);
}
SNAPSHOT_FUZZ_FUNCTION(UnmapUpdateSnapshot, const std::string& name) {
(void)snapshot->UnmapUpdateSnapshot(name);
}
SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions,
const CreateLogicalAndSnapshotPartitionsArgs& args) {
const std::string* super;
if (args.use_correct_super()) {
super = &GetSnapshotFuzzEnv()->super();
} else {
super = &args.super();
}
(void)snapshot->CreateLogicalAndSnapshotPartitions(
*super, std::chrono::milliseconds(args.timeout_millis()));
}
SNAPSHOT_FUZZ_FUNCTION(RecoveryCreateSnapshotDevicesWithMetadata,
const RecoveryCreateSnapshotDevicesArgs& args) {
std::unique_ptr<AutoDevice> device;
if (args.has_metadata_device_object()) {
device = std::make_unique<DummyAutoDevice>(args.metadata_mounted());
}
(void)snapshot->RecoveryCreateSnapshotDevices(device);
}
SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, const CreateLogicalPartitionParamsProto& params_proto) {
auto partition_opener = std::make_unique<TestPartitionOpener>(GetSnapshotFuzzEnv()->super());
CreateLogicalPartitionParams params;
if (params_proto.use_correct_super()) {
params.block_device = GetSnapshotFuzzEnv()->super();
} else {
params.block_device = params_proto.block_device();
}
if (params_proto.has_metadata_slot()) {
params.metadata_slot = params_proto.metadata_slot();
}
params.partition_name = params_proto.partition_name();
params.force_writable = params_proto.force_writable();
params.timeout_ms = std::chrono::milliseconds(params_proto.timeout_millis());
params.device_name = params_proto.device_name();
params.partition_opener = partition_opener.get();
std::string path;
(void)snapshot->MapUpdateSnapshot(params, &path);
}
// During global init, log all messages to stdio. This is only done once.
@ -107,35 +166,50 @@ void FatalOnlyLogger(LogId logid, LogSeverity severity, const char* tag, const c
unsigned int line, const char* message) {
if (severity == LogSeverity::FATAL) {
StderrLogger(logid, severity, tag, file, line, message);
// If test fails by a LOG(FATAL) or CHECK(), log the corpus. If it abort()'s, there's
// nothing else we can do.
StderrLogger(logid, severity, tag, __FILE__, __LINE__,
"Attempting to dump current corpus:");
if (current_data == nullptr) {
StderrLogger(logid, severity, tag, __FILE__, __LINE__, "Current corpus is nullptr.");
} else {
std::string content;
if (!google::protobuf::TextFormat::PrintToString(*current_data, &content)) {
StderrLogger(logid, severity, tag, __FILE__, __LINE__,
"Failed to print corpus to string.");
} else {
StderrLogger(logid, severity, tag, __FILE__, __LINE__, content.c_str());
}
}
}
}
// Stop logging (except fatal messages) after global initialization. This is only done once.
int StopLoggingAfterGlobalInit() {
[[maybe_unused]] static protobuf_mutator::protobuf::LogSilencer log_silincer;
SetLogger(&FatalOnlyLogger);
return 0;
}
} // namespace android::snapshot
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
using namespace android::snapshot;
SnapshotFuzzEnv* GetSnapshotFuzzEnv() {
[[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit();
static SnapshotFuzzEnv env;
static FuzzSnapshotManager fuzz_snapshot_manager;
[[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit();
return &env;
}
CHECK(env.InitOk());
FuzzData fuzz_data(data, size);
} // namespace android::snapshot
auto snapshot_manager_data = fuzz_data.Consume<SnapshotManagerFuzzData>();
if (!snapshot_manager_data.has_value()) {
return 0;
}
auto snapshot_manager = env.CreateSnapshotManager(snapshot_manager_data.value());
DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) {
using namespace android::snapshot;
current_data = &snapshot_fuzz_data;
auto env = GetSnapshotFuzzEnv();
env->CheckSoftReset();
auto snapshot_manager = env->CheckCreateSnapshotManager(snapshot_fuzz_data);
CHECK(snapshot_manager);
fuzz_snapshot_manager.DepleteData(snapshot_manager.get(), &fuzz_data);
return 0;
SnapshotManagerAction::ExecuteAll(snapshot_manager.get(), snapshot_fuzz_data.actions());
}

View File

@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <sysexits.h>
#include <chrono>
#include <string>
#include <android-base/file.h>
@ -29,17 +30,30 @@
#include <storage_literals/storage_literals.h>
#include "snapshot_fuzz_utils.h"
#include "utility.h"
// Prepends the errno string, but it is good enough.
#ifndef PCHECK
#define PCHECK(x) CHECK(x) << strerror(errno) << ": "
#endif
using namespace android::storage_literals;
using namespace std::chrono_literals;
using namespace std::string_literals;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::base::WriteStringToFile;
using android::dm::LoopControl;
using android::fiemap::IImageManager;
using android::fiemap::ImageManager;
using android::fs_mgr::BlockDeviceInfo;
using android::fs_mgr::IPartitionOpener;
using chromeos_update_engine::DynamicPartitionMetadata;
// This directory is exempted from pinning in ImageManager.
static const char MNT_DIR[] = "/data/gsi/ota/test/";
static const char MNT_DIR[] = "/mnt";
static const char FAKE_ROOT_NAME[] = "snapshot_fuzz";
static const auto SUPER_IMAGE_SIZE = 16_MiB;
static const auto FAKE_ROOT_SIZE = 64_MiB;
@ -128,6 +142,9 @@ class AutoUnmount : public AutoDevice {
class AutoMemBasedDir : public AutoDevice {
public:
static std::unique_ptr<AutoMemBasedDir> New(const std::string& name, uint64_t size) {
if (!Mkdir(MNT_DIR)) {
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
}
auto ret = std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(name));
ret->auto_delete_mount_dir_ = AutoDeleteDir::New(ret->mount_path());
if (!ret->auto_delete_mount_dir_->HasDevice()) {
@ -137,20 +154,31 @@ class AutoMemBasedDir : public AutoDevice {
if (!ret->auto_umount_mount_point_->HasDevice()) {
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
}
// path() does not need to be deleted upon destruction, hence it is not wrapped with
// AutoDeleteDir.
if (!Mkdir(ret->path())) {
// tmp_path() and persist_path does not need to be deleted upon destruction, hence it is
// not wrapped with AutoDeleteDir.
if (!Mkdir(ret->tmp_path())) {
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
}
if (!Mkdir(ret->persist_path())) {
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
}
return ret;
}
// Return the scratch directory.
std::string path() const {
// Return the temporary scratch directory.
std::string tmp_path() const {
CHECK(HasDevice());
return mount_path() + "/root";
return mount_path() + "/tmp";
}
// Return the temporary scratch directory.
std::string persist_path() const {
CHECK(HasDevice());
return mount_path() + "/persist";
}
// Delete all contents in tmp_path() and start over. tmp_path() itself is re-created.
void CheckSoftReset() {
PCHECK(RmdirRecursive(tmp_path()));
PCHECK(Mkdir(tmp_path()));
}
// Delete all contents in path() and start over. path() itself is re-created.
bool SoftReset() { return RmdirRecursive(path()) && Mkdir(path()); }
private:
AutoMemBasedDir(const std::string& name) : AutoDevice(name) {}
@ -164,64 +192,123 @@ class AutoMemBasedDir : public AutoDevice {
SnapshotFuzzEnv::SnapshotFuzzEnv() {
fake_root_ = AutoMemBasedDir::New(FAKE_ROOT_NAME, FAKE_ROOT_SIZE);
CHECK(fake_root_ != nullptr);
CHECK(fake_root_->HasDevice());
loop_control_ = std::make_unique<LoopControl>();
mapped_super_ = CheckMapSuper(fake_root_->persist_path(), loop_control_.get(), &fake_super_);
}
SnapshotFuzzEnv::~SnapshotFuzzEnv() = default;
bool SnapshotFuzzEnv::InitOk() const {
if (fake_root_ == nullptr || !fake_root_->HasDevice()) return false;
return true;
void CheckZeroFill(const std::string& file, size_t size) {
std::string zeros(size, '\0');
PCHECK(WriteStringToFile(zeros, file)) << "Cannot write zeros to " << file;
}
bool SnapshotFuzzEnv::SoftReset() {
return fake_root_->SoftReset();
void SnapshotFuzzEnv::CheckSoftReset() {
fake_root_->CheckSoftReset();
CheckZeroFill(super(), SUPER_IMAGE_SIZE);
}
std::unique_ptr<IImageManager> SnapshotFuzzEnv::CreateFakeImageManager(
const std::string& fake_root) {
auto images_dir = fake_root + "/images";
std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager(
const std::string& path) {
auto images_dir = path + "/images";
auto metadata_dir = images_dir + "/metadata";
auto data_dir = images_dir + "/data";
if (!Mkdir(images_dir) || !Mkdir(metadata_dir) || !Mkdir(data_dir)) {
return nullptr;
}
PCHECK(Mkdir(images_dir));
PCHECK(Mkdir(metadata_dir));
PCHECK(Mkdir(data_dir));
return ImageManager::Open(metadata_dir, data_dir);
}
std::unique_ptr<TestPartitionOpener> SnapshotFuzzEnv::CreatePartitionOpener(
const std::string& fake_root) {
auto fake_super = fake_root + "/super.img";
std::string zeros(SUPER_IMAGE_SIZE, '\0');
if (!WriteStringToFile(zeros, fake_super)) {
PLOG(ERROR) << "Cannot write zeros to " << fake_super;
return nullptr;
}
return std::make_unique<TestPartitionOpener>(fake_super);
// Helper to create a loop device for a file.
static void CheckCreateLoopDevice(LoopControl* control, const std::string& file,
const std::chrono::milliseconds& timeout_ms, std::string* path) {
static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags));
PCHECK(file_fd >= 0) << "Could not open file: " << file;
CHECK(control->Attach(file_fd, timeout_ms, path))
<< "Could not create loop device for: " << file;
}
std::string SnapshotFuzzEnv::root() const {
CHECK(InitOk());
return fake_root_->path();
class AutoDetachLoopDevice : public AutoDevice {
public:
AutoDetachLoopDevice(LoopControl* control, const std::string& device)
: AutoDevice(device), control_(control) {}
~AutoDetachLoopDevice() { control_->Detach(name_); }
private:
LoopControl* control_;
};
std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMapSuper(const std::string& fake_persist_path,
LoopControl* control,
std::string* fake_super) {
auto super_img = fake_persist_path + "/super.img";
CheckZeroFill(super_img, SUPER_IMAGE_SIZE);
CheckCreateLoopDevice(control, super_img, 1s, fake_super);
return std::make_unique<AutoDetachLoopDevice>(control, *fake_super);
}
std::unique_ptr<ISnapshotManager> SnapshotFuzzEnv::CreateSnapshotManager(
const SnapshotManagerFuzzData& data) {
// TODO(b/154633114): create valid super partition according to fuzz data
auto partition_opener = CreatePartitionOpener(root());
if (partition_opener == nullptr) return nullptr;
auto metadata_dir = root() + "/snapshot_metadata";
if (!Mkdir(metadata_dir)) return nullptr;
std::unique_ptr<ISnapshotManager> SnapshotFuzzEnv::CheckCreateSnapshotManager(
const SnapshotFuzzData& data) {
auto partition_opener = std::make_unique<TestPartitionOpener>(super());
CheckWriteSuperMetadata(data, *partition_opener);
auto metadata_dir = fake_root_->tmp_path() + "/snapshot_metadata";
PCHECK(Mkdir(metadata_dir));
auto device_info = new SnapshotFuzzDeviceInfo(data.device_info_data,
auto device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(),
std::move(partition_opener), metadata_dir);
auto snapshot = SnapshotManager::New(device_info /* takes ownership */);
snapshot->images_ = CreateFakeImageManager(root());
snapshot->has_local_image_manager_ = data.is_local_image_manager;
snapshot->images_ = CheckCreateFakeImageManager(fake_root_->tmp_path());
snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager();
return snapshot;
}
const std::string& SnapshotFuzzEnv::super() const {
return fake_super_;
}
void SnapshotFuzzEnv::CheckWriteSuperMetadata(const SnapshotFuzzData& data,
const IPartitionOpener& opener) {
if (!data.is_super_metadata_valid()) {
// Leave it zero.
return;
}
BlockDeviceInfo super_device("super", SUPER_IMAGE_SIZE, 0, 0, 4096);
std::vector<BlockDeviceInfo> devices = {super_device};
auto builder = MetadataBuilder::New(devices, "super", 65536, 2);
CHECK(builder != nullptr);
// Attempt to create a super partition metadata using proto. All errors are ignored.
for (const auto& group_proto : data.super_data().dynamic_partition_metadata().groups()) {
(void)builder->AddGroup(group_proto.name(), group_proto.size());
for (const auto& partition_name : group_proto.partition_names()) {
(void)builder->AddPartition(partition_name, group_proto.name(),
LP_PARTITION_ATTR_READONLY);
}
}
for (const auto& partition_proto : data.super_data().partitions()) {
auto p = builder->FindPartition(partition_proto.partition_name());
if (p == nullptr) continue;
(void)builder->ResizePartition(p, partition_proto.new_partition_info().size());
}
auto metadata = builder->Export();
// metadata may be nullptr if it is not valid (e.g. partition name too long).
// In this case, just use empty super partition data.
if (metadata == nullptr) {
builder = MetadataBuilder::New(devices, "super", 65536, 2);
CHECK(builder != nullptr);
metadata = builder->Export();
CHECK(metadata != nullptr);
}
CHECK(FlashPartitionTable(opener, super(), *metadata.get()));
}
} // namespace android::snapshot

View File

@ -16,32 +16,27 @@
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android/snapshot/snapshot_fuzz.pb.h>
#include <libdm/loop_control.h>
#include <libfiemap/image_manager.h>
#include <liblp/liblp.h>
#include <libsnapshot/auto_device.h>
#include <libsnapshot/test_helpers.h>
// libsnapshot-specific code for fuzzing. Defines fake classes that are depended
// by SnapshotManager.
#include "android/snapshot/snapshot_fuzz.pb.h"
namespace android::snapshot {
// Controls the behavior of IDeviceInfo.
typedef struct SnapshotFuzzDeviceInfoData {
bool slot_suffix_is_a : 1;
bool is_overlayfs_setup : 1;
bool allow_set_boot_control_merge_status : 1;
bool allow_set_slot_as_unbootable : 1;
bool is_recovery : 1;
} __attribute__((packed)) SnapshotFuzzDeviceInfoData;
// Controls the behavior of the test SnapshotManager.
typedef struct SnapshotManagerFuzzData {
SnapshotFuzzDeviceInfoData device_info_data;
bool is_local_image_manager : 1;
} __attribute__((packed)) SnapshotManagerFuzzData;
class AutoMemBasedDir;
class DummyAutoDevice : public AutoDevice {
public:
DummyAutoDevice(bool mounted) : AutoDevice(mounted ? "dummy" : "") {}
};
// Prepare test environment. This has a heavy overhead and should be done once.
class SnapshotFuzzEnv {
public:
@ -52,36 +47,41 @@ class SnapshotFuzzEnv {
SnapshotFuzzEnv();
~SnapshotFuzzEnv();
// Check if environment is initialized properly.
bool InitOk() const;
// A scratch directory for the test to play around with. The scratch directory
// is backed by tmpfs. SoftReset() clears the directory.
std::string root() const;
// Soft reset part of the environment before running the next test.
bool SoftReset();
// Abort if fails.
void CheckSoftReset();
// Create a snapshot manager for this test run.
// Client is responsible for maintaining the lifetime of |data| over the life time of
// ISnapshotManager.
std::unique_ptr<ISnapshotManager> CreateSnapshotManager(const SnapshotManagerFuzzData& data);
std::unique_ptr<ISnapshotManager> CheckCreateSnapshotManager(const SnapshotFuzzData& data);
// Return path to super partition.
const std::string& super() const;
private:
std::unique_ptr<AutoMemBasedDir> fake_root_;
std::unique_ptr<android::dm::LoopControl> loop_control_;
std::unique_ptr<AutoDevice> mapped_super_;
std::string fake_super_;
static std::unique_ptr<android::fiemap::IImageManager> CreateFakeImageManager(
const std::string& fake_root);
static std::unique_ptr<TestPartitionOpener> CreatePartitionOpener(const std::string& fake_root);
static std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager(
const std::string& fake_tmp_path);
static std::unique_ptr<AutoDevice> CheckMapSuper(const std::string& fake_persist_path,
android::dm::LoopControl* control,
std::string* fake_super);
void CheckWriteSuperMetadata(const SnapshotFuzzData& proto,
const android::fs_mgr::IPartitionOpener& opener);
};
class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
public:
// Client is responsible for maintaining the lifetime of |data|.
SnapshotFuzzDeviceInfo(const SnapshotFuzzDeviceInfoData& data,
SnapshotFuzzDeviceInfo(const FuzzDeviceInfoData& data,
std::unique_ptr<TestPartitionOpener>&& partition_opener,
const std::string& metadata_dir)
: data_(data),
: data_(&data),
partition_opener_(std::move(partition_opener)),
metadata_dir_(metadata_dir) {}
@ -97,17 +97,21 @@ class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
}
// Following APIs are fuzzed.
std::string GetSlotSuffix() const override { return data_.slot_suffix_is_a ? "_a" : "_b"; }
std::string GetOtherSlotSuffix() const override { return data_.slot_suffix_is_a ? "_b" : "_a"; }
bool IsOverlayfsSetup() const override { return data_.is_overlayfs_setup; }
bool SetBootControlMergeStatus(android::hardware::boot::V1_1::MergeStatus) override {
return data_.allow_set_boot_control_merge_status;
std::string GetSlotSuffix() const override { return data_->slot_suffix_is_a() ? "_a" : "_b"; }
std::string GetOtherSlotSuffix() const override {
return data_->slot_suffix_is_a() ? "_b" : "_a";
}
bool SetSlotAsUnbootable(unsigned int) override { return data_.allow_set_slot_as_unbootable; }
bool IsRecovery() const override { return data_.is_recovery; }
bool IsOverlayfsSetup() const override { return data_->is_overlayfs_setup(); }
bool SetBootControlMergeStatus(android::hardware::boot::V1_1::MergeStatus) override {
return data_->allow_set_boot_control_merge_status();
}
bool SetSlotAsUnbootable(unsigned int) override {
return data_->allow_set_slot_as_unbootable();
}
bool IsRecovery() const override { return data_->is_recovery(); }
private:
SnapshotFuzzDeviceInfoData data_;
const FuzzDeviceInfoData* data_;
std::unique_ptr<TestPartitionOpener> partition_opener_;
std::string metadata_dir_;
};

View File

@ -0,0 +1,75 @@
//
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// A subset of system/update_engine/update_metadata.proto. A separate file is
// used here because:
// - The original file is optimized for LITE_RUNTIME, but fuzzing needs
// reflection.
// - The definition here has less fields. libsnapshot only uses fields declared
// here, and all fields declared here are fuzzed by libsnapshot_fuzzer. If
// libsnapshot uses more fields in system/update_engine/update_metadata.proto
// in the future, they must be added here too, otherwise it will fail to
// compile.
//
// It is okay that this file is older than
// system/update_engine/update_metadata.proto as long as the messages defined
// here can also be parsed by protobuf defined there. However, it is not
// okay to add fields here without adding them to
// system/update_engine/update_metadata.proto. Doing so will cause a compiler
// error when libsnapshot code starts to use these dangling fields.
syntax = "proto2";
package chromeos_update_engine;
message Extent {
optional uint64 start_block = 1;
optional uint64 num_blocks = 2;
}
message PartitionInfo {
optional uint64 size = 1;
}
message InstallOperation {
enum Type { SOURCE_COPY = 4; }
required Type type = 1;
repeated Extent src_extents = 4;
repeated Extent dst_extents = 6;
}
message PartitionUpdate {
required string partition_name = 1;
optional PartitionInfo new_partition_info = 7;
repeated InstallOperation operations = 8;
optional Extent hash_tree_extent = 11;
optional Extent fec_extent = 15;
}
message DynamicPartitionGroup {
required string name = 1;
optional uint64 size = 2;
repeated string partition_names = 3;
}
message DynamicPartitionMetadata {
repeated DynamicPartitionGroup groups = 1;
}
message DeltaArchiveManifest {
repeated PartitionUpdate partitions = 13;
optional DynamicPartitionMetadata dynamic_partition_metadata = 15;
}