From 1b0893ea5e2acf2b1cf80d284131298bebd9d658 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Wed, 22 Apr 2020 18:45:34 -0700 Subject: [PATCH] Add fuzz test for libsnapshot Bug: 154633114 Test: source fuzz.sh && run_snapshot_fuzz_all -runs=100000 Change-Id: I5cd9e3f088ca283d3a49959c38aa74a483931f2c --- fs_mgr/libsnapshot/Android.bp | 41 +++ fs_mgr/libsnapshot/fuzz.sh | 88 ++++++ fs_mgr/libsnapshot/fuzz_utils.cpp | 25 ++ fs_mgr/libsnapshot/fuzz_utils.h | 267 ++++++++++++++++++ .../include/libsnapshot/snapshot.h | 1 + fs_mgr/libsnapshot/snapshot_fuzz.cpp | 141 +++++++++ fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp | 227 +++++++++++++++ fs_mgr/libsnapshot/snapshot_fuzz_utils.h | 115 ++++++++ 8 files changed, 905 insertions(+) create mode 100755 fs_mgr/libsnapshot/fuzz.sh create mode 100644 fs_mgr/libsnapshot/fuzz_utils.cpp create mode 100644 fs_mgr/libsnapshot/fuzz_utils.h create mode 100644 fs_mgr/libsnapshot/snapshot_fuzz.cpp create mode 100644 fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp create mode 100644 fs_mgr/libsnapshot/snapshot_fuzz_utils.h diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index 384595db6..705d29686 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -100,6 +100,7 @@ cc_library_static { cc_library_static { name: "libsnapshot_init", + native_coverage : true, defaults: ["libsnapshot_defaults"], srcs: [":libsnapshot_sources"], recovery_available: true, @@ -244,3 +245,43 @@ cc_test { ], gtest: false, } + +cc_fuzz { + name: "libsnapshot_fuzzer", + + // TODO(b/154633114): make host supported. + // host_supported: true, + + native_coverage : true, + srcs: [ + "snapshot_fuzz.cpp", + "snapshot_fuzz_utils.cpp", + "fuzz_utils.cpp", + ], + static_libs: [ + "libbase", + "libcrypto_static", + "libcutils", + "libfs_mgr", + "libgtest", // from libsnapshot_test_helpers + "libgmock", // from libsnapshot_test_helpers + "liblog", + "liblp", + "libsnapshot_init", // don't use binder or hwbinder + "libsnapshot_test_helpers", + "libprotobuf-cpp-lite", + "update_metadata-protos", + ], + header_libs: [ + "libstorage_literals_headers", + ], + + fuzz_config: { + cc: ["android-virtual-ab+bugs@google.com"], + componentid: 30545, + hotlists: ["1646452"], + fuzz_on_haiku_host: false, + // TODO(b/154633114): set to true to run this automatically. + fuzz_on_haiku_device: false, + }, +} diff --git a/fs_mgr/libsnapshot/fuzz.sh b/fs_mgr/libsnapshot/fuzz.sh new file mode 100755 index 000000000..64d822435 --- /dev/null +++ b/fs_mgr/libsnapshot/fuzz.sh @@ -0,0 +1,88 @@ +#!/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_CORPSE_DIR=/data/local/tmp/${FUZZ_TARGET} +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="" COVERAGE_PATHS="" m ${FUZZ_TARGET} + ret=$? + popd + return ${ret} +) + +build_cov() { + pushd $(gettop) + ret=$? + NATIVE_COVERAGE="true" NATIVE_LINE_COVERAGE="true" COVERAGE_PATHS="${PROJECT_PATH}" m ${FUZZ_TARGET} + popd + return ${ret} +} + +prepare_device() { + adb root && adb remount && + adb shell mkdir -p ${DEVICE_CORPSE_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} +} + +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_corpse() { + [[ "$@" ]] || { echo "run with -runs=X"; return 1; } + + prepare_device && + build_normal && + push_binary && + adb shell ${FUZZ_BINARY} "$@" ${DEVICE_CORPSE_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_CORPSE_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_corpse "$@" && + run_snapshot_fuzz && + show_fuzz_result +} diff --git a/fs_mgr/libsnapshot/fuzz_utils.cpp b/fs_mgr/libsnapshot/fuzz_utils.cpp new file mode 100644 index 000000000..509eb1bb3 --- /dev/null +++ b/fs_mgr/libsnapshot/fuzz_utils.cpp @@ -0,0 +1,25 @@ +// 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 + +namespace android::fuzz { + +void CheckInternal(bool value, std::string_view msg) { + CHECK(value) << msg; +} + +} // namespace android::fuzz diff --git a/fs_mgr/libsnapshot/fuzz_utils.h b/fs_mgr/libsnapshot/fuzz_utils.h new file mode 100644 index 000000000..4e14b9c57 --- /dev/null +++ b/fs_mgr/libsnapshot/fuzz_utils.h @@ -0,0 +1,267 @@ +// 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 + +// Generic classes for fuzzing a collection of APIs. + +namespace android::fuzz { + +// My custom boolean type -- to avoid conflict with (u)int8_t and char. +struct Bool { + bool value; + operator bool() const { return value; } +}; + +// Helper for FuzzData. +// A wrapper over an optional const object T. The buffer is maintained elsewhere. +template +class Optional { + public: + Optional(const T* ptr) : ptr_(ptr) {} + const T& operator*() const { return *ptr_; } + const T& value() const { return *ptr_; } + bool has_value() const { return ptr_; } + + private: + const T* ptr_; +}; + +// Helper for FuzzData. +// A wrapper over an optional boolean. The boolean is owned by this object. +template <> +class Optional { + public: + Optional(std::optional&& val) : val_(std::move(val)) {} + const Bool& operator*() const { return *val_; } + const Bool& value() const { return val_.value(); } + bool has_value() const { return val_.has_value(); } + + private: + std::optional val_; +}; + +// Helper for FuzzData. +// A view on a raw data buffer. Client is responsible for maintaining the lifetime of the data +// buffer. +class DataView { + public: + DataView(const uint8_t* data, uint64_t size) : data_(data), size_(size) {} + DataView(const void* data, uint64_t size) : DataView(static_cast(data), size) {} + inline uint64_t size() const { return size_; } + inline const uint8_t* data() const { return data_; } + inline bool CanConsume(uint64_t size) { return size_ >= size; } + // Consume the first |size| bytes from |this| and return a DataView object that represents + // the consumed data. Data pointer in |this| is incremented by |size| bytes. + // If not enough bytes, return nullopt. + std::optional Consume(uint64_t size) { + if (!CanConsume(size)) return std::nullopt; + DataView ret(data_, size); + size_ -= size; + data_ += size; + return ret; + } + + private: + const uint8_t* data_; + uint64_t size_; +}; + +// A view on the fuzz data. Provides APIs to consume typed objects. +class FuzzData : public DataView { + public: + // Inherit constructors. + using DataView::DataView; + // Consume a data object T and return the pointer (into the buffer). No copy is done. + // If not enough bytes, return nullptr. + template + inline Optional Consume() { + auto data_view = DataView::Consume(sizeof(T)); + if (!data_view.has_value()) return nullptr; + return reinterpret_cast(data_view->data()); + } + // To provide enough entropy for booleans, they are consumed bit by bit. + // Hence, the returned value is not indexed into the buffer. See Optional. + template <> + Optional Consume() { + if (!boolean_buffer_.has_value() || boolean_bit_offset_ >= sizeof(*boolean_buffer_)) { + boolean_buffer_ = Consume(); + boolean_bit_offset_ = 0; + } + if (!boolean_buffer_.has_value()) { + return Optional(std::nullopt); + } + const auto& byte = *boolean_buffer_; + bool ret = (byte >> boolean_bit_offset_) & 0x1; + boolean_bit_offset_++; + return Optional(Bool{ret}); + } + + private: + // Separate buffer for booleans. + Optional boolean_buffer_ = nullptr; + uint8_t boolean_bit_offset_ = 0; +}; + +enum class CallResult { + SUCCESS, + NOT_ENOUGH_DATA, +}; + +inline bool AllArgsHasValue() { + return true; +} +template +inline bool AllArgsHasValue(const Optional& t) { + return t.has_value(); +} +template +inline bool AllArgsHasValue(const Optional& first, const Optional&... remaining) { + return first.has_value() && AllArgsHasValue(remaining...); +} + +// Base class of FuzzFunction. +class FuzzFunctionBase { + public: + virtual ~FuzzFunctionBase() = default; + virtual CallResult Call(FuzzData* fuzz_data) const = 0; +}; + +template +class FuzzFunction; // undefined + +// A wrapper over a fuzzed function. +template +class FuzzFunction : public FuzzFunctionBase { + public: + using Function = std::function; + FuzzFunction(Function&& function) : function_(std::move(function)) {} + // Eat necessary data in |fuzz_data| and invoke the function. + CallResult Call(FuzzData* fuzz_data) const override { + return CallWithOptionalArgs(fuzz_data->Consume>()...); + } + + private: + Function function_; + + CallResult CallWithOptionalArgs(const Optional>&... args) const { + if (!AllArgsHasValue(args...)) { + return CallResult::NOT_ENOUGH_DATA; + } + (void)function_(args.value()...); // ignore returned value + return CallResult::SUCCESS; + } +}; + +// CHECK(value) << msg +void CheckInternal(bool value, std::string_view msg); + +// A collection of FuzzFunction's. +// FunctionsSizeType must be an integral type where +// functions_.size() <= std::numeric_limits::max(). +template +class FuzzFunctions { + public: + // Subclass should override this to register functions via AddFunction. + FuzzFunctions() = default; + virtual ~FuzzFunctions() = default; + // Eat some amount of data in |fuzz_data| and call one of the |functions_|. + CallResult CallOne(FuzzData* fuzz_data) const { + auto opt_number = fuzz_data->Consume(); + if (!opt_number.has_value()) { + return CallResult::NOT_ENOUGH_DATA; + } + auto function_index = opt_number.value() % functions_.size(); + return functions_[function_index]->Call(fuzz_data); + } + + private: + template + struct FunctionTraits { + using Function = std::function; + }; + + public: + // There are no deduction guide from lambda to std::function, so the + // signature of the lambda must be specified in the template argument. + // FuzzFunctions provide the following 3 ways to specify the signature of + // the lambda: + + // AddFunction, e.g. AddFunction + template + void AddFunction(std::function&& func) { + functions_.push_back(std::make_unique>(std::move(func))); + } + + // AddFunction, e.g. AddFunction + template + void AddFunction(typename FunctionTraits::Function&& func) { + functions_.push_back(std::make_unique>(std::move(func))); + } + + // AddFunction. Equivalent to AddFunction + template + void AddFunction(typename FunctionTraits::Function&& func) { + functions_.push_back(std::make_unique>(std::move(func))); + } + + // Use |fuzz_data| as a guide to call |functions_| until |fuzz_data| is + // depleted. Return + void DepleteData(FuzzData* fuzz_data) const { + CallResult result; + while ((result = CallOne(fuzz_data)) == CallResult::SUCCESS) + ; + CheckInternal(result == CallResult::NOT_ENOUGH_DATA, + "result is " + std::to_string(static_cast(result))); + } + + protected: + // Helper for subclass to check that size of |functions_| is actually within + // SizeType. Should be called after all functions are registered. + void CheckFunctionsSize() const { + CheckInternal(functions_.size() <= std::numeric_limits::max(), + "Need to extend number of bits for function count; there are " + + std::to_string(functions_.size()) + " functions now."); + } + + private: + std::vector> functions_; +}; + +// An object whose APIs are being fuzzed. +template +class FuzzObject : public FuzzFunctions { + public: + // Not thread-safe; client is responsible for ensuring only one thread calls DepleteData. + void DepleteData(T* obj, FuzzData* fuzz_data) { + obj_ = obj; + FuzzFunctions::DepleteData(fuzz_data); + obj_ = nullptr; + } + + protected: + // Helper for subclass to get the module under test in the added functions. + T* get() const { + CheckInternal(obj_ != nullptr, "No module under test is found."); + return obj_; + } + + private: + T* obj_ = nullptr; +}; + +} // namespace android::fuzz diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index fff667ee5..808186618 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -318,6 +318,7 @@ class SnapshotManager final : public ISnapshotManager { friend class SnapshotUpdateTest; friend class FlashAfterUpdateTest; friend class LockTestConsumer; + friend class SnapshotFuzzEnv; friend struct AutoDeleteCowImage; friend struct AutoDeleteSnapshot; friend struct PartitionCowCreator; diff --git a/fs_mgr/libsnapshot/snapshot_fuzz.cpp b/fs_mgr/libsnapshot/snapshot_fuzz.cpp new file mode 100644 index 000000000..12a0531c3 --- /dev/null +++ b/fs_mgr/libsnapshot/snapshot_fuzz.cpp @@ -0,0 +1,141 @@ +// 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 "fuzz_utils.h" +#include "snapshot_fuzz_utils.h" + +using android::base::LogId; +using android::base::LogSeverity; +using android::base::SetLogger; +using android::base::StderrLogger; +using android::base::StdioLogger; +using android::fuzz::Bool; +using android::fuzz::FuzzData; +using android::fuzz::FuzzObject; +using android::snapshot::SnapshotFuzzEnv; +using android::snapshot::SnapshotManagerFuzzData; + +// 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 { + +class FuzzSnapshotManager : public FuzzObject { + public: + FuzzSnapshotManager(); +}; + +FuzzSnapshotManager::FuzzSnapshotManager() { + AddFunction([this]() { (void)get()->BeginUpdate(); }); + AddFunction([this]() { (void)get()->CancelUpdate(); }); + AddFunction([this](Bool wipe) { (void)get()->FinishedSnapshotWrites(wipe); }); + AddFunction([this]() { (void)get()->InitiateMerge(); }); + AddFunction([this](auto has_before_cancel, auto fail_before_cancel) { + std::function before_cancel; + if (has_before_cancel) { + before_cancel = [=]() { return fail_before_cancel; }; + } + (void)get()->ProcessUpdateState({}, before_cancel); + }); + AddFunction([this](auto has_progress_arg) { + double progress; + (void)get()->GetUpdateState(has_progress_arg ? &progress : nullptr); + }); + // TODO add CreateUpdateSnapshots according to proto + // TODO add MapUpdateSnapshot + // TODO add UnmapUpdateSnapshot using names from the dictionary + AddFunction([this]() { (void)get()->NeedSnapshotsInFirstStageMount(); }); + // TODO add CreateLogicalAndSnapshotPartitions + AddFunction([this](const Bool& has_callback) { + std::function callback; + if (has_callback) { + callback = []() {}; + } + (void)get()->HandleImminentDataWipe(callback); + }); + AddFunction([this]() { (void)get()->RecoveryCreateSnapshotDevices(); }); + // TODO add RecoveryCreateSnapshotDevices with metadata_device arg + AddFunction([this]() { + std::stringstream ss; + (void)get()->Dump(ss); + }); + AddFunction([this]() { (void)get()->EnsureMetadataMounted(); }); + AddFunction([this]() { (void)get()->GetSnapshotMergeStatsInstance(); }); + + CheckFunctionsSize(); +} + +// 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); + } +} +// Stop logging (except fatal messages) after global initialization. This is only done once. +int StopLoggingAfterGlobalInit() { + SetLogger(&FatalOnlyLogger); + return 0; +} + +} // namespace android::snapshot + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + using namespace android::snapshot; + + [[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit(); + static SnapshotFuzzEnv env; + static FuzzSnapshotManager fuzz_snapshot_manager; + [[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit(); + + CHECK(env.InitOk()); + FuzzData fuzz_data(data, size); + + auto snapshot_manager_data = fuzz_data.Consume(); + if (!snapshot_manager_data.has_value()) { + return 0; + } + auto snapshot_manager = env.CreateSnapshotManager(snapshot_manager_data.value()); + CHECK(snapshot_manager); + + fuzz_snapshot_manager.DepleteData(snapshot_manager.get(), &fuzz_data); + + return 0; +} diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp new file mode 100644 index 000000000..d5e3e968e --- /dev/null +++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp @@ -0,0 +1,227 @@ +// 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 + +#include "snapshot_fuzz_utils.h" + +using namespace android::storage_literals; +using namespace std::string_literals; + +using android::base::StringPrintf; +using android::base::unique_fd; +using android::base::WriteStringToFile; +using android::fiemap::IImageManager; +using android::fiemap::ImageManager; + +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 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; +} + +class AutoDeleteDir : public AutoDevice { + public: + static std::unique_ptr New(const std::string& path) { + if (!Mkdir(path)) { + return std::unique_ptr(new AutoDeleteDir("")); + } + return std::unique_ptr(new AutoDeleteDir(path)); + } + ~AutoDeleteDir() { + if (!HasDevice()) return; + if (rmdir(name_.c_str()) == -1) { + PLOG(ERROR) << "Cannot remove " << name_; + } + } + + private: + AutoDeleteDir(const std::string& path) : AutoDevice(path) {} +}; + +class AutoUnmount : public AutoDevice { + public: + static std::unique_ptr 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(new AutoUnmount("")); + } + 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) {} +}; + +// 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) { + 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); + if (!ret->auto_umount_mount_point_->HasDevice()) { + return std::unique_ptr(new AutoMemBasedDir("")); + } + // path() does not need to be deleted upon destruction, hence it is not wrapped with + // AutoDeleteDir. + if (!Mkdir(ret->path())) { + return std::unique_ptr(new AutoMemBasedDir("")); + } + return ret; + } + // Return the scratch directory. + std::string path() const { + CHECK(HasDevice()); + return mount_path() + "/root"; + } + // Delete all contents in path() and start over. path() itself is re-created. + bool SoftReset() { return RmdirRecursive(path()) && Mkdir(path()); } + + private: + AutoMemBasedDir(const std::string& name) : AutoDevice(name) {} + std::string mount_path() const { + CHECK(HasDevice()); + return MNT_DIR + "/"s + name_; + } + std::unique_ptr auto_delete_mount_dir_; + std::unique_ptr auto_umount_mount_point_; +}; + +SnapshotFuzzEnv::SnapshotFuzzEnv() { + fake_root_ = AutoMemBasedDir::New(FAKE_ROOT_NAME, FAKE_ROOT_SIZE); +} + +SnapshotFuzzEnv::~SnapshotFuzzEnv() = default; + +bool SnapshotFuzzEnv::InitOk() const { + if (fake_root_ == nullptr || !fake_root_->HasDevice()) return false; + return true; +} + +bool SnapshotFuzzEnv::SoftReset() { + return fake_root_->SoftReset(); +} + +std::unique_ptr SnapshotFuzzEnv::CreateFakeImageManager( + const std::string& fake_root) { + auto images_dir = fake_root + "/images"; + auto metadata_dir = images_dir + "/metadata"; + auto data_dir = images_dir + "/data"; + + if (!Mkdir(images_dir) || !Mkdir(metadata_dir) || !Mkdir(data_dir)) { + return nullptr; + } + return ImageManager::Open(metadata_dir, data_dir); +} + +std::unique_ptr SnapshotFuzzEnv::CreatePartitionOpener( + const std::string& fake_root) { + auto fake_super = fake_root + "/super.img"; + std::string zeros(SUPER_IMAGE_SIZE, '\0'); + + if (!WriteStringToFile(zeros, fake_super)) { + PLOG(ERROR) << "Cannot write zeros to " << fake_super; + return nullptr; + } + + return std::make_unique(fake_super); +} + +std::string SnapshotFuzzEnv::root() const { + CHECK(InitOk()); + return fake_root_->path(); +} + +std::unique_ptr SnapshotFuzzEnv::CreateSnapshotManager( + const SnapshotManagerFuzzData& data) { + // TODO(b/154633114): create valid super partition according to fuzz data + auto partition_opener = CreatePartitionOpener(root()); + if (partition_opener == nullptr) return nullptr; + auto metadata_dir = root() + "/snapshot_metadata"; + if (!Mkdir(metadata_dir)) return nullptr; + + 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_ = CreateFakeImageManager(root()); + snapshot->has_local_image_manager_ = data.is_local_image_manager; + + return snapshot; +} + +} // namespace android::snapshot diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h new file mode 100644 index 000000000..32910a9ad --- /dev/null +++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h @@ -0,0 +1,115 @@ +// 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 + +// libsnapshot-specific code for fuzzing. Defines fake classes that are depended +// by SnapshotManager. + +namespace android::snapshot { + +// Controls the behavior of IDeviceInfo. +typedef struct SnapshotFuzzDeviceInfoData { + bool slot_suffix_is_a : 1; + bool is_overlayfs_setup : 1; + bool allow_set_boot_control_merge_status : 1; + bool allow_set_slot_as_unbootable : 1; + bool is_recovery : 1; +} __attribute__((packed)) SnapshotFuzzDeviceInfoData; + +// Controls the behavior of the test SnapshotManager. +typedef struct SnapshotManagerFuzzData { + SnapshotFuzzDeviceInfoData device_info_data; + bool is_local_image_manager : 1; +} __attribute__((packed)) SnapshotManagerFuzzData; + +class AutoMemBasedDir; + +// Prepare test environment. This has a heavy overhead and should be done once. +class SnapshotFuzzEnv { + public: + // Check if test should run at all. + static bool ShouldSkipTest(); + + // Initialize the environment. + SnapshotFuzzEnv(); + ~SnapshotFuzzEnv(); + + // Check if environment is initialized properly. + bool InitOk() const; + + // A scratch directory for the test to play around with. The scratch directory + // is backed by tmpfs. SoftReset() clears the directory. + std::string root() const; + + // Soft reset part of the environment before running the next test. + bool SoftReset(); + + // 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 CreateSnapshotManager(const SnapshotManagerFuzzData& data); + + private: + std::unique_ptr fake_root_; + + static std::unique_ptr CreateFakeImageManager( + const std::string& fake_root); + static std::unique_ptr CreatePartitionOpener(const std::string& fake_root); +}; + +class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo { + public: + // Client is responsible for maintaining the lifetime of |data|. + SnapshotFuzzDeviceInfo(const SnapshotFuzzDeviceInfoData& data, + std::unique_ptr&& partition_opener, + const std::string& metadata_dir) + : data_(data), + partition_opener_(std::move(partition_opener)), + metadata_dir_(metadata_dir) {} + + // Following APIs are mocked. + std::string GetGsidDir() const override { return "fuzz_ota"; } + 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 data_.slot_suffix_is_a ? "_a" : "_b"; } + std::string GetOtherSlotSuffix() const override { return data_.slot_suffix_is_a ? "_b" : "_a"; } + bool IsOverlayfsSetup() const override { return data_.is_overlayfs_setup; } + bool SetBootControlMergeStatus(android::hardware::boot::V1_1::MergeStatus) override { + return data_.allow_set_boot_control_merge_status; + } + bool SetSlotAsUnbootable(unsigned int) override { return data_.allow_set_slot_as_unbootable; } + bool IsRecovery() const override { return data_.is_recovery; } + + private: + SnapshotFuzzDeviceInfoData data_; + std::unique_ptr partition_opener_; + std::string metadata_dir_; +}; + +} // namespace android::snapshot