353 lines
13 KiB
C++
353 lines
13 KiB
C++
|
// 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
|