Remove the libsnapshot fuzzer.

The only bugs in the corpus here were bugs in the fuzzer itself. Part
of the reason is its speed - it's limited by the dm layer so can't fuzz
optimally. That also makes it difficult to maintain, since it's
effectively a functional OTA fuzzer, and is very sensitive to the
implementation of libsnapshot.

After discussing this through with the team, we think the best course is
to sunset this and aim for improved unit and functional OTA testing.

Bug: 227656961
Test: builds
Change-Id: I2ecf838be336ee06459988ef282f0694cebfce51
This commit is contained in:
David Anderson 2023-03-09 08:25:18 -08:00
parent 42908c45e5
commit 6aaae78047
10 changed files with 0 additions and 1878 deletions

View file

@ -366,80 +366,6 @@ cc_test {
gtest: false,
}
cc_defaults {
name: "libsnapshot_fuzzer_defaults",
defaults: [
"libsnapshot_cow_defaults",
],
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",
// Compile libsnapshot sources directly to avoid dependency
// to update_metadata-protos
":libsnapshot_sources",
],
static_libs: [
"libbase",
"libbrotli",
"libc++fs",
"libchrome",
"libcrypto_static",
"libcutils",
"libext2_uuid",
"libext4_utils",
"libfstab",
"libfs_mgr",
"libgtest", // from libsnapshot_test_helpers
"libgmock", // from libsnapshot_test_helpers
"liblog",
"liblp",
"libsnapshot_cow",
"libsnapshot_test_helpers",
"libprotobuf-mutator",
"libz",
],
header_libs: [
"libfiemap_headers",
"libstorage_literals_headers",
"libupdate_engine_headers",
],
proto: {
type: "full",
canonical_path_from_root: false,
local_include_dirs: ["."],
},
}
cc_fuzz {
name: "libsnapshot_fuzzer",
defaults: ["libsnapshot_fuzzer_defaults"],
corpus: ["corpus/*"],
fuzz_config: {
cc: ["android-virtual-ab+bugs@google.com"],
componentid: 30545,
hotlists: ["1646452"],
fuzz_on_haiku_host: false,
fuzz_on_haiku_device: true,
},
}
cc_test {
name: "libsnapshot_fuzzer_test",
defaults: ["libsnapshot_fuzzer_defaults"],
data: ["corpus/*"],
test_suites: [
"device-tests",
],
auto_gen_config: true,
require_root: true,
}
cc_test {
name: "cow_api_test",
defaults: [

View file

@ -1,110 +0,0 @@
// 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;
}
reserved 18 to 9999;
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;
// Test directives that has nothing to do with ISnapshotManager API surface.
NoArgs switch_slot = 10000;
}
}
// 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;
// Whether the directory that mocks /metadata/ota/snapshot is created.
bool has_metadata_snapshots_dir = 5;
// More data used to prep the test before running actions.
reserved 6 to 9999;
repeated SnapshotManagerActionProto actions = 10000;
}

View file

@ -1,41 +0,0 @@
device_info_data {
allow_set_slot_as_unbootable: true
is_recovery: true
}
is_super_metadata_valid: true
super_data {
partitions {
partition_name: "sys_a"
new_partition_info {
size: 3145728
}
}
partitions {
partition_name: "vnnd_"
new_partition_info {
size: 3145728
}
}
partitions {
partition_name: "prd_a"
new_partition_info {
}
}
dynamic_partition_metadata {
groups {
name: "group_google_dp_a"
size: 34375467008
partition_names: "sys_a"
partition_names: "vnd_a"
partition_names: "prd_a"
}
}
}
has_metadata_snapshots_dir: true
actions {
handle_imminent_data_wipe: true
}
actions {
begin_update {
}
}

View file

@ -1,161 +0,0 @@
device_info_data {
slot_suffix_is_a: true
is_overlayfs_setup: false
allow_set_boot_control_merge_status: true
allow_set_slot_as_unbootable: true
is_recovery: false
}
manager_data {
is_local_image_manager: false
}
is_super_metadata_valid: true
super_data {
partitions {
partition_name: "sys_a"
new_partition_info {
size: 3145728
}
}
partitions {
partition_name: "vnd_a"
new_partition_info {
size: 3145728
}
}
partitions {
partition_name: "prd_a"
new_partition_info {
size: 3145728
}
}
dynamic_partition_metadata {
groups {
name: "group_google_dp_a"
size: 15728640
partition_names: "sys_a"
partition_names: "vnd_a"
partition_names: "prd_a"
}
}
}
has_metadata_snapshots_dir: true
actions {
begin_update {
}
}
actions {
create_update_snapshots {
partitions {
partition_name: "sys"
new_partition_info {
size: 3878912
}
operations {
type: ZERO,
dst_extents {
start_block: 0
num_blocks: 947
}
}
}
partitions {
partition_name: "vnd"
new_partition_info {
size: 3878912
}
operations {
type: ZERO,
dst_extents {
start_block: 0
num_blocks: 947
}
}
}
partitions {
partition_name: "prd"
new_partition_info {
size: 3878912
}
operations {
type: ZERO,
dst_extents {
start_block: 0
num_blocks: 947
}
}
}
dynamic_partition_metadata {
groups {
name: "group_google_dp"
size: 15728640
partition_names: "sys"
partition_names: "vnd"
partition_names: "prd"
}
}
}
}
actions {
map_update_snapshot {
use_correct_super: true
has_metadata_slot: true
metadata_slot: 1
partition_name: "sys_b"
force_writable: true
timeout_millis: 3000
}
}
actions {
map_update_snapshot {
use_correct_super: true
has_metadata_slot: true
metadata_slot: 1
partition_name: "vnd_b"
force_writable: true
timeout_millis: 3000
}
}
actions {
map_update_snapshot {
use_correct_super: true
has_metadata_slot: true
metadata_slot: 1
partition_name: "prd_b"
force_writable: true
timeout_millis: 3000
}
}
actions {
finished_snapshot_writes: false
}
actions {
unmap_update_snapshot: "sys_b"
}
actions {
unmap_update_snapshot: "vnd_b"
}
actions {
unmap_update_snapshot: "prd_b"
}
actions {
switch_slot {
}
}
actions {
need_snapshots_in_first_stage_mount {
}
}
actions {
create_logical_and_snapshot_partitions {
use_correct_super: true
timeout_millis: 5000
}
}
actions {
initiate_merge {
}
}
actions {
process_update_state {
}
}

View file

@ -1,90 +0,0 @@
#!/bin/bash
PROJECT_PATH=system/core/fs_mgr/libsnapshot
FUZZ_TARGET=libsnapshot_fuzzer
TARGET_ARCH=$(get_build_var TARGET_ARCH)
FUZZ_BINARY=/data/fuzz/${TARGET_ARCH}/${FUZZ_TARGET}/${FUZZ_TARGET}
DEVICE_INIT_CORPUS_DIR=/data/fuzz/${TARGET_ARCH}/${FUZZ_TARGET}/corpus
DEVICE_GENERATED_CORPUS_DIR=/data/local/tmp/${FUZZ_TARGET}/corpus
DEVICE_GCOV_DIR=/data/local/tmp/${FUZZ_TARGET}/gcov
HOST_SCRATCH_DIR=/tmp/${FUZZ_TARGET}
GCOV_TOOL=${HOST_SCRATCH_DIR}/llvm-gcov
build_normal() (
pushd $(gettop)
NATIVE_COVERAGE="" NATIVE_LINE_COVERAGE="" NATIVE_COVERAGE_PATHS="" m ${FUZZ_TARGET}
ret=$?
popd
return ${ret}
)
build_cov() {
pushd $(gettop)
NATIVE_COVERAGE="true" NATIVE_LINE_COVERAGE="true" NATIVE_COVERAGE_PATHS="${PROJECT_PATH}" m ${FUZZ_TARGET}
ret=$?
popd
return ${ret}
}
prepare_device() {
adb root && adb remount &&
adb shell mkdir -p ${DEVICE_GENERATED_CORPUS_DIR} &&
adb shell rm -rf ${DEVICE_GCOV_DIR} &&
adb shell mkdir -p ${DEVICE_GCOV_DIR}
}
push_binary() {
adb push ${ANDROID_PRODUCT_OUT}/${FUZZ_BINARY} ${FUZZ_BINARY} &&
adb push ${ANDROID_PRODUCT_OUT}/${DEVICE_INIT_CORPUS_DIR} $(dirname ${FUZZ_BINARY})
}
prepare_host() {
which lcov || {
echo "please run:";
echo " sudo apt-get install lcov ";
return 1;
}
rm -rf ${HOST_SCRATCH_DIR} &&
mkdir -p ${HOST_SCRATCH_DIR}
}
# run_snapshot_fuzz -runs=10000
generate_corpus() {
[[ "$@" ]] || { echo "run with -runs=X"; return 1; }
prepare_device &&
build_normal &&
push_binary &&
adb shell ${FUZZ_BINARY} "$@" ${DEVICE_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_DIR}
}
run_snapshot_fuzz() {
prepare_device &&
build_cov &&
push_binary &&
adb shell GCOV_PREFIX=${DEVICE_GCOV_DIR} GCOV_PREFIX_STRIP=3 \
${FUZZ_BINARY} \
-runs=0 \
${DEVICE_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_DIR}
}
show_fuzz_result() {
prepare_host &&
unzip -o -j -d ${HOST_SCRATCH_DIR} ${ANDROID_PRODUCT_OUT}/coverage/data/fuzz/${TARGET_ARCH}/${FUZZ_TARGET}/${FUZZ_TARGET}.zip &&
adb shell find ${DEVICE_GCOV_DIR} -type f | xargs -I {} adb pull {} ${HOST_SCRATCH_DIR} &&
ls ${HOST_SCRATCH_DIR} &&
cat > ${GCOV_TOOL} <<< '
#!/bin/bash
exec llvm-cov gcov "$@"
' &&
chmod +x ${GCOV_TOOL} &&
lcov --directory ${HOST_SCRATCH_DIR} --base-directory $(gettop) --gcov-tool ${GCOV_TOOL} --capture -o ${HOST_SCRATCH_DIR}/report.cov &&
genhtml ${HOST_SCRATCH_DIR}/report.cov -o ${HOST_SCRATCH_DIR}/html &&
echo file://$(realpath ${HOST_SCRATCH_DIR}/html/index.html)
}
# run_snapshot_fuzz -runs=10000
run_snapshot_fuzz_all() {
generate_corpus "$@" &&
run_snapshot_fuzz &&
show_fuzz_result
}

View file

@ -1,38 +0,0 @@
// 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.
#include "fuzz_utils.h"
#include <android-base/logging.h>
namespace android::fuzz {
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

@ -1,285 +0,0 @@
// 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.
#pragma once
#include <map>
#include <string>
#include <string_view>
#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 {
// CHECK(value) << msg
void CheckInternal(bool value, std::string_view msg);
// 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:
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));
}
};
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;
}
// Get the field descriptor for the oneof field in the action message. If no oneof field is set,
// return nullptr.
template <typename Action>
const google::protobuf::FieldDescriptor* GetValueFieldDescriptor(
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 nullptr;
}
return action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc);
}
template <typename Action>
void ExecuteActionProto(typename Action::ClassType* module,
const typename Action::Proto& action_proto) {
const auto* field_desc = GetValueFieldDescriptor<Action>(action_proto);
if (field_desc == nullptr) return;
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::ClassType* 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; \
}
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 ActionPerformerImpl; // undefined
template <typename FuzzFunction, typename MessageProto>
struct ActionPerformerImpl<
FuzzFunction, void(const MessageProto&),
typename std::enable_if_t<std::is_base_of_v<google::protobuf::Message, MessageProto>>> {
static typename FuzzFunction::ReturnType Invoke(
typename FuzzFunction::ClassType* 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));
return FuzzFunction::ImplBody(module, arg);
}
};
template <typename FuzzFunction, typename Primitive>
struct ActionPerformerImpl<FuzzFunction, void(Primitive),
typename std::enable_if_t<std::is_arithmetic_v<Primitive>>> {
static typename FuzzFunction::ReturnType Invoke(
typename FuzzFunction::ClassType* 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);
return FuzzFunction::ImplBody(module, arg);
}
};
template <typename FuzzFunction>
struct ActionPerformerImpl<FuzzFunction, void()> {
static typename FuzzFunction::ReturnType Invoke(typename FuzzFunction::ClassType* module,
const google::protobuf::Message&,
const google::protobuf::FieldDescriptor*) {
return FuzzFunction::ImplBody(module);
}
};
template <typename FuzzFunction>
struct ActionPerformerImpl<FuzzFunction, void(const std::string&)> {
static typename FuzzFunction::ReturnType Invoke(
typename FuzzFunction::ClassType* 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);
return FuzzFunction::ImplBody(module, arg);
}
};
template <typename FuzzFunction>
struct ActionPerformer : ActionPerformerImpl<FuzzFunction, typename FuzzFunction::Signature> {};
} // 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(Class, Action) \
class Action { \
public: \
using Proto = Action##Proto; \
using ClassType = Class; \
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, void, IFoo* 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, Return, ModuleArg, ...) \
class FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) { \
public: \
using ActionType = Action; \
using ClassType = Action::ClassType; \
using ReturnType = Return; \
using Signature = void(__VA_ARGS__); \
static constexpr const char name[] = #FunctionName; \
static constexpr const auto tag = \
Action::Proto::ValueCase::FUZZ_FUNCTION_TAG_NAME(FunctionName); \
static ReturnType ImplBody(ModuleArg, ##__VA_ARGS__); \
\
private: \
static bool registered_; \
}; \
auto FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::registered_ = ([] { \
auto tag = FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::tag; \
auto func = &::android::fuzz::ActionPerformer<FUZZ_FUNCTION_CLASS_NAME( \
Action, FunctionName)>::Invoke; \
Action::GetFunctionMap()->CheckEmplace(tag, func); \
return true; \
})(); \
Return FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::ImplBody(ModuleArg, ##__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, \
decltype(std::declval<Action::ClassType>().FunctionName()), \
Action::ClassType* module) { \
return module->FunctionName(); \
}

View file

@ -1,352 +0,0 @@
// 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.
#include <stddef.h>
#include <stdint.h>
#include <sysexits.h>
#include <functional>
#include <sstream>
#include <tuple>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/result.h>
#include <gtest/gtest.h>
#include <src/libfuzzer/libfuzzer_macro.h>
#include <storage_literals/storage_literals.h>
#include "fuzz_utils.h"
#include "snapshot_fuzz_utils.h"
using android::base::Error;
using android::base::GetBoolProperty;
using android::base::LogId;
using android::base::LogSeverity;
using android::base::ReadFileToString;
using android::base::Result;
using android::base::SetLogger;
using android::base::StderrLogger;
using android::base::StdioLogger;
using android::fs_mgr::CreateLogicalPartitionParams;
using android::fuzz::CheckedCast;
using android::snapshot::SnapshotFuzzData;
using android::snapshot::SnapshotFuzzEnv;
using chromeos_update_engine::DeltaArchiveManifest;
using google::protobuf::FieldDescriptor;
using google::protobuf::Message;
using google::protobuf::RepeatedPtrField;
// Avoid linking to libgsi since it needs disk I/O.
namespace android::gsi {
bool IsGsiRunning() {
LOG(FATAL) << "Called IsGsiRunning";
__builtin_unreachable();
}
std::string GetDsuSlot(const std::string& install_dir) {
LOG(FATAL) << "Called GetDsuSlot(" << install_dir << ")";
__builtin_unreachable();
}
} // namespace android::gsi
namespace android::snapshot {
const SnapshotFuzzData* current_data = nullptr;
const SnapshotTestModule* current_module = nullptr;
SnapshotFuzzEnv* GetSnapshotFuzzEnv();
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, ReturnType, ...) \
FUZZ_FUNCTION(SnapshotManagerAction, FunctionName, ReturnType, ISnapshotManager* snapshot, \
##__VA_ARGS__)
SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool, bool wipe) {
return snapshot->FinishedSnapshotWrites(wipe);
}
SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, bool, const ProcessUpdateStateArgs& args) {
std::function<bool()> before_cancel;
if (args.has_before_cancel()) {
before_cancel = [&]() { return args.fail_before_cancel(); };
}
return snapshot->ProcessUpdateState({}, before_cancel);
}
SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, UpdateState, bool has_progress_arg) {
double progress;
return snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr);
}
SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool, bool has_callback) {
std::function<void()> callback;
if (has_callback) {
callback = []() {};
}
return snapshot->HandleImminentDataWipe(callback);
}
SNAPSHOT_FUZZ_FUNCTION(Dump, bool) {
std::stringstream ss;
return snapshot->Dump(ss);
}
SNAPSHOT_FUZZ_FUNCTION(CreateUpdateSnapshots, bool, const DeltaArchiveManifest& manifest) {
return snapshot->CreateUpdateSnapshots(manifest);
}
SNAPSHOT_FUZZ_FUNCTION(UnmapUpdateSnapshot, bool, const std::string& name) {
return snapshot->UnmapUpdateSnapshot(name);
}
SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions, bool,
const CreateLogicalAndSnapshotPartitionsArgs& args) {
const std::string* super;
if (args.use_correct_super()) {
super = &GetSnapshotFuzzEnv()->super();
} else {
super = &args.super();
}
return snapshot->CreateLogicalAndSnapshotPartitions(
*super, std::chrono::milliseconds(args.timeout_millis()));
}
SNAPSHOT_FUZZ_FUNCTION(RecoveryCreateSnapshotDevicesWithMetadata, CreateResult,
const RecoveryCreateSnapshotDevicesArgs& args) {
std::unique_ptr<AutoDevice> device;
if (args.has_metadata_device_object()) {
device = std::make_unique<NoOpAutoDevice>(args.metadata_mounted());
}
return snapshot->RecoveryCreateSnapshotDevices(device);
}
SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, bool,
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;
return snapshot->MapUpdateSnapshot(params, &path);
}
SNAPSHOT_FUZZ_FUNCTION(SwitchSlot, void) {
(void)snapshot;
CHECK(current_module != nullptr);
CHECK(current_module->device_info != nullptr);
current_module->device_info->SwitchSlot();
}
// During global init, log all messages to stdio. This is only done once.
int AllowLoggingDuringGlobalInit() {
SetLogger(&StdioLogger);
return 0;
}
// Only log fatal messages during tests.
void FatalOnlyLogger(LogId logid, LogSeverity severity, const char* tag, const char* file,
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() {
(void)GetSnapshotFuzzEnv();
[[maybe_unused]] static protobuf_mutator::protobuf::LogSilencer log_silencer;
SetLogger(&FatalOnlyLogger);
return 0;
}
SnapshotFuzzEnv* GetSnapshotFuzzEnv() {
[[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit();
static SnapshotFuzzEnv env;
return &env;
}
SnapshotTestModule SetUpTest(const SnapshotFuzzData& snapshot_fuzz_data) {
current_data = &snapshot_fuzz_data;
auto env = GetSnapshotFuzzEnv();
env->CheckSoftReset();
auto test_module = env->CheckCreateSnapshotManager(snapshot_fuzz_data);
current_module = &test_module;
CHECK(test_module.snapshot);
return test_module;
}
void TearDownTest() {
current_module = nullptr;
current_data = nullptr;
}
} // namespace android::snapshot
DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) {
using namespace android::snapshot;
[[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit();
auto test_module = SetUpTest(snapshot_fuzz_data);
SnapshotManagerAction::ExecuteAll(test_module.snapshot.get(), snapshot_fuzz_data.actions());
TearDownTest();
}
namespace android::snapshot {
// Work-around to cast a 'void' value to Result<void>.
template <typename T>
struct GoodResult {
template <typename F>
static Result<T> Cast(F&& f) {
return f();
}
};
template <>
struct GoodResult<void> {
template <typename F>
static Result<void> Cast(F&& f) {
f();
return {};
}
};
class LibsnapshotFuzzerTest : public ::testing::Test {
protected:
static void SetUpTestCase() {
// Do initialization once.
(void)GetSnapshotFuzzEnv();
}
void SetUp() override {
bool is_virtual_ab = GetBoolProperty("ro.virtual_ab.enabled", false);
if (!is_virtual_ab) GTEST_SKIP() << "Test only runs on Virtual A/B devices.";
}
void SetUpFuzzData(const std::string& fn) {
auto path = android::base::GetExecutableDirectory() + "/corpus/"s + fn;
std::string proto_text;
ASSERT_TRUE(ReadFileToString(path, &proto_text));
snapshot_fuzz_data_ = std::make_unique<SnapshotFuzzData>();
ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(proto_text,
snapshot_fuzz_data_.get()));
test_module_ = android::snapshot::SetUpTest(*snapshot_fuzz_data_);
}
void TearDown() override { android::snapshot::TearDownTest(); }
template <typename FuzzFunction>
Result<typename FuzzFunction::ReturnType> Execute(int action_index) {
if (action_index >= snapshot_fuzz_data_->actions_size()) {
return Error() << "Index " << action_index << " is out of bounds ("
<< snapshot_fuzz_data_->actions_size() << " actions in corpus";
}
const auto& action_proto = snapshot_fuzz_data_->actions(action_index);
const auto* field_desc =
android::fuzz::GetValueFieldDescriptor<typename FuzzFunction::ActionType>(
action_proto);
if (field_desc == nullptr) {
return Error() << "Action at index " << action_index << " has no value defined.";
}
if (FuzzFunction::tag != field_desc->number()) {
return Error() << "Action at index " << action_index << " is expected to be "
<< FuzzFunction::name << ", but it is " << field_desc->name()
<< " in corpus.";
}
return GoodResult<typename FuzzFunction::ReturnType>::Cast([&]() {
return android::fuzz::ActionPerformer<FuzzFunction>::Invoke(test_module_.snapshot.get(),
action_proto, field_desc);
});
}
std::unique_ptr<SnapshotFuzzData> snapshot_fuzz_data_;
SnapshotTestModule test_module_;
};
#define SNAPSHOT_FUZZ_FN_NAME(name) FUZZ_FUNCTION_CLASS_NAME(SnapshotManagerAction, name)
MATCHER_P(ResultIs, expected, "") {
if (!arg.ok()) {
*result_listener << arg.error();
return false;
}
*result_listener << "expected: " << expected;
return arg.value() == expected;
}
#define ASSERT_RESULT_TRUE(actual) ASSERT_THAT(actual, ResultIs(true))
// Check that launch_device.txt is executed correctly.
TEST_F(LibsnapshotFuzzerTest, LaunchDevice) {
SetUpFuzzData("launch_device.txt");
int i = 0;
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(BeginUpdate)>(i++));
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(CreateUpdateSnapshots)>(i++));
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "sys_b";
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "vnd_b";
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "prd_b";
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(FinishedSnapshotWrites)>(i++));
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "sys_b";
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "vnd_b";
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "prd_b";
ASSERT_RESULT_OK(Execute<SNAPSHOT_FUZZ_FN_NAME(SwitchSlot)>(i++));
ASSERT_EQ("_b", test_module_.device_info->GetSlotSuffix());
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(NeedSnapshotsInFirstStageMount)>(i++));
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(CreateLogicalAndSnapshotPartitions)>(i++));
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(InitiateMerge)>(i++));
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(ProcessUpdateState)>(i++));
ASSERT_EQ(i, snapshot_fuzz_data_->actions_size()) << "Not all actions are executed.";
}
} // namespace android::snapshot

View file

@ -1,513 +0,0 @@
// 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.
#include <ftw.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <chrono>
#include <string>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <fs_mgr.h>
#include <libsnapshot/auto_device.h>
#include <libsnapshot/snapshot.h>
#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::Basename;
using android::base::ReadFileToString;
using android::base::SetProperty;
using android::base::Split;
using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::base::WriteStringToFile;
using android::dm::DeviceMapper;
using android::dm::DmTarget;
using android::dm::LoopControl;
using android::fiemap::IImageManager;
using android::fiemap::ImageManager;
using android::fs_mgr::BlockDeviceInfo;
using android::fs_mgr::FstabEntry;
using android::fs_mgr::IPartitionOpener;
using chromeos_update_engine::DynamicPartitionMetadata;
static const char MNT_DIR[] = "/mnt";
static const char BLOCK_SYSFS[] = "/sys/block";
static const char FAKE_ROOT_NAME[] = "snapshot_fuzz";
static const auto SUPER_IMAGE_SIZE = 16_MiB;
static const auto DATA_IMAGE_SIZE = 16_MiB;
static const auto FAKE_ROOT_SIZE = 64_MiB;
namespace android::snapshot {
bool Mkdir(const std::string& path) {
if (mkdir(path.c_str(), 0750) == -1 && errno != EEXIST) {
PLOG(ERROR) << "Cannot create " << path;
return false;
}
return true;
}
bool RmdirRecursive(const std::string& path) {
auto callback = [](const char* child, const struct stat*, int file_type, struct FTW*) -> int {
switch (file_type) {
case FTW_D:
case FTW_DP:
case FTW_DNR:
if (rmdir(child) == -1) {
PLOG(ERROR) << "rmdir " << child;
return -1;
}
return 0;
case FTW_NS:
default:
if (rmdir(child) != -1) break;
[[fallthrough]];
case FTW_F:
case FTW_SL:
case FTW_SLN:
if (unlink(child) == -1) {
PLOG(ERROR) << "unlink " << child;
return -1;
}
return 0;
}
return 0;
};
return nftw(path.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) == 0;
}
std::string GetLinearBaseDeviceString(const DeviceMapper::TargetInfo& target) {
if (target.spec.target_type != "linear"s) return {};
auto tokens = Split(target.data, " ");
CHECK_EQ(2, tokens.size());
return tokens[0];
}
std::vector<std::string> GetSnapshotBaseDeviceStrings(const DeviceMapper::TargetInfo& target) {
if (target.spec.target_type != "snapshot"s && target.spec.target_type != "snapshot-merge"s)
return {};
auto tokens = Split(target.data, " ");
CHECK_EQ(4, tokens.size());
return {tokens[0], tokens[1]};
}
bool ShouldDeleteLoopDevice(const std::string& node) {
std::string backing_file;
if (ReadFileToString(StringPrintf("%s/loop/backing_file", node.data()), &backing_file)) {
if (StartsWith(backing_file, std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME)) {
return true;
}
}
return false;
}
std::vector<DeviceMapper::TargetInfo> GetTableInfoIfExists(const std::string& dev_name) {
auto& dm = DeviceMapper::Instance();
std::vector<DeviceMapper::TargetInfo> table;
if (!dm.GetTableInfo(dev_name, &table)) {
PCHECK(errno == ENODEV || errno == ENXIO);
return {};
}
return table;
}
std::set<std::string> GetAllBaseDeviceStrings(const std::string& child_dev) {
std::set<std::string> ret;
for (const auto& child_target : GetTableInfoIfExists(child_dev)) {
auto snapshot_bases = GetSnapshotBaseDeviceStrings(child_target);
ret.insert(snapshot_bases.begin(), snapshot_bases.end());
auto linear_base = GetLinearBaseDeviceString(child_target);
if (!linear_base.empty()) {
ret.insert(linear_base);
}
}
return ret;
}
using PropertyList = std::set<std::string>;
void InsertProperty(const char* key, const char* /*name*/, void* cookie) {
reinterpret_cast<PropertyList*>(cookie)->insert(key);
}
// Attempt to delete all devices that is based on dev_name, including itself.
void CheckDeleteDeviceMapperTree(const std::string& dev_name, bool known_allow_delete = false,
uint64_t depth = 100) {
CHECK(depth > 0) << "Reaching max depth when deleting " << dev_name
<< ". There may be devices referencing itself. Check `dmctl list devices -v`.";
auto& dm = DeviceMapper::Instance();
auto table = GetTableInfoIfExists(dev_name);
if (table.empty()) {
PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
return;
}
if (!known_allow_delete) {
for (const auto& target : table) {
auto base_device_string = GetLinearBaseDeviceString(target);
if (base_device_string.empty()) continue;
if (ShouldDeleteLoopDevice(
StringPrintf("/sys/dev/block/%s", base_device_string.data()))) {
known_allow_delete = true;
break;
}
}
}
if (!known_allow_delete) {
return;
}
std::string dev_string;
PCHECK(dm.GetDeviceString(dev_name, &dev_string));
std::vector<DeviceMapper::DmBlockDevice> devices;
PCHECK(dm.GetAvailableDevices(&devices));
for (const auto& child_dev : devices) {
auto child_bases = GetAllBaseDeviceStrings(child_dev.name());
if (child_bases.find(dev_string) != child_bases.end()) {
CheckDeleteDeviceMapperTree(child_dev.name(), true /* known_allow_delete */, depth - 1);
}
}
PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
}
// Attempt to clean up residues from previous runs.
void CheckCleanupDeviceMapperDevices() {
auto& dm = DeviceMapper::Instance();
std::vector<DeviceMapper::DmBlockDevice> devices;
PCHECK(dm.GetAvailableDevices(&devices));
for (const auto& dev : devices) {
CheckDeleteDeviceMapperTree(dev.name());
}
}
void CheckUmount(const std::string& path) {
PCHECK(TEMP_FAILURE_RETRY(umount(path.data()) == 0) || errno == ENOENT || errno == EINVAL)
<< path;
}
void CheckDetachLoopDevices(const std::set<std::string>& exclude_names = {}) {
// ~SnapshotFuzzEnv automatically does the following.
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(BLOCK_SYSFS), closedir);
PCHECK(dir != nullptr) << BLOCK_SYSFS;
LoopControl loop_control;
dirent* dp;
while ((dp = readdir(dir.get())) != nullptr) {
if (exclude_names.find(dp->d_name) != exclude_names.end()) {
continue;
}
if (!ShouldDeleteLoopDevice(StringPrintf("%s/%s", BLOCK_SYSFS, dp->d_name).data())) {
continue;
}
PCHECK(loop_control.Detach(StringPrintf("/dev/block/%s", dp->d_name).data()));
}
}
void CheckUmountAll() {
CheckUmount(std::string(MNT_DIR) + "/snapshot_fuzz_data");
CheckUmount(std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME);
}
class AutoDeleteDir : public AutoDevice {
public:
static std::unique_ptr<AutoDeleteDir> New(const std::string& path) {
if (!Mkdir(path)) {
return std::unique_ptr<AutoDeleteDir>(new AutoDeleteDir(""));
}
return std::unique_ptr<AutoDeleteDir>(new AutoDeleteDir(path));
}
~AutoDeleteDir() {
if (!HasDevice()) return;
PCHECK(rmdir(name_.c_str()) == 0 || errno == ENOENT) << name_;
}
private:
AutoDeleteDir(const std::string& path) : AutoDevice(path) {}
};
class AutoUnmount : public AutoDevice {
public:
~AutoUnmount() {
if (!HasDevice()) return;
CheckUmount(name_);
}
AutoUnmount(const std::string& path) : AutoDevice(path) {}
};
class AutoUnmountTmpfs : public AutoUnmount {
public:
static std::unique_ptr<AutoUnmount> New(const std::string& path, uint64_t size) {
if (mount("tmpfs", path.c_str(), "tmpfs", 0,
(void*)StringPrintf("size=%" PRIu64, size).data()) == -1) {
PLOG(ERROR) << "Cannot mount " << path;
return std::unique_ptr<AutoUnmount>(new AutoUnmount(""));
}
return std::unique_ptr<AutoUnmount>(new AutoUnmount(path));
}
private:
using AutoUnmount::AutoUnmount;
};
// A directory on tmpfs. Upon destruct, it is unmounted and deleted.
class AutoMemBasedDir : public AutoDevice {
public:
static std::unique_ptr<AutoMemBasedDir> New(const std::string& name, uint64_t size) {
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()) {
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
}
ret->auto_umount_mount_point_ = AutoUnmountTmpfs::New(ret->mount_path(), size);
if (!ret->auto_umount_mount_point_->HasDevice()) {
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
}
// 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 temporary scratch directory.
std::string tmp_path() const {
CHECK(HasDevice());
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()));
}
private:
AutoMemBasedDir(const std::string& name) : AutoDevice(name) {}
std::string mount_path() const {
CHECK(HasDevice());
return MNT_DIR + "/"s + name_;
}
std::unique_ptr<AutoDeleteDir> auto_delete_mount_dir_;
std::unique_ptr<AutoUnmount> auto_umount_mount_point_;
};
SnapshotFuzzEnv::SnapshotFuzzEnv() {
CheckCleanupDeviceMapperDevices();
CheckDetachLoopDevices();
CheckUmountAll();
fake_root_ = AutoMemBasedDir::New(FAKE_ROOT_NAME, FAKE_ROOT_SIZE);
CHECK(fake_root_ != nullptr);
CHECK(fake_root_->HasDevice());
loop_control_ = std::make_unique<LoopControl>();
fake_data_mount_point_ = MNT_DIR + "/snapshot_fuzz_data"s;
auto_delete_data_mount_point_ = AutoDeleteDir::New(fake_data_mount_point_);
CHECK(auto_delete_data_mount_point_ != nullptr);
CHECK(auto_delete_data_mount_point_->HasDevice());
const auto& fake_persist_path = fake_root_->persist_path();
mapped_super_ = CheckMapImage(fake_persist_path + "/super.img", SUPER_IMAGE_SIZE,
loop_control_.get(), &fake_super_);
mapped_data_ = CheckMapImage(fake_persist_path + "/data.img", DATA_IMAGE_SIZE,
loop_control_.get(), &fake_data_block_device_);
mounted_data_ = CheckMountFormatData(fake_data_block_device_, fake_data_mount_point_);
}
SnapshotFuzzEnv::~SnapshotFuzzEnv() {
CheckCleanupDeviceMapperDevices();
mounted_data_ = nullptr;
auto_delete_data_mount_point_ = nullptr;
mapped_data_ = nullptr;
mapped_super_ = nullptr;
CheckDetachLoopDevices();
loop_control_ = nullptr;
fake_root_ = nullptr;
CheckUmountAll();
}
void CheckZeroFill(const std::string& file, size_t size) {
std::string zeros(size, '\0');
PCHECK(WriteStringToFile(zeros, file)) << "Cannot write zeros to " << file;
}
void SnapshotFuzzEnv::CheckSoftReset() {
fake_root_->CheckSoftReset();
CheckZeroFill(super(), SUPER_IMAGE_SIZE);
CheckCleanupDeviceMapperDevices();
CheckDetachLoopDevices({Basename(fake_super_), Basename(fake_data_block_device_)});
}
std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager() {
auto metadata_dir = fake_root_->tmp_path() + "/images_manager_metadata";
auto data_dir = fake_data_mount_point_ + "/image_manager_data";
PCHECK(Mkdir(metadata_dir));
PCHECK(Mkdir(data_dir));
return SnapshotFuzzImageManager::Open(metadata_dir, data_dir);
}
// 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;
}
class AutoDetachLoopDevice : public AutoDevice {
public:
AutoDetachLoopDevice(LoopControl* control, const std::string& device)
: AutoDevice(device), control_(control) {}
~AutoDetachLoopDevice() { PCHECK(control_->Detach(name_)) << name_; }
private:
LoopControl* control_;
};
std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMapImage(const std::string& img_path,
uint64_t size, LoopControl* control,
std::string* mapped_path) {
CheckZeroFill(img_path, size);
CheckCreateLoopDevice(control, img_path, 1s, mapped_path);
return std::make_unique<AutoDetachLoopDevice>(control, *mapped_path);
}
SnapshotTestModule SnapshotFuzzEnv::CheckCreateSnapshotManager(const SnapshotFuzzData& data) {
SnapshotTestModule ret;
auto partition_opener = std::make_unique<TestPartitionOpener>(super());
ret.opener = partition_opener.get();
CheckWriteSuperMetadata(data, *partition_opener);
auto metadata_dir = fake_root_->tmp_path() + "/snapshot_metadata";
PCHECK(Mkdir(metadata_dir));
if (data.has_metadata_snapshots_dir()) {
PCHECK(Mkdir(metadata_dir + "/snapshots"));
}
ret.device_info = new SnapshotFuzzDeviceInfo(this, data.device_info_data(),
std::move(partition_opener), metadata_dir);
auto snapshot = SnapshotManager::New(ret.device_info /* takes ownership */);
ret.snapshot = std::move(snapshot);
return ret;
}
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()));
}
std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMountFormatData(const std::string& blk_device,
const std::string& mount_point) {
FstabEntry entry{
.blk_device = blk_device,
.length = static_cast<off64_t>(DATA_IMAGE_SIZE),
.fs_type = "ext4",
.mount_point = mount_point,
};
CHECK(0 == fs_mgr_do_format(entry));
CHECK(0 == fs_mgr_do_mount_one(entry));
return std::make_unique<AutoUnmount>(mount_point);
}
SnapshotFuzzImageManager::~SnapshotFuzzImageManager() {
// Remove relevant gsid.mapped_images.* props.
for (const auto& name : mapped_) {
CHECK(UnmapImageIfExists(name)) << "Cannot unmap " << name;
}
}
bool SnapshotFuzzImageManager::MapImageDevice(const std::string& name,
const std::chrono::milliseconds& timeout_ms,
std::string* path) {
if (impl_->MapImageDevice(name, timeout_ms, path)) {
mapped_.insert(name);
return true;
}
return false;
}
} // namespace android::snapshot

View file

@ -1,214 +0,0 @@
// 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.
#include <memory>
#include <set>
#include <string>
#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"
#include "libsnapshot/snapshot.h"
namespace android::snapshot {
class AutoMemBasedDir;
class SnapshotFuzzDeviceInfo;
class NoOpAutoDevice : public AutoDevice {
public:
NoOpAutoDevice(bool mounted) : AutoDevice(mounted ? "no_op" : "") {}
};
struct SnapshotTestModule {
std::unique_ptr<ISnapshotManager> snapshot;
SnapshotFuzzDeviceInfo* device_info = nullptr;
TestPartitionOpener* opener = nullptr;
};
// Prepare test environment. This has a heavy overhead and should be done once.
class SnapshotFuzzEnv {
public:
// Check if test should run at all.
static bool ShouldSkipTest();
// Initialize the environment.
SnapshotFuzzEnv();
~SnapshotFuzzEnv();
// Soft reset part of the environment before running the next test.
// 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.
SnapshotTestModule CheckCreateSnapshotManager(const SnapshotFuzzData& data);
std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager();
// 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::string fake_data_mount_point_;
std::unique_ptr<AutoDevice> auto_delete_data_mount_point_;
std::unique_ptr<AutoDevice> mapped_super_;
std::string fake_super_;
std::unique_ptr<AutoDevice> mapped_data_;
std::string fake_data_block_device_;
std::unique_ptr<AutoDevice> mounted_data_;
static std::unique_ptr<AutoDevice> CheckMapImage(const std::string& fake_persist_path,
uint64_t size,
android::dm::LoopControl* control,
std::string* mapped_path);
static std::unique_ptr<AutoDevice> CheckMountFormatData(const std::string& blk_device,
const std::string& mount_point);
void CheckWriteSuperMetadata(const SnapshotFuzzData& proto,
const android::fs_mgr::IPartitionOpener& opener);
};
class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
public:
using MergeStatus = ISnapshotManager::IDeviceInfo::MergeStatus;
// Client is responsible for maintaining the lifetime of |data|.
SnapshotFuzzDeviceInfo(SnapshotFuzzEnv* env, const FuzzDeviceInfoData& data,
std::unique_ptr<TestPartitionOpener>&& partition_opener,
const std::string& metadata_dir)
: env_(env),
data_(&data),
partition_opener_(std::move(partition_opener)),
metadata_dir_(metadata_dir),
dm_(android::dm::DeviceMapper::Instance()) {}
// Following APIs are mocked.
std::string GetMetadataDir() const override { return metadata_dir_; }
std::string GetSuperDevice(uint32_t) const override {
// TestPartitionOpener can recognize this.
return "super";
}
const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const override {
return *partition_opener_;
}
// Following APIs are fuzzed.
std::string GetSlotSuffix() const override { return CurrentSlotIsA() ? "_a" : "_b"; }
std::string GetOtherSlotSuffix() const override { return CurrentSlotIsA() ? "_b" : "_a"; }
bool IsOverlayfsSetup() const override { return data_->is_overlayfs_setup(); }
bool SetBootControlMergeStatus(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(); }
bool IsFirstStageInit() const override { return false; }
android::dm::IDeviceMapper& GetDeviceMapper() override { return dm_; }
std::unique_ptr<IImageManager> OpenImageManager() const {
return env_->CheckCreateFakeImageManager();
}
void SwitchSlot() { switched_slot_ = !switched_slot_; }
private:
SnapshotFuzzEnv* env_;
const FuzzDeviceInfoData* data_;
std::unique_ptr<TestPartitionOpener> partition_opener_;
std::string metadata_dir_;
bool switched_slot_ = false;
android::dm::DeviceMapper& dm_;
bool CurrentSlotIsA() const { return data_->slot_suffix_is_a() != switched_slot_; }
};
// A spy class on ImageManager implementation. Upon destruction, unmaps all images
// map through this object.
class SnapshotFuzzImageManager : public android::fiemap::IImageManager {
public:
static std::unique_ptr<SnapshotFuzzImageManager> Open(const std::string& metadata_dir,
const std::string& data_dir) {
auto impl = android::fiemap::ImageManager::Open(metadata_dir, data_dir);
if (impl == nullptr) return nullptr;
return std::unique_ptr<SnapshotFuzzImageManager>(
new SnapshotFuzzImageManager(std::move(impl)));
}
~SnapshotFuzzImageManager();
// Spied APIs.
bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
std::string* path) override;
// Other functions call through.
android::fiemap::FiemapStatus CreateBackingImage(
const std::string& name, uint64_t size, int flags,
std::function<bool(uint64_t, uint64_t)>&& on_progress) override {
return impl_->CreateBackingImage(name, size, flags, std::move(on_progress));
}
bool DeleteBackingImage(const std::string& name) override {
return impl_->DeleteBackingImage(name);
}
bool UnmapImageDevice(const std::string& name) override {
return impl_->UnmapImageDevice(name);
}
bool BackingImageExists(const std::string& name) override {
return impl_->BackingImageExists(name);
}
bool IsImageMapped(const std::string& name) override { return impl_->IsImageMapped(name); }
bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
std::string* dev) override {
return impl_->MapImageWithDeviceMapper(opener, name, dev);
}
bool GetMappedImageDevice(const std::string& name, std::string* device) override {
return impl_->GetMappedImageDevice(name, device);
}
bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override {
return impl_->MapAllImages(init);
}
bool DisableImage(const std::string& name) override { return impl_->DisableImage(name); }
bool RemoveDisabledImages() override { return impl_->RemoveDisabledImages(); }
std::vector<std::string> GetAllBackingImages() override { return impl_->GetAllBackingImages(); }
android::fiemap::FiemapStatus ZeroFillNewImage(const std::string& name,
uint64_t bytes) override {
return impl_->ZeroFillNewImage(name, bytes);
}
bool RemoveAllImages() override { return impl_->RemoveAllImages(); }
bool UnmapImageIfExists(const std::string& name) override {
return impl_->UnmapImageIfExists(name);
}
bool IsImageDisabled(const std::string& name) override { return impl_->IsImageDisabled(name); }
private:
std::unique_ptr<android::fiemap::IImageManager> impl_;
std::set<std::string> mapped_;
SnapshotFuzzImageManager(std::unique_ptr<android::fiemap::IImageManager>&& impl)
: impl_(std::move(impl)) {}
};
} // namespace android::snapshot