libsnapshot_fuzzer: use protobuf
Use protobuf because it already has all the fuzzing implemenetations. Delete fuzz_utils. Pros: - Fuzzing protobuf is faster; it is easy to achieve 4K exec/s - It is more guided; protobufs are fuzzed using mutators, and mutators should have better knowledge of the structure of the fuzz data - No more hand-written parsing code of the fuzz data. That code in fuzz_utils.h is deleted. - Corpus data can be reused even after adding new fields in the protobuf - Corpus data is human-readable and easily manually written (it is the text format of the protobuf) Cons: - The "actions" are "declared" in protobuf definition and "defined" in C++, so there's more boilerplate to write. Adding a new "Action" requires changes in both. Test: run libsnapshot_fuzzer Bug: 154633114 Change-Id: Idc2a6b2c087e370e4cfef53142a244b9b275389e
This commit is contained in:
parent
5eb2d6fa27
commit
90a9393ea0
8 changed files with 387 additions and 323 deletions
|
|
@ -254,9 +254,11 @@ cc_fuzz {
|
|||
|
||||
native_coverage : true,
|
||||
srcs: [
|
||||
// Compile the protobuf definition again with type full.
|
||||
"android/snapshot/snapshot_fuzz.proto",
|
||||
"fuzz_utils.cpp",
|
||||
"snapshot_fuzz.cpp",
|
||||
"snapshot_fuzz_utils.cpp",
|
||||
"fuzz_utils.cpp",
|
||||
],
|
||||
static_libs: [
|
||||
"libbase",
|
||||
|
|
@ -269,12 +271,16 @@ cc_fuzz {
|
|||
"liblp",
|
||||
"libsnapshot_init", // don't use binder or hwbinder
|
||||
"libsnapshot_test_helpers",
|
||||
"libprotobuf-cpp-lite",
|
||||
"libprotobuf-mutator",
|
||||
"update_metadata-protos",
|
||||
],
|
||||
header_libs: [
|
||||
"libstorage_literals_headers",
|
||||
],
|
||||
proto: {
|
||||
type: "full",
|
||||
canonical_path_from_root: false,
|
||||
},
|
||||
|
||||
fuzz_config: {
|
||||
cc: ["android-virtual-ab+bugs@google.com"],
|
||||
|
|
|
|||
76
fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
Normal file
76
fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
reserved 7;
|
||||
reserved "create_update_snapshots";
|
||||
reserved 8;
|
||||
reserved "map_update_snapshot";
|
||||
reserved 9;
|
||||
reserved "unmap_update_snapshot";
|
||||
reserved 11;
|
||||
reserved "create_logical_and_snapshot_partitions";
|
||||
reserved 14;
|
||||
reserved "recovery_create_snapshot_devices_with_metadata";
|
||||
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;
|
||||
NoArgs need_snapshots_in_first_stage_mount = 10;
|
||||
bool handle_imminent_data_wipe = 12;
|
||||
NoArgs recovery_create_snapshot_devices = 13;
|
||||
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;
|
||||
// More data used to prep the test before running actions.
|
||||
reserved 3 to 9999;
|
||||
repeated SnapshotManagerActionProto actions = 10000;
|
||||
}
|
||||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -12,256 +12,242 @@
|
|||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
} // 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 ↦ \
|
||||
} \
|
||||
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(); }
|
||||
|
|
|
|||
|
|
@ -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,10 @@ 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::fuzz::CheckedCast;
|
||||
using android::snapshot::SnapshotFuzzData;
|
||||
using android::snapshot::SnapshotFuzzEnv;
|
||||
using android::snapshot::SnapshotManagerFuzzData;
|
||||
using google::protobuf::RepeatedPtrField;
|
||||
|
||||
// Avoid linking to libgsi since it needs disk I/O.
|
||||
namespace android::gsi {
|
||||
|
|
@ -51,49 +51,49 @@ std::string GetDsuSlot(const std::string& install_dir) {
|
|||
|
||||
namespace android::snapshot {
|
||||
|
||||
class FuzzSnapshotManager : public FuzzObject<ISnapshotManager, uint8_t> {
|
||||
public:
|
||||
FuzzSnapshotManager();
|
||||
};
|
||||
FUZZ_CLASS(ISnapshotManager, SnapshotManagerAction);
|
||||
|
||||
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(); });
|
||||
using ProcessUpdateStateArgs = SnapshotManagerAction::Proto::ProcessUpdateStateArgs;
|
||||
|
||||
CheckFunctionsSize();
|
||||
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);
|
||||
}
|
||||
|
||||
// During global init, log all messages to stdio. This is only done once.
|
||||
|
|
@ -111,31 +111,24 @@ void FatalOnlyLogger(LogId logid, LogSeverity severity, const char* tag, const c
|
|||
}
|
||||
// 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) {
|
||||
DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) {
|
||||
using namespace android::snapshot;
|
||||
|
||||
[[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit();
|
||||
static SnapshotFuzzEnv env;
|
||||
static FuzzSnapshotManager fuzz_snapshot_manager;
|
||||
[[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit();
|
||||
|
||||
env.CheckSoftReset();
|
||||
FuzzData fuzz_data(data, size);
|
||||
|
||||
auto snapshot_manager_data = fuzz_data.Consume<SnapshotManagerFuzzData>();
|
||||
if (!snapshot_manager_data.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
auto snapshot_manager = env.CheckCreateSnapshotManager(snapshot_manager_data.value());
|
||||
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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,16 +250,16 @@ std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMapSuper(const std::string& fa
|
|||
}
|
||||
|
||||
std::unique_ptr<ISnapshotManager> SnapshotFuzzEnv::CheckCreateSnapshotManager(
|
||||
const SnapshotManagerFuzzData& data) {
|
||||
const SnapshotFuzzData& data) {
|
||||
auto partition_opener = std::make_unique<TestPartitionOpener>(super());
|
||||
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_ = CheckCreateFakeImageManager(fake_root_->tmp_path());
|
||||
snapshot->has_local_image_manager_ = data.is_local_image_manager;
|
||||
snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager();
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,23 +24,10 @@
|
|||
// 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;
|
||||
|
||||
// Prepare test environment. This has a heavy overhead and should be done once.
|
||||
|
|
@ -60,8 +47,7 @@ class SnapshotFuzzEnv {
|
|||
// 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> CheckCreateSnapshotManager(
|
||||
const SnapshotManagerFuzzData& data);
|
||||
std::unique_ptr<ISnapshotManager> CheckCreateSnapshotManager(const SnapshotFuzzData& data);
|
||||
|
||||
// Return path to super partition.
|
||||
const std::string& super() const;
|
||||
|
|
@ -82,10 +68,10 @@ class SnapshotFuzzEnv {
|
|||
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) {}
|
||||
|
||||
|
|
@ -101,17 +87,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_;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue