Add fuzz test for libsnapshot

Bug: 154633114
Test: source fuzz.sh && run_snapshot_fuzz_all -runs=100000

Change-Id: I5cd9e3f088ca283d3a49959c38aa74a483931f2c
This commit is contained in:
Yifan Hong 2020-04-22 18:45:34 -07:00
parent d694a67865
commit 1b0893ea5e
8 changed files with 905 additions and 0 deletions

View File

@ -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,
},
}

88
fs_mgr/libsnapshot/fuzz.sh Executable file
View File

@ -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
}

View File

@ -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 <android-base/logging.h>
namespace android::fuzz {
void CheckInternal(bool value, std::string_view msg) {
CHECK(value) << msg;
}
} // namespace android::fuzz

View File

@ -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 <optional>
#include <string>
#include <string_view>
#include <vector>
// 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 <typename T>
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<Bool> {
public:
Optional(std::optional<Bool>&& 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<Bool> 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<const uint8_t*>(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<DataView> 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 <typename T>
inline Optional<T> Consume() {
auto data_view = DataView::Consume(sizeof(T));
if (!data_view.has_value()) return nullptr;
return reinterpret_cast<const T*>(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<Bool>.
template <>
Optional<Bool> Consume<Bool>() {
if (!boolean_buffer_.has_value() || boolean_bit_offset_ >= sizeof(*boolean_buffer_)) {
boolean_buffer_ = Consume<uint8_t>();
boolean_bit_offset_ = 0;
}
if (!boolean_buffer_.has_value()) {
return Optional<Bool>(std::nullopt);
}
const auto& byte = *boolean_buffer_;
bool ret = (byte >> boolean_bit_offset_) & 0x1;
boolean_bit_offset_++;
return Optional<Bool>(Bool{ret});
}
private:
// Separate buffer for booleans.
Optional<uint8_t> boolean_buffer_ = nullptr;
uint8_t boolean_bit_offset_ = 0;
};
enum class CallResult {
SUCCESS,
NOT_ENOUGH_DATA,
};
inline bool AllArgsHasValue() {
return true;
}
template <typename T>
inline bool AllArgsHasValue(const Optional<T>& t) {
return t.has_value();
}
template <typename First, typename... Remaining>
inline bool AllArgsHasValue(const Optional<First>& first, const Optional<Remaining>&... 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 <typename T>
class FuzzFunction; // undefined
// A wrapper over a fuzzed function.
template <typename R, typename... Args>
class FuzzFunction<R(Args...)> : public FuzzFunctionBase {
public:
using Function = std::function<R(Args...)>;
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<std::remove_reference_t<Args>>()...);
}
private:
Function function_;
CallResult CallWithOptionalArgs(const Optional<std::remove_reference_t<Args>>&... 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<FunctionSizeType>::max().
template <typename FunctionsSizeType>
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<FunctionsSizeType>();
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 <typename R, typename... Args>
struct FunctionTraits {
using Function = std::function<R(Args...)>;
};
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<R(Args...)>, e.g. AddFunction<ReturnType(ArgType, ArgType)>
template <typename T>
void AddFunction(std::function<T>&& func) {
functions_.push_back(std::make_unique<FuzzFunction<T>>(std::move(func)));
}
// AddFunction<R, Args...>, e.g. AddFunction<ReturnType, ArgType, ArgType>
template <typename R, typename... Args>
void AddFunction(typename FunctionTraits<R, Args...>::Function&& func) {
functions_.push_back(std::make_unique<FuzzFunction<R(Args...)>>(std::move(func)));
}
// AddFunction<ArgType...>. Equivalent to AddFunction<void, Args...>
template <typename... Args>
void AddFunction(typename FunctionTraits<void, Args...>::Function&& func) {
functions_.push_back(std::make_unique<FuzzFunction<void(Args...)>>(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<int>(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<FunctionsSizeType>::max(),
"Need to extend number of bits for function count; there are " +
std::to_string(functions_.size()) + " functions now.");
}
private:
std::vector<std::unique_ptr<FuzzFunctionBase>> functions_;
};
// An object whose APIs are being fuzzed.
template <typename T, typename FunctionsSizeType>
class FuzzObject : public FuzzFunctions<FunctionsSizeType> {
public:
// Not thread-safe; client is responsible for ensuring only one thread calls DepleteData.
void DepleteData(T* obj, FuzzData* fuzz_data) {
obj_ = obj;
FuzzFunctions<FunctionsSizeType>::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

View File

@ -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;

View File

@ -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 <stddef.h>
#include <stdint.h>
#include <sysexits.h>
#include <functional>
#include <sstream>
#include <tuple>
#include <android-base/logging.h>
#include <storage_literals/storage_literals.h>
#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<ISnapshotManager, uint8_t> {
public:
FuzzSnapshotManager();
};
FuzzSnapshotManager::FuzzSnapshotManager() {
AddFunction([this]() { (void)get()->BeginUpdate(); });
AddFunction([this]() { (void)get()->CancelUpdate(); });
AddFunction<Bool>([this](Bool wipe) { (void)get()->FinishedSnapshotWrites(wipe); });
AddFunction([this]() { (void)get()->InitiateMerge(); });
AddFunction<Bool, Bool>([this](auto has_before_cancel, auto fail_before_cancel) {
std::function<bool()> before_cancel;
if (has_before_cancel) {
before_cancel = [=]() { return fail_before_cancel; };
}
(void)get()->ProcessUpdateState({}, before_cancel);
});
AddFunction<Bool>([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<Bool>([this](const Bool& has_callback) {
std::function<void()> 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<SnapshotManagerFuzzData>();
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;
}

View File

@ -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 <ftw.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <string>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <libsnapshot/auto_device.h>
#include <libsnapshot/snapshot.h>
#include <storage_literals/storage_literals.h>
#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<AutoDeleteDir> New(const std::string& path) {
if (!Mkdir(path)) {
return std::unique_ptr<AutoDeleteDir>(new AutoDeleteDir(""));
}
return std::unique_ptr<AutoDeleteDir>(new AutoDeleteDir(path));
}
~AutoDeleteDir() {
if (!HasDevice()) return;
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<AutoUnmount> New(const std::string& path, uint64_t size) {
if (mount("tmpfs", path.c_str(), "tmpfs", 0,
(void*)StringPrintf("size=%" PRIu64, size).data()) == -1) {
PLOG(ERROR) << "Cannot mount " << path;
return std::unique_ptr<AutoUnmount>(new AutoUnmount(""));
}
return std::unique_ptr<AutoUnmount>(new AutoUnmount(path));
}
~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<AutoMemBasedDir> New(const std::string& name, uint64_t size) {
auto ret = std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(name));
ret->auto_delete_mount_dir_ = AutoDeleteDir::New(ret->mount_path());
if (!ret->auto_delete_mount_dir_->HasDevice()) {
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
}
ret->auto_umount_mount_point_ = AutoUnmount::New(ret->mount_path(), size);
if (!ret->auto_umount_mount_point_->HasDevice()) {
return std::unique_ptr<AutoMemBasedDir>(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<AutoMemBasedDir>(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<AutoDeleteDir> auto_delete_mount_dir_;
std::unique_ptr<AutoUnmount> 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<IImageManager> 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<TestPartitionOpener> 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<TestPartitionOpener>(fake_super);
}
std::string SnapshotFuzzEnv::root() const {
CHECK(InitOk());
return fake_root_->path();
}
std::unique_ptr<ISnapshotManager> 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

View File

@ -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 <string>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <libfiemap/image_manager.h>
#include <libsnapshot/auto_device.h>
#include <libsnapshot/test_helpers.h>
// 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<ISnapshotManager> CreateSnapshotManager(const SnapshotManagerFuzzData& data);
private:
std::unique_ptr<AutoMemBasedDir> fake_root_;
static std::unique_ptr<android::fiemap::IImageManager> CreateFakeImageManager(
const std::string& fake_root);
static std::unique_ptr<TestPartitionOpener> 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<TestPartitionOpener>&& 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<TestPartitionOpener> partition_opener_;
std::string metadata_dir_;
};
} // namespace android::snapshot