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:
parent
d694a67865
commit
1b0893ea5e
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue