From 7134ae064ecf47054410b47a5632e385b72f2dbe Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 4 May 2020 13:36:54 -0700 Subject: [PATCH 1/7] libsnapshot_fuzzer: Add ZERO to operation types Even though operation type ZERO is not used in libsnapshot, add it so that the fuzzer has an alternative operation type to use. Without it, the fuzzer will only generate operations SOURCE_COPY. Test: pass Bug: 154633114 Change-Id: I4fd36421b8b33ed68b94be2d739a6dd706b3829f --- fs_mgr/libsnapshot/update_engine/update_metadata.proto | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto index be5e1fe69..8a11eaa95 100644 --- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto +++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto @@ -45,7 +45,12 @@ message PartitionInfo { } message InstallOperation { - enum Type { SOURCE_COPY = 4; } + enum Type { + SOURCE_COPY = 4; + // Not used by libsnapshot. Declared here so that the fuzzer has an + // alternative value to use for |type|. + ZERO = 6; + } required Type type = 1; repeated Extent src_extents = 4; repeated Extent dst_extents = 6; From 933d341b6a5beb57a6278a678123f1ce33b4a341 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 4 May 2020 13:59:52 -0700 Subject: [PATCH 2/7] libsnapshot_fuzzer: also create snapshots dir /metadata/ota/snapshots is created by init.rc at boot time. Conditionally create it in the fuzz test Test: run it Bug: 154633114 Change-Id: Ice5e340b554ffd59861c4b21d9c4eb652ca60c55 --- fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto | 5 ++++- fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto index 91fbb60eb..bbe92d426 100644 --- a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto +++ b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto @@ -97,7 +97,10 @@ message SnapshotFuzzData { 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 5 to 9999; + reserved 6 to 9999; repeated SnapshotManagerActionProto actions = 10000; } diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp index 8101d03b7..d8954bbc0 100644 --- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp +++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp @@ -258,6 +258,9 @@ std::unique_ptr SnapshotFuzzEnv::CheckCreateSnapshotManager( 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")); + } auto device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(), std::move(partition_opener), metadata_dir); From 322ea7ae3c3b3428f42b01ceb19138d9afb6cb0c Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 4 May 2020 15:10:07 -0700 Subject: [PATCH 3/7] libsnapshot_fuzzer: mount data image Mount a real data image and use it as the backing storage of image manager. This is needed for ImageManager to DetermineMaximumFileSize. Test: run with corpus Bug: 154633114 Change-Id: I4802094d78459427f5d83102bcb716de590789e0 --- fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp | 81 ++++++++++++++-------- fs_mgr/libsnapshot/snapshot_fuzz_utils.h | 14 +++- 2 files changed, 63 insertions(+), 32 deletions(-) diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp index d8954bbc0..5e9889f10 100644 --- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp +++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -48,14 +49,15 @@ 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; -// This directory is exempted from pinning in ImageManager. -static const char MNT_DIR[] = "/data/gsi/ota/test/"; +static const char MNT_DIR[] = "/mnt"; static const char FAKE_ROOT_NAME[] = "snapshot_fuzz"; static const auto SUPER_IMAGE_SIZE = 16_MiB; +static const auto DATA_IMAGE_SIZE = 16_MiB; static const auto FAKE_ROOT_SIZE = 64_MiB; namespace android::snapshot { @@ -118,6 +120,17 @@ class AutoDeleteDir : public AutoDevice { }; class AutoUnmount : public AutoDevice { + public: + ~AutoUnmount() { + if (!HasDevice()) return; + if (umount(name_.c_str()) == -1) { + PLOG(ERROR) << "Cannot umount " << name_; + } + } + AutoUnmount(const std::string& path) : AutoDevice(path) {} +}; + +class AutoUnmountTmpfs : public AutoUnmount { public: static std::unique_ptr New(const std::string& path, uint64_t size) { if (mount("tmpfs", path.c_str(), "tmpfs", 0, @@ -127,30 +140,20 @@ class AutoUnmount : public AutoDevice { } return std::unique_ptr(new AutoUnmount(path)); } - ~AutoUnmount() { - if (!HasDevice()) return; - if (umount(name_.c_str()) == -1) { - PLOG(ERROR) << "Cannot umount " << name_; - } - } - private: - AutoUnmount(const std::string& path) : AutoDevice(path) {} + using AutoUnmount::AutoUnmount; }; // A directory on tmpfs. Upon destruct, it is unmounted and deleted. class AutoMemBasedDir : public AutoDevice { public: static std::unique_ptr New(const std::string& name, uint64_t size) { - if (!Mkdir(MNT_DIR)) { - return std::unique_ptr(new AutoMemBasedDir("")); - } auto ret = std::unique_ptr(new AutoMemBasedDir(name)); ret->auto_delete_mount_dir_ = AutoDeleteDir::New(ret->mount_path()); if (!ret->auto_delete_mount_dir_->HasDevice()) { return std::unique_ptr(new AutoMemBasedDir("")); } - ret->auto_umount_mount_point_ = AutoUnmount::New(ret->mount_path(), size); + ret->auto_umount_mount_point_ = AutoUnmountTmpfs::New(ret->mount_path(), size); if (!ret->auto_umount_mount_point_->HasDevice()) { return std::unique_ptr(new AutoMemBasedDir("")); } @@ -195,7 +198,18 @@ SnapshotFuzzEnv::SnapshotFuzzEnv() { CHECK(fake_root_ != nullptr); CHECK(fake_root_->HasDevice()); loop_control_ = std::make_unique(); - mapped_super_ = CheckMapSuper(fake_root_->persist_path(), loop_control_.get(), &fake_super_); + + 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() = default; @@ -211,12 +225,7 @@ void SnapshotFuzzEnv::CheckSoftReset() { } std::unique_ptr SnapshotFuzzEnv::CheckCreateFakeImageManager( - const std::string& path) { - auto images_dir = path + "/images"; - auto metadata_dir = images_dir + "/metadata"; - auto data_dir = images_dir + "/data"; - - PCHECK(Mkdir(images_dir)); + const std::string& metadata_dir, const std::string& data_dir) { PCHECK(Mkdir(metadata_dir)); PCHECK(Mkdir(data_dir)); return ImageManager::Open(metadata_dir, data_dir); @@ -242,14 +251,13 @@ class AutoDetachLoopDevice : public AutoDevice { LoopControl* control_; }; -std::unique_ptr SnapshotFuzzEnv::CheckMapSuper(const std::string& fake_persist_path, - LoopControl* control, - std::string* fake_super) { - auto super_img = fake_persist_path + "/super.img"; - CheckZeroFill(super_img, SUPER_IMAGE_SIZE); - CheckCreateLoopDevice(control, super_img, 1s, fake_super); +std::unique_ptr 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(control, *fake_super); + return std::make_unique(control, *mapped_path); } std::unique_ptr SnapshotFuzzEnv::CheckCreateSnapshotManager( @@ -265,7 +273,9 @@ std::unique_ptr SnapshotFuzzEnv::CheckCreateSnapshotManager( 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->images_ = + CheckCreateFakeImageManager(fake_root_->tmp_path() + "/images_manager_metadata", + fake_data_mount_point_ + "/image_manager_data"); snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager(); return snapshot; @@ -314,4 +324,17 @@ void SnapshotFuzzEnv::CheckWriteSuperMetadata(const SnapshotFuzzData& data, CHECK(FlashPartitionTable(opener, super(), *metadata.get())); } +std::unique_ptr SnapshotFuzzEnv::CheckMountFormatData(const std::string& blk_device, + const std::string& mount_point) { + FstabEntry entry{ + .blk_device = blk_device, + .length = static_cast(DATA_IMAGE_SIZE), + .fs_type = "ext4", + .mount_point = mount_point, + }; + CHECK(0 == fs_mgr_do_format(entry, false /* crypt_footer */)); + CHECK(0 == fs_mgr_do_mount_one(entry)); + return std::make_unique(mount_point); +} + } // namespace android::snapshot diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h index 5533defa7..577fc7a40 100644 --- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h +++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h @@ -62,14 +62,22 @@ class SnapshotFuzzEnv { private: std::unique_ptr fake_root_; std::unique_ptr loop_control_; + std::string fake_data_mount_point_; + std::unique_ptr auto_delete_data_mount_point_; std::unique_ptr mapped_super_; std::string fake_super_; + std::unique_ptr mapped_data_; + std::string fake_data_block_device_; + std::unique_ptr mounted_data_; static std::unique_ptr CheckCreateFakeImageManager( - const std::string& fake_tmp_path); - static std::unique_ptr CheckMapSuper(const std::string& fake_persist_path, + const std::string& metadata_dir, const std::string& data_dir); + static std::unique_ptr CheckMapImage(const std::string& fake_persist_path, + uint64_t size, android::dm::LoopControl* control, - std::string* fake_super); + std::string* mapped_path); + static std::unique_ptr CheckMountFormatData(const std::string& blk_device, + const std::string& mount_point); void CheckWriteSuperMetadata(const SnapshotFuzzData& proto, const android::fs_mgr::IPartitionOpener& opener); From 4cbaf858deba4d301acca0b7e9f0f6e05d8b5646 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 4 May 2020 15:42:11 -0700 Subject: [PATCH 4/7] libsnapshot_fuzzer: add new test directive to switch slot Add a test directive to mimic the behavior of switching slots after rebooting. This changes the behavior of SnapshotFuzzDeviceInfo and has nothing to do with the API surface of ISnapshotManager. It also relies on the fact that SnapshotManager does not record the current slot suffix and always reads from IDeviceInfo. Test: run with corpus Bug: 154633114 Change-Id: I6ebb68afb8519feaa05bd3e4817f0e06596fc920 --- .../android/snapshot/snapshot_fuzz.proto | 4 ++++ fs_mgr/libsnapshot/snapshot_fuzz.cpp | 18 ++++++++++++++--- fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp | 14 +++++++------ fs_mgr/libsnapshot/snapshot_fuzz_utils.h | 20 ++++++++++++++----- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto index bbe92d426..a55b42acb 100644 --- a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto +++ b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto @@ -64,6 +64,7 @@ message SnapshotManagerActionProto { bool has_metadata_device_object = 1; bool metadata_mounted = 2; } + reserved 18 to 9999; oneof value { NoArgs begin_update = 1; NoArgs cancel_update = 2; @@ -82,6 +83,9 @@ message SnapshotManagerActionProto { 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; } } diff --git a/fs_mgr/libsnapshot/snapshot_fuzz.cpp b/fs_mgr/libsnapshot/snapshot_fuzz.cpp index 421154d3c..1e90ace6f 100644 --- a/fs_mgr/libsnapshot/snapshot_fuzz.cpp +++ b/fs_mgr/libsnapshot/snapshot_fuzz.cpp @@ -54,6 +54,7 @@ std::string GetDsuSlot(const std::string& install_dir) { namespace android::snapshot { const SnapshotFuzzData* current_data = nullptr; +const SnapshotTestModule* current_module = nullptr; SnapshotFuzzEnv* GetSnapshotFuzzEnv(); @@ -155,6 +156,13 @@ SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, const CreateLogicalPartitionParamsProt (void)snapshot->MapUpdateSnapshot(params, &path); } +SNAPSHOT_FUZZ_FUNCTION(SwitchSlot) { + (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); @@ -208,8 +216,12 @@ DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) { auto env = GetSnapshotFuzzEnv(); env->CheckSoftReset(); - auto snapshot_manager = env->CheckCreateSnapshotManager(snapshot_fuzz_data); - CHECK(snapshot_manager); + auto test_module = env->CheckCreateSnapshotManager(snapshot_fuzz_data); + current_module = &test_module; + CHECK(test_module.snapshot); - SnapshotManagerAction::ExecuteAll(snapshot_manager.get(), snapshot_fuzz_data.actions()); + SnapshotManagerAction::ExecuteAll(test_module.snapshot.get(), snapshot_fuzz_data.actions()); + + current_module = nullptr; + current_data = nullptr; } diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp index 5e9889f10..f0c1f8f66 100644 --- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp +++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp @@ -260,9 +260,10 @@ std::unique_ptr SnapshotFuzzEnv::CheckMapImage(const std::string& im return std::make_unique(control, *mapped_path); } -std::unique_ptr SnapshotFuzzEnv::CheckCreateSnapshotManager( - const SnapshotFuzzData& data) { +SnapshotTestModule SnapshotFuzzEnv::CheckCreateSnapshotManager(const SnapshotFuzzData& data) { + SnapshotTestModule ret; auto partition_opener = std::make_unique(super()); + ret.opener = partition_opener.get(); CheckWriteSuperMetadata(data, *partition_opener); auto metadata_dir = fake_root_->tmp_path() + "/snapshot_metadata"; PCHECK(Mkdir(metadata_dir)); @@ -270,15 +271,16 @@ std::unique_ptr SnapshotFuzzEnv::CheckCreateSnapshotManager( PCHECK(Mkdir(metadata_dir + "/snapshots")); } - auto device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(), - std::move(partition_opener), metadata_dir); - auto snapshot = SnapshotManager::New(device_info /* takes ownership */); + ret.device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(), + std::move(partition_opener), metadata_dir); + auto snapshot = SnapshotManager::New(ret.device_info /* takes ownership */); snapshot->images_ = CheckCreateFakeImageManager(fake_root_->tmp_path() + "/images_manager_metadata", fake_data_mount_point_ + "/image_manager_data"); snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager(); + ret.snapshot = std::move(snapshot); - return snapshot; + return ret; } const std::string& SnapshotFuzzEnv::super() const { diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h index 577fc7a40..240508881 100644 --- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h +++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h @@ -31,12 +31,19 @@ namespace android::snapshot { class AutoMemBasedDir; +class SnapshotFuzzDeviceInfo; class DummyAutoDevice : public AutoDevice { public: DummyAutoDevice(bool mounted) : AutoDevice(mounted ? "dummy" : "") {} }; +struct SnapshotTestModule { + std::unique_ptr snapshot; + SnapshotFuzzDeviceInfo* device_info = nullptr; + TestPartitionOpener* opener = nullptr; +}; + // Prepare test environment. This has a heavy overhead and should be done once. class SnapshotFuzzEnv { public: @@ -54,7 +61,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 CheckCreateSnapshotManager(const SnapshotFuzzData& data); + SnapshotTestModule CheckCreateSnapshotManager(const SnapshotFuzzData& data); // Return path to super partition. const std::string& super() const; @@ -105,10 +112,8 @@ 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"; - } + 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(android::hardware::boot::V1_1::MergeStatus) override { return data_->allow_set_boot_control_merge_status(); @@ -118,10 +123,15 @@ class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo { } bool IsRecovery() const override { return data_->is_recovery(); } + void SwitchSlot() { switched_slot_ = !switched_slot_; } + private: const FuzzDeviceInfoData* data_; std::unique_ptr partition_opener_; std::string metadata_dir_; + bool switched_slot_ = false; + + bool CurrentSlotIsA() const { return data_->slot_suffix_is_a() != switched_slot_; } }; } // namespace android::snapshot From 50c52c223b0404f321aa68243791e37b2777418d Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 4 May 2020 18:59:14 -0700 Subject: [PATCH 5/7] libsnapshot_fuzzer: Attempt to cleanup env before and after Ensure a clean env as much as possible. Otherwise, previous crashes may affect subsequent runs. Test: crash and run it again Bug: 154633114 Change-Id: I0db690f420bef4a95d2e937f423c21ddf916e0d5 --- fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp | 184 ++++++++++++++++++++- 1 file changed, 176 insertions(+), 8 deletions(-) diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp index f0c1f8f66..c9f1ab02b 100644 --- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp +++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp @@ -24,7 +24,10 @@ #include #include +#include #include +#include +#include #include #include #include @@ -42,9 +45,16 @@ 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; @@ -54,6 +64,7 @@ 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; @@ -100,6 +111,149 @@ bool RmdirRecursive(const std::string& path) { 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 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 GetTableInfoIfExists(const std::string& dev_name) { + auto& dm = DeviceMapper::Instance(); + std::vector table; + if (!dm.GetTableInfo(dev_name, &table)) { + PCHECK(errno == ENODEV); + return {}; + } + return table; +} + +std::set GetAllBaseDeviceStrings(const std::string& child_dev) { + std::set 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; +void InsertProperty(const char* key, const char* /*name*/, void* cookie) { + reinterpret_cast(cookie)->insert(key); +} + +void CheckUnsetGsidProps() { + PropertyList list; + property_list(&InsertProperty, reinterpret_cast(&list)); + for (const auto& key : list) { + SetProperty(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 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 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& exclude_names = {}) { + // ~SnapshotFuzzEnv automatically does the following. + std::unique_ptr 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 New(const std::string& path) { @@ -110,9 +264,7 @@ class AutoDeleteDir : public AutoDevice { } ~AutoDeleteDir() { if (!HasDevice()) return; - if (rmdir(name_.c_str()) == -1) { - PLOG(ERROR) << "Cannot remove " << name_; - } + PCHECK(rmdir(name_.c_str()) == 0 || errno == ENOENT) << name_; } private: @@ -123,9 +275,7 @@ class AutoUnmount : public AutoDevice { public: ~AutoUnmount() { if (!HasDevice()) return; - if (umount(name_.c_str()) == -1) { - PLOG(ERROR) << "Cannot umount " << name_; - } + CheckUmount(name_); } AutoUnmount(const std::string& path) : AutoDevice(path) {} }; @@ -194,6 +344,11 @@ class AutoMemBasedDir : public AutoDevice { }; SnapshotFuzzEnv::SnapshotFuzzEnv() { + CheckUnsetGsidProps(); + CheckCleanupDeviceMapperDevices(); + CheckDetachLoopDevices(); + CheckUmountAll(); + fake_root_ = AutoMemBasedDir::New(FAKE_ROOT_NAME, FAKE_ROOT_SIZE); CHECK(fake_root_ != nullptr); CHECK(fake_root_->HasDevice()); @@ -212,7 +367,18 @@ SnapshotFuzzEnv::SnapshotFuzzEnv() { mounted_data_ = CheckMountFormatData(fake_data_block_device_, fake_data_mount_point_); } -SnapshotFuzzEnv::~SnapshotFuzzEnv() = default; +SnapshotFuzzEnv::~SnapshotFuzzEnv() { + CheckUnsetGsidProps(); + 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'); @@ -222,6 +388,8 @@ void CheckZeroFill(const std::string& file, size_t size) { void SnapshotFuzzEnv::CheckSoftReset() { fake_root_->CheckSoftReset(); CheckZeroFill(super(), SUPER_IMAGE_SIZE); + CheckCleanupDeviceMapperDevices(); + CheckDetachLoopDevices({Basename(fake_super_), Basename(fake_data_block_device_)}); } std::unique_ptr SnapshotFuzzEnv::CheckCreateFakeImageManager( @@ -245,7 +413,7 @@ class AutoDetachLoopDevice : public AutoDevice { public: AutoDetachLoopDevice(LoopControl* control, const std::string& device) : AutoDevice(device), control_(control) {} - ~AutoDetachLoopDevice() { control_->Detach(name_); } + ~AutoDetachLoopDevice() { PCHECK(control_->Detach(name_)) << name_; } private: LoopControl* control_; From b2e939599a8bca7aa407b6d027576087465c9677 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 4 May 2020 13:38:27 -0700 Subject: [PATCH 6/7] libsnapshot_fuzzer: add initial corpus Transform some of the tests in vts_libsnapshot_test to corpus and use them as initial corpus to libsnapshot_fuzzer. The corpus alone gives us 50% of line coverage in snapshot.cpp. Test: run it Bug: 154633114 Change-Id: I8f3bf1d76ef64d710224e24c913990692481b65e --- fs_mgr/libsnapshot/Android.bp | 2 +- fs_mgr/libsnapshot/corpus/launch_device.txt | 161 ++++++++++++++++++++ fs_mgr/libsnapshot/fuzz.sh | 14 +- 3 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 fs_mgr/libsnapshot/corpus/launch_device.txt diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index e916693ea..c1911022f 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -289,7 +289,7 @@ cc_fuzz { canonical_path_from_root: false, local_include_dirs: ["."], }, - + corpus: ["corpus/*"], fuzz_config: { cc: ["android-virtual-ab+bugs@google.com"], componentid: 30545, diff --git a/fs_mgr/libsnapshot/corpus/launch_device.txt b/fs_mgr/libsnapshot/corpus/launch_device.txt new file mode 100644 index 000000000..55a7f2ccd --- /dev/null +++ b/fs_mgr/libsnapshot/corpus/launch_device.txt @@ -0,0 +1,161 @@ +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 { + } +} diff --git a/fs_mgr/libsnapshot/fuzz.sh b/fs_mgr/libsnapshot/fuzz.sh index 29101298e..0e5767422 100755 --- a/fs_mgr/libsnapshot/fuzz.sh +++ b/fs_mgr/libsnapshot/fuzz.sh @@ -3,7 +3,8 @@ 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_CORPSE_DIR=/data/local/tmp/${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 @@ -26,13 +27,14 @@ build_cov() { prepare_device() { adb root && adb remount && - adb shell mkdir -p ${DEVICE_CORPSE_DIR} && + 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}/${FUZZ_BINARY} ${FUZZ_BINARY} && + adb push ${ANDROID_PRODUCT_OUT}/${DEVICE_INIT_CORPUS_DIR} $(dirname ${FUZZ_BINARY}) } prepare_host() { @@ -52,7 +54,7 @@ generate_corpus() { prepare_device && build_normal && push_binary && - adb shell ${FUZZ_BINARY} "$@" ${DEVICE_CORPSE_DIR} + adb shell ${FUZZ_BINARY} "$@" ${DEVICE_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_DIR} } run_snapshot_fuzz() { @@ -62,7 +64,7 @@ run_snapshot_fuzz() { adb shell GCOV_PREFIX=${DEVICE_GCOV_DIR} GCOV_PREFIX_STRIP=3 \ ${FUZZ_BINARY} \ -runs=0 \ - ${DEVICE_CORPSE_DIR} + ${DEVICE_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_DIR} } show_fuzz_result() { @@ -82,7 +84,7 @@ exec llvm-cov gcov "$@" # run_snapshot_fuzz -runs=10000 run_snapshot_fuzz_all() { - generate_corpse "$@" && + generate_corpus "$@" && run_snapshot_fuzz && show_fuzz_result } From 51bfe08d84a547160cfcc743c22075f5aca9d71f Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Tue, 5 May 2020 14:34:38 -0700 Subject: [PATCH 7/7] libsnapshot_fuzzer: Add tests Add tests for the fuzzer itself. The test ensures that the initial corpus is a good run on SnapshotManager (in the sense that not only it doesn't crash, but also it executes as expected). Also changed fuzz_utils so that impl functions return the invocation result too, so that the test can check its values. Test: run it Bug: 154633114 Change-Id: I31ab7830f8c20a0575aaadabd038632d10f33962 --- fs_mgr/TEST_MAPPING | 3 + fs_mgr/libsnapshot/Android.bp | 20 ++- fs_mgr/libsnapshot/fuzz_utils.h | 120 ++++++++++------- fs_mgr/libsnapshot/snapshot_fuzz.cpp | 189 ++++++++++++++++++++++----- 4 files changed, 248 insertions(+), 84 deletions(-) diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING index 676f446e7..6cd043013 100644 --- a/fs_mgr/TEST_MAPPING +++ b/fs_mgr/TEST_MAPPING @@ -14,6 +14,9 @@ }, { "name": "vts_libsnapshot_test" + }, + { + "name": "libsnapshot_fuzzer_test" } ] } diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index c1911022f..2783e4de4 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -246,8 +246,8 @@ cc_test { gtest: false, } -cc_fuzz { - name: "libsnapshot_fuzzer", +cc_defaults { + name: "libsnapshot_fuzzer_defaults", // TODO(b/154633114): make host supported. // host_supported: true, @@ -289,6 +289,11 @@ cc_fuzz { 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"], @@ -298,3 +303,14 @@ cc_fuzz { 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, +} diff --git a/fs_mgr/libsnapshot/fuzz_utils.h b/fs_mgr/libsnapshot/fuzz_utils.h index 4dc6cdc0b..20b13b2fa 100644 --- a/fs_mgr/libsnapshot/fuzz_utils.h +++ b/fs_mgr/libsnapshot/fuzz_utils.h @@ -68,17 +68,25 @@ int CheckConsistency() { return 0; } +// Get the field descriptor for the oneof field in the action message. If no oneof field is set, +// return nullptr. template -void ExecuteActionProto(typename Action::Class* module, - const typename Action::Proto& action_proto) { +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; + return nullptr; } + return action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc); +} - const auto* field_desc = action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc); +template +void ExecuteActionProto(typename Action::ClassType* module, + const typename Action::Proto& action_proto) { + const auto* field_desc = GetValueFieldDescriptor(action_proto); + if (field_desc == nullptr) return; auto number = field_desc->number(); const auto& map = *Action::GetFunctionMap(); auto it = map.find(number); @@ -89,7 +97,7 @@ void ExecuteActionProto(typename Action::Class* module, template void ExecuteAllActionProtos( - typename Action::Class* module, + typename Action::ClassType* module, const google::protobuf::RepeatedPtrField& action_protos) { for (const auto& proto : action_protos) { ExecuteActionProto(module, proto); @@ -134,53 +142,57 @@ FUZZ_DEFINE_PRIMITIVE_GETTER(float, GetFloat); // ActionPerformer extracts arguments from the protobuf message, and then call FuzzFunction // with these arguments. template -struct ActionPerfomer; // undefined +struct ActionPerformerImpl; // undefined template -struct ActionPerfomer< +struct ActionPerformerImpl< FuzzFunction, void(const MessageProto&), typename std::enable_if_t>> { - static void Invoke(typename FuzzFunction::Class* module, - const google::protobuf::Message& action_proto, - const google::protobuf::FieldDescriptor* field_desc) { + 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>( action_proto.GetReflection()->GetMessage(action_proto, field_desc)); - FuzzFunction::ImplBody(module, arg); + return FuzzFunction::ImplBody(module, arg); } }; template -struct ActionPerfomer>> { - static void Invoke(typename FuzzFunction::Class* module, - const google::protobuf::Message& action_proto, - const google::protobuf::FieldDescriptor* field_desc) { +struct ActionPerformerImpl>> { + 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::fp, action_proto.GetReflection(), action_proto, field_desc); - FuzzFunction::ImplBody(module, arg); + return FuzzFunction::ImplBody(module, arg); } }; template -struct ActionPerfomer { - static void Invoke(typename FuzzFunction::Class* module, const google::protobuf::Message&, - const google::protobuf::FieldDescriptor*) { - FuzzFunction::ImplBody(module); +struct ActionPerformerImpl { + static typename FuzzFunction::ReturnType Invoke(typename FuzzFunction::ClassType* module, + const google::protobuf::Message&, + const google::protobuf::FieldDescriptor*) { + return FuzzFunction::ImplBody(module); } }; template -struct ActionPerfomer { - static void Invoke(typename FuzzFunction::Class* module, - const google::protobuf::Message& action_proto, - const google::protobuf::FieldDescriptor* field_desc) { +struct ActionPerformerImpl { + 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); - FuzzFunction::ImplBody(module, arg); + return FuzzFunction::ImplBody(module, arg); } }; +template +struct ActionPerformer : ActionPerformerImpl {}; + } // namespace android::fuzz // Fuzz existing C++ class, ClassType, with a collection of functions under the name Action. @@ -197,11 +209,11 @@ struct ActionPerfomer { // 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) \ +#define FUZZ_CLASS(Class, Action) \ class Action { \ public: \ using Proto = Action##Proto; \ - using Class = ClassType; \ + using ClassType = Class; \ using FunctionMap = android::fuzz::FunctionMap; \ static FunctionMap* GetFunctionMap() { \ static Action::FunctionMap map; \ @@ -225,29 +237,33 @@ struct ActionPerfomer { // } // class Foo { public: void DoAwesomeFoo(bool arg); }; // FUZZ_OBJECT(FooAction, Foo); -// FUZZ_FUNCTION(FooAction, DoFoo, module, bool arg) { +// 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, 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::Invoke; \ - Action::GetFunctionMap()->CheckEmplace(tag, func); \ - return true; \ - })(); \ - void FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::ImplBody(Action::Class* module, \ - ##__VA_ARGS__) +#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::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 { @@ -261,5 +277,9 @@ struct ActionPerfomer { // 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(); } +#define FUZZ_SIMPLE_FUNCTION(Action, FunctionName) \ + FUZZ_FUNCTION(Action, FunctionName, \ + decltype(std::declval().FunctionName()), \ + Action::ClassType* module) { \ + return module->FunctionName(); \ + } diff --git a/fs_mgr/libsnapshot/snapshot_fuzz.cpp b/fs_mgr/libsnapshot/snapshot_fuzz.cpp index 1e90ace6f..5b145c31f 100644 --- a/fs_mgr/libsnapshot/snapshot_fuzz.cpp +++ b/fs_mgr/libsnapshot/snapshot_fuzz.cpp @@ -21,14 +21,21 @@ #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; @@ -37,6 +44,8 @@ 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. @@ -74,48 +83,49 @@ 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__) +#define SNAPSHOT_FUZZ_FUNCTION(FunctionName, ReturnType, ...) \ + FUZZ_FUNCTION(SnapshotManagerAction, FunctionName, ReturnType, ISnapshotManager* snapshot, \ + ##__VA_ARGS__) -SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool wipe) { - (void)snapshot->FinishedSnapshotWrites(wipe); +SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool, bool wipe) { + return snapshot->FinishedSnapshotWrites(wipe); } -SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, const ProcessUpdateStateArgs& args) { +SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, bool, const ProcessUpdateStateArgs& args) { std::function before_cancel; if (args.has_before_cancel()) { before_cancel = [&]() { return args.fail_before_cancel(); }; } - (void)snapshot->ProcessUpdateState({}, before_cancel); + return snapshot->ProcessUpdateState({}, before_cancel); } -SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, bool has_progress_arg) { +SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, UpdateState, bool has_progress_arg) { double progress; - (void)snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr); + return snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr); } -SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool has_callback) { +SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool, bool has_callback) { std::function callback; if (has_callback) { callback = []() {}; } - (void)snapshot->HandleImminentDataWipe(callback); + return snapshot->HandleImminentDataWipe(callback); } -SNAPSHOT_FUZZ_FUNCTION(Dump) { +SNAPSHOT_FUZZ_FUNCTION(Dump, bool) { std::stringstream ss; - (void)snapshot->Dump(ss); + return snapshot->Dump(ss); } -SNAPSHOT_FUZZ_FUNCTION(CreateUpdateSnapshots, const DeltaArchiveManifest& manifest) { - (void)snapshot->CreateUpdateSnapshots(manifest); +SNAPSHOT_FUZZ_FUNCTION(CreateUpdateSnapshots, bool, const DeltaArchiveManifest& manifest) { + return snapshot->CreateUpdateSnapshots(manifest); } -SNAPSHOT_FUZZ_FUNCTION(UnmapUpdateSnapshot, const std::string& name) { - (void)snapshot->UnmapUpdateSnapshot(name); +SNAPSHOT_FUZZ_FUNCTION(UnmapUpdateSnapshot, bool, const std::string& name) { + return snapshot->UnmapUpdateSnapshot(name); } -SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions, +SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions, bool, const CreateLogicalAndSnapshotPartitionsArgs& args) { const std::string* super; if (args.use_correct_super()) { @@ -123,20 +133,21 @@ SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions, } else { super = &args.super(); } - (void)snapshot->CreateLogicalAndSnapshotPartitions( + return snapshot->CreateLogicalAndSnapshotPartitions( *super, std::chrono::milliseconds(args.timeout_millis())); } -SNAPSHOT_FUZZ_FUNCTION(RecoveryCreateSnapshotDevicesWithMetadata, +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()); } - (void)snapshot->RecoveryCreateSnapshotDevices(device); + return snapshot->RecoveryCreateSnapshotDevices(device); } -SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, const CreateLogicalPartitionParamsProto& params_proto) { +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()) { @@ -153,10 +164,10 @@ SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, const CreateLogicalPartitionParamsProt params.device_name = params_proto.device_name(); params.partition_opener = partition_opener.get(); std::string path; - (void)snapshot->MapUpdateSnapshot(params, &path); + return snapshot->MapUpdateSnapshot(params, &path); } -SNAPSHOT_FUZZ_FUNCTION(SwitchSlot) { +SNAPSHOT_FUZZ_FUNCTION(SwitchSlot, void) { (void)snapshot; CHECK(current_module != nullptr); CHECK(current_module->device_info != nullptr); @@ -194,7 +205,8 @@ 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; + (void)GetSnapshotFuzzEnv(); + [[maybe_unused]] static protobuf_mutator::protobuf::LogSilencer log_silencer; SetLogger(&FatalOnlyLogger); return 0; } @@ -202,15 +214,10 @@ int StopLoggingAfterGlobalInit() { SnapshotFuzzEnv* GetSnapshotFuzzEnv() { [[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit(); static SnapshotFuzzEnv env; - [[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit(); return &env; } -} // namespace android::snapshot - -DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) { - using namespace android::snapshot; - +SnapshotTestModule SetUpTest(const SnapshotFuzzData& snapshot_fuzz_data) { current_data = &snapshot_fuzz_data; auto env = GetSnapshotFuzzEnv(); @@ -219,9 +226,127 @@ DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) { auto test_module = env->CheckCreateSnapshotManager(snapshot_fuzz_data); current_module = &test_module; CHECK(test_module.snapshot); + return test_module; +} - SnapshotManagerAction::ExecuteAll(test_module.snapshot.get(), snapshot_fuzz_data.actions()); - +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