// 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 #include #include #include #include #include #include #include #include #include #include #include #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 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 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 device; if (args.has_metadata_device_object()) { device = std::make_unique(args.metadata_mounted()); } return snapshot->RecoveryCreateSnapshotDevices(device); } SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, bool, const CreateLogicalPartitionParamsProto& params_proto) { auto partition_opener = std::make_unique(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. template struct GoodResult { template static Result Cast(F&& f) { return f(); } }; template <> struct GoodResult { template static Result 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(); 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 Result 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( 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::Cast([&]() { return android::fuzz::ActionPerformer::Invoke(test_module_.snapshot.get(), action_proto, field_desc); }); } std::unique_ptr 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(i++)); ASSERT_RESULT_TRUE(Execute(i++)); ASSERT_RESULT_TRUE(Execute(i++)) << "sys_b"; ASSERT_RESULT_TRUE(Execute(i++)) << "vnd_b"; ASSERT_RESULT_TRUE(Execute(i++)) << "prd_b"; ASSERT_RESULT_TRUE(Execute(i++)); ASSERT_RESULT_TRUE(Execute(i++)) << "sys_b"; ASSERT_RESULT_TRUE(Execute(i++)) << "vnd_b"; ASSERT_RESULT_TRUE(Execute(i++)) << "prd_b"; ASSERT_RESULT_OK(Execute(i++)); ASSERT_EQ("_b", test_module_.device_info->GetSlotSuffix()); ASSERT_RESULT_TRUE(Execute(i++)); ASSERT_RESULT_TRUE(Execute(i++)); ASSERT_RESULT_TRUE(Execute(i++)); ASSERT_RESULT_TRUE(Execute(i++)); ASSERT_EQ(i, snapshot_fuzz_data_->actions_size()) << "Not all actions are executed."; } } // namespace android::snapshot