Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b0893ea authored by Yifan Hong's avatar Yifan Hong
Browse files

Add fuzz test for libsnapshot

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

Change-Id: I5cd9e3f088ca283d3a49959c38aa74a483931f2c
parent d694a678
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
#!/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
}
+25 −0
Original line number Diff line number Diff line
// 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
+267 −0
Original line number Diff line number Diff line
// 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
+1 −0
Original line number Diff line number Diff line
@@ -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;
Loading