Merge changes Ife319450,I2ecf838b
* changes: snapshotctl: Display merge progress and phases. Remove the libsnapshot fuzzer.
This commit is contained in:
commit
10c200db28
11 changed files with 27 additions and 1879 deletions
|
@ -366,80 +366,6 @@ cc_test {
|
|||
gtest: false,
|
||||
}
|
||||
|
||||
cc_defaults {
|
||||
name: "libsnapshot_fuzzer_defaults",
|
||||
defaults: [
|
||||
"libsnapshot_cow_defaults",
|
||||
],
|
||||
native_coverage : true,
|
||||
srcs: [
|
||||
// Compile the protobuf definition again with type full.
|
||||
"android/snapshot/snapshot_fuzz.proto",
|
||||
"update_engine/update_metadata.proto",
|
||||
"fuzz_utils.cpp",
|
||||
"snapshot_fuzz.cpp",
|
||||
"snapshot_fuzz_utils.cpp",
|
||||
|
||||
// Compile libsnapshot sources directly to avoid dependency
|
||||
// to update_metadata-protos
|
||||
":libsnapshot_sources",
|
||||
],
|
||||
static_libs: [
|
||||
"libbase",
|
||||
"libbrotli",
|
||||
"libc++fs",
|
||||
"libchrome",
|
||||
"libcrypto_static",
|
||||
"libcutils",
|
||||
"libext2_uuid",
|
||||
"libext4_utils",
|
||||
"libfstab",
|
||||
"libfs_mgr",
|
||||
"libgtest", // from libsnapshot_test_helpers
|
||||
"libgmock", // from libsnapshot_test_helpers
|
||||
"liblog",
|
||||
"liblp",
|
||||
"libsnapshot_cow",
|
||||
"libsnapshot_test_helpers",
|
||||
"libprotobuf-mutator",
|
||||
"libz",
|
||||
],
|
||||
header_libs: [
|
||||
"libfiemap_headers",
|
||||
"libstorage_literals_headers",
|
||||
"libupdate_engine_headers",
|
||||
],
|
||||
proto: {
|
||||
type: "full",
|
||||
canonical_path_from_root: false,
|
||||
local_include_dirs: ["."],
|
||||
},
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "libsnapshot_fuzzer",
|
||||
defaults: ["libsnapshot_fuzzer_defaults"],
|
||||
corpus: ["corpus/*"],
|
||||
fuzz_config: {
|
||||
cc: ["android-virtual-ab+bugs@google.com"],
|
||||
componentid: 30545,
|
||||
hotlists: ["1646452"],
|
||||
fuzz_on_haiku_host: false,
|
||||
fuzz_on_haiku_device: true,
|
||||
},
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "libsnapshot_fuzzer_test",
|
||||
defaults: ["libsnapshot_fuzzer_defaults"],
|
||||
data: ["corpus/*"],
|
||||
test_suites: [
|
||||
"device-tests",
|
||||
],
|
||||
auto_gen_config: true,
|
||||
require_root: true,
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "cow_api_test",
|
||||
defaults: [
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package android.snapshot;
|
||||
|
||||
import "update_engine/update_metadata.proto";
|
||||
|
||||
// Controls the behavior of IDeviceInfo.
|
||||
// Next: 6
|
||||
message FuzzDeviceInfoData {
|
||||
bool slot_suffix_is_a = 1;
|
||||
bool is_overlayfs_setup = 2;
|
||||
bool allow_set_boot_control_merge_status = 3;
|
||||
bool allow_set_slot_as_unbootable = 4;
|
||||
bool is_recovery = 5;
|
||||
}
|
||||
|
||||
// Controls the behavior of the test SnapshotManager.
|
||||
// Next: 2
|
||||
message FuzzSnapshotManagerData {
|
||||
bool is_local_image_manager = 1;
|
||||
}
|
||||
|
||||
// A simplified version of CreateLogicalPartitionParams for fuzzing.
|
||||
// Next: 9
|
||||
message CreateLogicalPartitionParamsProto {
|
||||
bool use_correct_super = 1;
|
||||
string block_device = 2;
|
||||
bool has_metadata_slot = 3;
|
||||
uint32 metadata_slot = 4;
|
||||
string partition_name = 5;
|
||||
bool force_writable = 6;
|
||||
int64 timeout_millis = 7;
|
||||
string device_name = 8;
|
||||
}
|
||||
|
||||
// Mimics the API of ISnapshotManager. Defines one action on the snapshot
|
||||
// manager.
|
||||
// Next: 18
|
||||
message SnapshotManagerActionProto {
|
||||
message NoArgs {}
|
||||
message ProcessUpdateStateArgs {
|
||||
bool has_before_cancel = 1;
|
||||
bool fail_before_cancel = 2;
|
||||
}
|
||||
message CreateLogicalAndSnapshotPartitionsArgs {
|
||||
bool use_correct_super = 1;
|
||||
string super = 2;
|
||||
int64 timeout_millis = 3;
|
||||
}
|
||||
message RecoveryCreateSnapshotDevicesArgs {
|
||||
bool has_metadata_device_object = 1;
|
||||
bool metadata_mounted = 2;
|
||||
}
|
||||
reserved 18 to 9999;
|
||||
oneof value {
|
||||
NoArgs begin_update = 1;
|
||||
NoArgs cancel_update = 2;
|
||||
bool finished_snapshot_writes = 3;
|
||||
NoArgs initiate_merge = 4;
|
||||
ProcessUpdateStateArgs process_update_state = 5;
|
||||
bool get_update_state = 6;
|
||||
chromeos_update_engine.DeltaArchiveManifest create_update_snapshots = 7;
|
||||
CreateLogicalPartitionParamsProto map_update_snapshot = 8;
|
||||
string unmap_update_snapshot = 9;
|
||||
NoArgs need_snapshots_in_first_stage_mount = 10;
|
||||
CreateLogicalAndSnapshotPartitionsArgs create_logical_and_snapshot_partitions = 11;
|
||||
bool handle_imminent_data_wipe = 12;
|
||||
NoArgs recovery_create_snapshot_devices = 13;
|
||||
RecoveryCreateSnapshotDevicesArgs recovery_create_snapshot_devices_with_metadata = 14;
|
||||
NoArgs dump = 15;
|
||||
NoArgs ensure_metadata_mounted = 16;
|
||||
NoArgs get_snapshot_merge_stats_instance = 17;
|
||||
|
||||
// Test directives that has nothing to do with ISnapshotManager API surface.
|
||||
NoArgs switch_slot = 10000;
|
||||
}
|
||||
}
|
||||
|
||||
// Includes all data that needs to be fuzzed.
|
||||
message SnapshotFuzzData {
|
||||
FuzzDeviceInfoData device_info_data = 1;
|
||||
FuzzSnapshotManagerData manager_data = 2;
|
||||
|
||||
// If true:
|
||||
// - if super_data is empty, create empty super partition metadata.
|
||||
// - otherwise, create super partition metadata accordingly.
|
||||
// If false, no valid super partition metadata (it is zeroed)
|
||||
bool is_super_metadata_valid = 3;
|
||||
chromeos_update_engine.DeltaArchiveManifest super_data = 4;
|
||||
|
||||
// Whether the directory that mocks /metadata/ota/snapshot is created.
|
||||
bool has_metadata_snapshots_dir = 5;
|
||||
|
||||
// More data used to prep the test before running actions.
|
||||
reserved 6 to 9999;
|
||||
repeated SnapshotManagerActionProto actions = 10000;
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
device_info_data {
|
||||
allow_set_slot_as_unbootable: true
|
||||
is_recovery: true
|
||||
}
|
||||
is_super_metadata_valid: true
|
||||
super_data {
|
||||
partitions {
|
||||
partition_name: "sys_a"
|
||||
new_partition_info {
|
||||
size: 3145728
|
||||
}
|
||||
}
|
||||
partitions {
|
||||
partition_name: "vnnd_"
|
||||
new_partition_info {
|
||||
size: 3145728
|
||||
}
|
||||
}
|
||||
partitions {
|
||||
partition_name: "prd_a"
|
||||
new_partition_info {
|
||||
}
|
||||
}
|
||||
dynamic_partition_metadata {
|
||||
groups {
|
||||
name: "group_google_dp_a"
|
||||
size: 34375467008
|
||||
partition_names: "sys_a"
|
||||
partition_names: "vnd_a"
|
||||
partition_names: "prd_a"
|
||||
}
|
||||
}
|
||||
}
|
||||
has_metadata_snapshots_dir: true
|
||||
actions {
|
||||
handle_imminent_data_wipe: true
|
||||
}
|
||||
actions {
|
||||
begin_update {
|
||||
}
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
device_info_data {
|
||||
slot_suffix_is_a: true
|
||||
is_overlayfs_setup: false
|
||||
allow_set_boot_control_merge_status: true
|
||||
allow_set_slot_as_unbootable: true
|
||||
is_recovery: false
|
||||
}
|
||||
manager_data {
|
||||
is_local_image_manager: false
|
||||
}
|
||||
is_super_metadata_valid: true
|
||||
super_data {
|
||||
partitions {
|
||||
partition_name: "sys_a"
|
||||
new_partition_info {
|
||||
size: 3145728
|
||||
}
|
||||
}
|
||||
partitions {
|
||||
partition_name: "vnd_a"
|
||||
new_partition_info {
|
||||
size: 3145728
|
||||
}
|
||||
}
|
||||
partitions {
|
||||
partition_name: "prd_a"
|
||||
new_partition_info {
|
||||
size: 3145728
|
||||
}
|
||||
}
|
||||
dynamic_partition_metadata {
|
||||
groups {
|
||||
name: "group_google_dp_a"
|
||||
size: 15728640
|
||||
partition_names: "sys_a"
|
||||
partition_names: "vnd_a"
|
||||
partition_names: "prd_a"
|
||||
}
|
||||
}
|
||||
}
|
||||
has_metadata_snapshots_dir: true
|
||||
actions {
|
||||
begin_update {
|
||||
}
|
||||
}
|
||||
actions {
|
||||
create_update_snapshots {
|
||||
partitions {
|
||||
partition_name: "sys"
|
||||
new_partition_info {
|
||||
size: 3878912
|
||||
}
|
||||
operations {
|
||||
type: ZERO,
|
||||
dst_extents {
|
||||
start_block: 0
|
||||
num_blocks: 947
|
||||
}
|
||||
}
|
||||
}
|
||||
partitions {
|
||||
partition_name: "vnd"
|
||||
new_partition_info {
|
||||
size: 3878912
|
||||
}
|
||||
operations {
|
||||
type: ZERO,
|
||||
dst_extents {
|
||||
start_block: 0
|
||||
num_blocks: 947
|
||||
}
|
||||
}
|
||||
}
|
||||
partitions {
|
||||
partition_name: "prd"
|
||||
new_partition_info {
|
||||
size: 3878912
|
||||
}
|
||||
operations {
|
||||
type: ZERO,
|
||||
dst_extents {
|
||||
start_block: 0
|
||||
num_blocks: 947
|
||||
}
|
||||
}
|
||||
}
|
||||
dynamic_partition_metadata {
|
||||
groups {
|
||||
name: "group_google_dp"
|
||||
size: 15728640
|
||||
partition_names: "sys"
|
||||
partition_names: "vnd"
|
||||
partition_names: "prd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
actions {
|
||||
map_update_snapshot {
|
||||
use_correct_super: true
|
||||
has_metadata_slot: true
|
||||
metadata_slot: 1
|
||||
partition_name: "sys_b"
|
||||
force_writable: true
|
||||
timeout_millis: 3000
|
||||
}
|
||||
}
|
||||
actions {
|
||||
map_update_snapshot {
|
||||
use_correct_super: true
|
||||
has_metadata_slot: true
|
||||
metadata_slot: 1
|
||||
partition_name: "vnd_b"
|
||||
force_writable: true
|
||||
timeout_millis: 3000
|
||||
}
|
||||
}
|
||||
actions {
|
||||
map_update_snapshot {
|
||||
use_correct_super: true
|
||||
has_metadata_slot: true
|
||||
metadata_slot: 1
|
||||
partition_name: "prd_b"
|
||||
force_writable: true
|
||||
timeout_millis: 3000
|
||||
}
|
||||
}
|
||||
actions {
|
||||
finished_snapshot_writes: false
|
||||
}
|
||||
actions {
|
||||
unmap_update_snapshot: "sys_b"
|
||||
}
|
||||
actions {
|
||||
unmap_update_snapshot: "vnd_b"
|
||||
}
|
||||
actions {
|
||||
unmap_update_snapshot: "prd_b"
|
||||
}
|
||||
actions {
|
||||
switch_slot {
|
||||
}
|
||||
}
|
||||
actions {
|
||||
need_snapshots_in_first_stage_mount {
|
||||
}
|
||||
}
|
||||
actions {
|
||||
create_logical_and_snapshot_partitions {
|
||||
use_correct_super: true
|
||||
timeout_millis: 5000
|
||||
}
|
||||
}
|
||||
actions {
|
||||
initiate_merge {
|
||||
}
|
||||
}
|
||||
actions {
|
||||
process_update_state {
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
#!/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_INIT_CORPUS_DIR=/data/fuzz/${TARGET_ARCH}/${FUZZ_TARGET}/corpus
|
||||
DEVICE_GENERATED_CORPUS_DIR=/data/local/tmp/${FUZZ_TARGET}/corpus
|
||||
DEVICE_GCOV_DIR=/data/local/tmp/${FUZZ_TARGET}/gcov
|
||||
HOST_SCRATCH_DIR=/tmp/${FUZZ_TARGET}
|
||||
GCOV_TOOL=${HOST_SCRATCH_DIR}/llvm-gcov
|
||||
|
||||
build_normal() (
|
||||
pushd $(gettop)
|
||||
NATIVE_COVERAGE="" NATIVE_LINE_COVERAGE="" NATIVE_COVERAGE_PATHS="" m ${FUZZ_TARGET}
|
||||
ret=$?
|
||||
popd
|
||||
return ${ret}
|
||||
)
|
||||
|
||||
build_cov() {
|
||||
pushd $(gettop)
|
||||
NATIVE_COVERAGE="true" NATIVE_LINE_COVERAGE="true" NATIVE_COVERAGE_PATHS="${PROJECT_PATH}" m ${FUZZ_TARGET}
|
||||
ret=$?
|
||||
popd
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
prepare_device() {
|
||||
adb root && adb remount &&
|
||||
adb shell mkdir -p ${DEVICE_GENERATED_CORPUS_DIR} &&
|
||||
adb shell rm -rf ${DEVICE_GCOV_DIR} &&
|
||||
adb shell mkdir -p ${DEVICE_GCOV_DIR}
|
||||
}
|
||||
|
||||
push_binary() {
|
||||
adb push ${ANDROID_PRODUCT_OUT}/${FUZZ_BINARY} ${FUZZ_BINARY} &&
|
||||
adb push ${ANDROID_PRODUCT_OUT}/${DEVICE_INIT_CORPUS_DIR} $(dirname ${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_corpus() {
|
||||
[[ "$@" ]] || { echo "run with -runs=X"; return 1; }
|
||||
|
||||
prepare_device &&
|
||||
build_normal &&
|
||||
push_binary &&
|
||||
adb shell ${FUZZ_BINARY} "$@" ${DEVICE_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_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_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_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_corpus "$@" &&
|
||||
run_snapshot_fuzz &&
|
||||
show_fuzz_result
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// 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;
|
||||
}
|
||||
|
||||
const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
|
||||
const google::protobuf::Descriptor* action_desc) {
|
||||
CHECK(action_desc);
|
||||
CHECK(action_desc->oneof_decl_count() == 1)
|
||||
<< action_desc->oneof_decl_count() << " oneof fields found in " << action_desc->name()
|
||||
<< "; only one is expected.";
|
||||
auto* oneof_value_desc = action_desc->oneof_decl(0);
|
||||
CHECK(oneof_value_desc);
|
||||
CHECK(oneof_value_desc->name() == "value")
|
||||
<< "oneof field has name " << oneof_value_desc->name();
|
||||
return oneof_value_desc;
|
||||
}
|
||||
|
||||
} // namespace android::fuzz
|
|
@ -1,285 +0,0 @@
|
|||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <google/protobuf/descriptor.h>
|
||||
#include <google/protobuf/message.h>
|
||||
#include <google/protobuf/repeated_field.h>
|
||||
|
||||
// Utilities for using a protobuf definition to fuzz APIs in a class.
|
||||
// Terms:
|
||||
// The "fuzzed class" is the C++ class definition whose functions are fuzzed.
|
||||
// The "fuzzed object" is an instantiated object of the fuzzed class. It is
|
||||
// typically created and destroyed for each test run.
|
||||
// An "action" is an operation on the fuzzed object that may mutate its state.
|
||||
// This typically involves one function call into the fuzzed object.
|
||||
|
||||
namespace android::fuzz {
|
||||
|
||||
// CHECK(value) << msg
|
||||
void CheckInternal(bool value, std::string_view msg);
|
||||
|
||||
// Get the oneof descriptor inside Action
|
||||
const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
|
||||
const google::protobuf::Descriptor* action_desc);
|
||||
|
||||
template <typename Class>
|
||||
using FunctionMapImpl =
|
||||
std::map<int, std::function<void(Class*, const google::protobuf::Message& action_proto,
|
||||
const google::protobuf::FieldDescriptor* field_desc)>>;
|
||||
|
||||
template <typename Class>
|
||||
class FunctionMap : public FunctionMapImpl<Class> {
|
||||
public:
|
||||
void CheckEmplace(typename FunctionMapImpl<Class>::key_type key,
|
||||
typename FunctionMapImpl<Class>::mapped_type&& value) {
|
||||
auto [it, inserted] = this->emplace(key, std::move(value));
|
||||
CheckInternal(inserted,
|
||||
"Multiple implementation registered for tag number " + std::to_string(key));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Action>
|
||||
int CheckConsistency() {
|
||||
const auto* function_map = Action::GetFunctionMap();
|
||||
const auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
|
||||
|
||||
for (int field_index = 0; field_index < action_value_desc->field_count(); ++field_index) {
|
||||
const auto* field_desc = action_value_desc->field(field_index);
|
||||
CheckInternal(function_map->find(field_desc->number()) != function_map->end(),
|
||||
"Missing impl for function " + field_desc->camelcase_name());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the field descriptor for the oneof field in the action message. If no oneof field is set,
|
||||
// return nullptr.
|
||||
template <typename Action>
|
||||
const google::protobuf::FieldDescriptor* GetValueFieldDescriptor(
|
||||
const typename Action::Proto& action_proto) {
|
||||
static auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
|
||||
|
||||
auto* action_refl = Action::Proto::GetReflection();
|
||||
if (!action_refl->HasOneof(action_proto, action_value_desc)) {
|
||||
return nullptr;
|
||||
}
|
||||
return action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc);
|
||||
}
|
||||
|
||||
template <typename Action>
|
||||
void ExecuteActionProto(typename Action::ClassType* module,
|
||||
const typename Action::Proto& action_proto) {
|
||||
const auto* field_desc = GetValueFieldDescriptor<Action>(action_proto);
|
||||
if (field_desc == nullptr) return;
|
||||
auto number = field_desc->number();
|
||||
const auto& map = *Action::GetFunctionMap();
|
||||
auto it = map.find(number);
|
||||
CheckInternal(it != map.end(), "Missing impl for function " + field_desc->camelcase_name());
|
||||
const auto& func = it->second;
|
||||
func(module, action_proto, field_desc);
|
||||
}
|
||||
|
||||
template <typename Action>
|
||||
void ExecuteAllActionProtos(
|
||||
typename Action::ClassType* module,
|
||||
const google::protobuf::RepeatedPtrField<typename Action::Proto>& action_protos) {
|
||||
for (const auto& proto : action_protos) {
|
||||
ExecuteActionProto<Action>(module, proto);
|
||||
}
|
||||
}
|
||||
|
||||
// Safely cast message to T. Returns a pointer to message if cast successfully, otherwise nullptr.
|
||||
template <typename T>
|
||||
const T* SafeCast(const google::protobuf::Message& message) {
|
||||
if (message.GetDescriptor() != T::GetDescriptor()) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const T*>(&message);
|
||||
}
|
||||
|
||||
// Cast message to const T&. Abort if type mismatch.
|
||||
template <typename T>
|
||||
const T& CheckedCast(const google::protobuf::Message& message) {
|
||||
const auto* ptr = SafeCast<T>(message);
|
||||
CheckInternal(ptr, "Cannot cast " + message.GetDescriptor()->name() + " to " +
|
||||
T::GetDescriptor()->name());
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
// A templated way to a primitive field from a message using reflection.
|
||||
template <typename T>
|
||||
struct PrimitiveGetter;
|
||||
#define FUZZ_DEFINE_PRIMITIVE_GETTER(type, func_name) \
|
||||
template <> \
|
||||
struct PrimitiveGetter<type> { \
|
||||
static constexpr const auto fp = &google::protobuf::Reflection::func_name; \
|
||||
}
|
||||
|
||||
FUZZ_DEFINE_PRIMITIVE_GETTER(bool, GetBool);
|
||||
FUZZ_DEFINE_PRIMITIVE_GETTER(uint32_t, GetUInt32);
|
||||
FUZZ_DEFINE_PRIMITIVE_GETTER(int32_t, GetInt32);
|
||||
FUZZ_DEFINE_PRIMITIVE_GETTER(uint64_t, GetUInt64);
|
||||
FUZZ_DEFINE_PRIMITIVE_GETTER(int64_t, GetInt64);
|
||||
FUZZ_DEFINE_PRIMITIVE_GETTER(double, GetDouble);
|
||||
FUZZ_DEFINE_PRIMITIVE_GETTER(float, GetFloat);
|
||||
|
||||
// ActionPerformer extracts arguments from the protobuf message, and then call FuzzFunction
|
||||
// with these arguments.
|
||||
template <typename FuzzFunction, typename Signature, typename Enabled = void>
|
||||
struct ActionPerformerImpl; // undefined
|
||||
|
||||
template <typename FuzzFunction, typename MessageProto>
|
||||
struct ActionPerformerImpl<
|
||||
FuzzFunction, void(const MessageProto&),
|
||||
typename std::enable_if_t<std::is_base_of_v<google::protobuf::Message, MessageProto>>> {
|
||||
static typename FuzzFunction::ReturnType Invoke(
|
||||
typename FuzzFunction::ClassType* module, const google::protobuf::Message& action_proto,
|
||||
const google::protobuf::FieldDescriptor* field_desc) {
|
||||
const MessageProto& arg = CheckedCast<std::remove_reference_t<MessageProto>>(
|
||||
action_proto.GetReflection()->GetMessage(action_proto, field_desc));
|
||||
return FuzzFunction::ImplBody(module, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FuzzFunction, typename Primitive>
|
||||
struct ActionPerformerImpl<FuzzFunction, void(Primitive),
|
||||
typename std::enable_if_t<std::is_arithmetic_v<Primitive>>> {
|
||||
static typename FuzzFunction::ReturnType Invoke(
|
||||
typename FuzzFunction::ClassType* module, const google::protobuf::Message& action_proto,
|
||||
const google::protobuf::FieldDescriptor* field_desc) {
|
||||
Primitive arg = std::invoke(PrimitiveGetter<Primitive>::fp, action_proto.GetReflection(),
|
||||
action_proto, field_desc);
|
||||
return FuzzFunction::ImplBody(module, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FuzzFunction>
|
||||
struct ActionPerformerImpl<FuzzFunction, void()> {
|
||||
static typename FuzzFunction::ReturnType Invoke(typename FuzzFunction::ClassType* module,
|
||||
const google::protobuf::Message&,
|
||||
const google::protobuf::FieldDescriptor*) {
|
||||
return FuzzFunction::ImplBody(module);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FuzzFunction>
|
||||
struct ActionPerformerImpl<FuzzFunction, void(const std::string&)> {
|
||||
static typename FuzzFunction::ReturnType Invoke(
|
||||
typename FuzzFunction::ClassType* module, const google::protobuf::Message& action_proto,
|
||||
const google::protobuf::FieldDescriptor* field_desc) {
|
||||
std::string scratch;
|
||||
const std::string& arg = action_proto.GetReflection()->GetStringReference(
|
||||
action_proto, field_desc, &scratch);
|
||||
return FuzzFunction::ImplBody(module, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FuzzFunction>
|
||||
struct ActionPerformer : ActionPerformerImpl<FuzzFunction, typename FuzzFunction::Signature> {};
|
||||
|
||||
} // namespace android::fuzz
|
||||
|
||||
// Fuzz existing C++ class, ClassType, with a collection of functions under the name Action.
|
||||
//
|
||||
// Prerequisite: ActionProto must be defined in Protobuf to describe possible actions:
|
||||
// message FooActionProto {
|
||||
// message NoArgs {}
|
||||
// oneof value {
|
||||
// bool do_foo = 1;
|
||||
// NoArgs do_bar = 1;
|
||||
// }
|
||||
// }
|
||||
// Use it to fuzz a C++ class Foo by doing the following:
|
||||
// FUZZ_CLASS(Foo, FooAction)
|
||||
// After linking functions of Foo to FooAction, execute all actions by:
|
||||
// FooAction::ExecuteAll(foo_object, action_protos)
|
||||
#define FUZZ_CLASS(Class, Action) \
|
||||
class Action { \
|
||||
public: \
|
||||
using Proto = Action##Proto; \
|
||||
using ClassType = Class; \
|
||||
using FunctionMap = android::fuzz::FunctionMap<Class>; \
|
||||
static FunctionMap* GetFunctionMap() { \
|
||||
static Action::FunctionMap map; \
|
||||
return ↦ \
|
||||
} \
|
||||
static void ExecuteAll(Class* module, \
|
||||
const google::protobuf::RepeatedPtrField<Proto>& action_protos) { \
|
||||
[[maybe_unused]] static int consistent = android::fuzz::CheckConsistency<Action>(); \
|
||||
android::fuzz::ExecuteAllActionProtos<Action>(module, action_protos); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) Action##_##FunctionName
|
||||
#define FUZZ_FUNCTION_TAG_NAME(FunctionName) k##FunctionName
|
||||
|
||||
// Implement an action defined in protobuf. Example:
|
||||
// message FooActionProto {
|
||||
// oneof value {
|
||||
// bool do_foo = 1;
|
||||
// }
|
||||
// }
|
||||
// class Foo { public: void DoAwesomeFoo(bool arg); };
|
||||
// FUZZ_OBJECT(FooAction, Foo);
|
||||
// FUZZ_FUNCTION(FooAction, DoFoo, void, IFoo* module, bool arg) {
|
||||
// module->DoAwesomeFoo(arg);
|
||||
// }
|
||||
// The name DoFoo is the camel case name of the action in protobuf definition of FooActionProto.
|
||||
#define FUZZ_FUNCTION(Action, FunctionName, Return, ModuleArg, ...) \
|
||||
class FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) { \
|
||||
public: \
|
||||
using ActionType = Action; \
|
||||
using ClassType = Action::ClassType; \
|
||||
using ReturnType = Return; \
|
||||
using Signature = void(__VA_ARGS__); \
|
||||
static constexpr const char name[] = #FunctionName; \
|
||||
static constexpr const auto tag = \
|
||||
Action::Proto::ValueCase::FUZZ_FUNCTION_TAG_NAME(FunctionName); \
|
||||
static ReturnType ImplBody(ModuleArg, ##__VA_ARGS__); \
|
||||
\
|
||||
private: \
|
||||
static bool registered_; \
|
||||
}; \
|
||||
auto FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::registered_ = ([] { \
|
||||
auto tag = FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::tag; \
|
||||
auto func = &::android::fuzz::ActionPerformer<FUZZ_FUNCTION_CLASS_NAME( \
|
||||
Action, FunctionName)>::Invoke; \
|
||||
Action::GetFunctionMap()->CheckEmplace(tag, func); \
|
||||
return true; \
|
||||
})(); \
|
||||
Return FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::ImplBody(ModuleArg, ##__VA_ARGS__)
|
||||
|
||||
// Implement a simple action by linking it to the function with the same name. Example:
|
||||
// message FooActionProto {
|
||||
// message NoArgs {}
|
||||
// oneof value {
|
||||
// NoArgs do_bar = 1;
|
||||
// }
|
||||
// }
|
||||
// class Foo { public void DoBar(); };
|
||||
// FUZZ_OBJECT(FooAction, Foo);
|
||||
// FUZZ_FUNCTION(FooAction, DoBar);
|
||||
// The name DoBar is the camel case name of the action in protobuf definition of FooActionProto, and
|
||||
// also the name of the function of Foo.
|
||||
#define FUZZ_SIMPLE_FUNCTION(Action, FunctionName) \
|
||||
FUZZ_FUNCTION(Action, FunctionName, \
|
||||
decltype(std::declval<Action::ClassType>().FunctionName()), \
|
||||
Action::ClassType* module) { \
|
||||
return module->FunctionName(); \
|
||||
}
|
|
@ -2901,6 +2901,20 @@ std::ostream& operator<<(std::ostream& os, UpdateState state) {
|
|||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, MergePhase phase) {
|
||||
switch (phase) {
|
||||
case MergePhase::NO_MERGE:
|
||||
return os << "none";
|
||||
case MergePhase::FIRST_PHASE:
|
||||
return os << "first";
|
||||
case MergePhase::SECOND_PHASE:
|
||||
return os << "second";
|
||||
default:
|
||||
LOG(ERROR) << "Unknown merge phase: " << static_cast<uint32_t>(phase);
|
||||
return os << "unknown(" << static_cast<uint32_t>(phase) << ")";
|
||||
}
|
||||
}
|
||||
|
||||
UpdateState SnapshotManager::ReadUpdateState(LockedFile* lock) {
|
||||
SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock);
|
||||
return status.state();
|
||||
|
@ -3761,7 +3775,7 @@ bool SnapshotManager::Dump(std::ostream& os) {
|
|||
|
||||
auto update_status = ReadSnapshotUpdateStatus(file.get());
|
||||
|
||||
ss << "Update state: " << ReadUpdateState(file.get()) << std::endl;
|
||||
ss << "Update state: " << update_status.state() << std::endl;
|
||||
ss << "Using snapuserd: " << update_status.using_snapuserd() << std::endl;
|
||||
ss << "Using userspace snapshots: " << update_status.userspace_snapshots() << std::endl;
|
||||
ss << "Using io_uring: " << update_status.io_uring_enabled() << std::endl;
|
||||
|
@ -3776,6 +3790,17 @@ bool SnapshotManager::Dump(std::ostream& os) {
|
|||
<< std::endl;
|
||||
ss << "Source build fingerprint: " << update_status.source_build_fingerprint() << std::endl;
|
||||
|
||||
if (update_status.state() == UpdateState::Merging) {
|
||||
ss << "Merge completion: ";
|
||||
if (!EnsureSnapuserdConnected()) {
|
||||
ss << "N/A";
|
||||
} else {
|
||||
ss << snapuserd_client_->GetMergePercent() << "%";
|
||||
}
|
||||
ss << std::endl;
|
||||
ss << "Merge phase: " << update_status.merge_phase() << std::endl;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
std::vector<std::string> snapshots;
|
||||
if (!ListSnapshots(file.get(), &snapshots)) {
|
||||
|
@ -3798,6 +3823,7 @@ bool SnapshotManager::Dump(std::ostream& os) {
|
|||
ss << " allocated sectors: " << status.sectors_allocated() << std::endl;
|
||||
ss << " metadata sectors: " << status.metadata_sectors() << std::endl;
|
||||
ss << " compression: " << status.compression_algorithm() << std::endl;
|
||||
ss << " merge phase: " << DecideMergePhase(status) << std::endl;
|
||||
}
|
||||
os << ss.rdbuf();
|
||||
return ok;
|
||||
|
|
|
@ -1,352 +0,0 @@
|
|||
// 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 <android-base/properties.h>
|
||||
#include <android-base/result.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <src/libfuzzer/libfuzzer_macro.h>
|
||||
#include <storage_literals/storage_literals.h>
|
||||
|
||||
#include "fuzz_utils.h"
|
||||
#include "snapshot_fuzz_utils.h"
|
||||
|
||||
using android::base::Error;
|
||||
using android::base::GetBoolProperty;
|
||||
using android::base::LogId;
|
||||
using android::base::LogSeverity;
|
||||
using android::base::ReadFileToString;
|
||||
using android::base::Result;
|
||||
using android::base::SetLogger;
|
||||
using android::base::StderrLogger;
|
||||
using android::base::StdioLogger;
|
||||
using android::fs_mgr::CreateLogicalPartitionParams;
|
||||
using android::fuzz::CheckedCast;
|
||||
using android::snapshot::SnapshotFuzzData;
|
||||
using android::snapshot::SnapshotFuzzEnv;
|
||||
using chromeos_update_engine::DeltaArchiveManifest;
|
||||
using google::protobuf::FieldDescriptor;
|
||||
using google::protobuf::Message;
|
||||
using google::protobuf::RepeatedPtrField;
|
||||
|
||||
// Avoid linking to libgsi since it needs disk I/O.
|
||||
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 {
|
||||
|
||||
const SnapshotFuzzData* current_data = nullptr;
|
||||
const SnapshotTestModule* current_module = nullptr;
|
||||
|
||||
SnapshotFuzzEnv* GetSnapshotFuzzEnv();
|
||||
|
||||
FUZZ_CLASS(ISnapshotManager, SnapshotManagerAction);
|
||||
|
||||
using ProcessUpdateStateArgs = SnapshotManagerAction::Proto::ProcessUpdateStateArgs;
|
||||
using CreateLogicalAndSnapshotPartitionsArgs =
|
||||
SnapshotManagerAction::Proto::CreateLogicalAndSnapshotPartitionsArgs;
|
||||
using RecoveryCreateSnapshotDevicesArgs =
|
||||
SnapshotManagerAction::Proto::RecoveryCreateSnapshotDevicesArgs;
|
||||
|
||||
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, BeginUpdate);
|
||||
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, CancelUpdate);
|
||||
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, InitiateMerge);
|
||||
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, NeedSnapshotsInFirstStageMount);
|
||||
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, RecoveryCreateSnapshotDevices);
|
||||
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, EnsureMetadataMounted);
|
||||
FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, GetSnapshotMergeStatsInstance);
|
||||
|
||||
#define SNAPSHOT_FUZZ_FUNCTION(FunctionName, ReturnType, ...) \
|
||||
FUZZ_FUNCTION(SnapshotManagerAction, FunctionName, ReturnType, ISnapshotManager* snapshot, \
|
||||
##__VA_ARGS__)
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool, bool wipe) {
|
||||
return snapshot->FinishedSnapshotWrites(wipe);
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, bool, const ProcessUpdateStateArgs& args) {
|
||||
std::function<bool()> before_cancel;
|
||||
if (args.has_before_cancel()) {
|
||||
before_cancel = [&]() { return args.fail_before_cancel(); };
|
||||
}
|
||||
return snapshot->ProcessUpdateState({}, before_cancel);
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, UpdateState, bool has_progress_arg) {
|
||||
double progress;
|
||||
return snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr);
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool, bool has_callback) {
|
||||
std::function<void()> callback;
|
||||
if (has_callback) {
|
||||
callback = []() {};
|
||||
}
|
||||
return snapshot->HandleImminentDataWipe(callback);
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(Dump, bool) {
|
||||
std::stringstream ss;
|
||||
return snapshot->Dump(ss);
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(CreateUpdateSnapshots, bool, const DeltaArchiveManifest& manifest) {
|
||||
return snapshot->CreateUpdateSnapshots(manifest);
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(UnmapUpdateSnapshot, bool, const std::string& name) {
|
||||
return snapshot->UnmapUpdateSnapshot(name);
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions, bool,
|
||||
const CreateLogicalAndSnapshotPartitionsArgs& args) {
|
||||
const std::string* super;
|
||||
if (args.use_correct_super()) {
|
||||
super = &GetSnapshotFuzzEnv()->super();
|
||||
} else {
|
||||
super = &args.super();
|
||||
}
|
||||
return snapshot->CreateLogicalAndSnapshotPartitions(
|
||||
*super, std::chrono::milliseconds(args.timeout_millis()));
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(RecoveryCreateSnapshotDevicesWithMetadata, CreateResult,
|
||||
const RecoveryCreateSnapshotDevicesArgs& args) {
|
||||
std::unique_ptr<AutoDevice> device;
|
||||
if (args.has_metadata_device_object()) {
|
||||
device = std::make_unique<NoOpAutoDevice>(args.metadata_mounted());
|
||||
}
|
||||
return snapshot->RecoveryCreateSnapshotDevices(device);
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, bool,
|
||||
const CreateLogicalPartitionParamsProto& params_proto) {
|
||||
auto partition_opener = std::make_unique<TestPartitionOpener>(GetSnapshotFuzzEnv()->super());
|
||||
CreateLogicalPartitionParams params;
|
||||
if (params_proto.use_correct_super()) {
|
||||
params.block_device = GetSnapshotFuzzEnv()->super();
|
||||
} else {
|
||||
params.block_device = params_proto.block_device();
|
||||
}
|
||||
if (params_proto.has_metadata_slot()) {
|
||||
params.metadata_slot = params_proto.metadata_slot();
|
||||
}
|
||||
params.partition_name = params_proto.partition_name();
|
||||
params.force_writable = params_proto.force_writable();
|
||||
params.timeout_ms = std::chrono::milliseconds(params_proto.timeout_millis());
|
||||
params.device_name = params_proto.device_name();
|
||||
params.partition_opener = partition_opener.get();
|
||||
std::string path;
|
||||
return snapshot->MapUpdateSnapshot(params, &path);
|
||||
}
|
||||
|
||||
SNAPSHOT_FUZZ_FUNCTION(SwitchSlot, void) {
|
||||
(void)snapshot;
|
||||
CHECK(current_module != nullptr);
|
||||
CHECK(current_module->device_info != nullptr);
|
||||
current_module->device_info->SwitchSlot();
|
||||
}
|
||||
|
||||
// During global init, log all messages to stdio. This is only done once.
|
||||
int AllowLoggingDuringGlobalInit() {
|
||||
SetLogger(&StdioLogger);
|
||||
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);
|
||||
|
||||
// If test fails by a LOG(FATAL) or CHECK(), log the corpus. If it abort()'s, there's
|
||||
// nothing else we can do.
|
||||
StderrLogger(logid, severity, tag, __FILE__, __LINE__,
|
||||
"Attempting to dump current corpus:");
|
||||
if (current_data == nullptr) {
|
||||
StderrLogger(logid, severity, tag, __FILE__, __LINE__, "Current corpus is nullptr.");
|
||||
} else {
|
||||
std::string content;
|
||||
if (!google::protobuf::TextFormat::PrintToString(*current_data, &content)) {
|
||||
StderrLogger(logid, severity, tag, __FILE__, __LINE__,
|
||||
"Failed to print corpus to string.");
|
||||
} else {
|
||||
StderrLogger(logid, severity, tag, __FILE__, __LINE__, content.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stop logging (except fatal messages) after global initialization. This is only done once.
|
||||
int StopLoggingAfterGlobalInit() {
|
||||
(void)GetSnapshotFuzzEnv();
|
||||
[[maybe_unused]] static protobuf_mutator::protobuf::LogSilencer log_silencer;
|
||||
SetLogger(&FatalOnlyLogger);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SnapshotFuzzEnv* GetSnapshotFuzzEnv() {
|
||||
[[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit();
|
||||
static SnapshotFuzzEnv env;
|
||||
return &env;
|
||||
}
|
||||
|
||||
SnapshotTestModule SetUpTest(const SnapshotFuzzData& snapshot_fuzz_data) {
|
||||
current_data = &snapshot_fuzz_data;
|
||||
|
||||
auto env = GetSnapshotFuzzEnv();
|
||||
env->CheckSoftReset();
|
||||
|
||||
auto test_module = env->CheckCreateSnapshotManager(snapshot_fuzz_data);
|
||||
current_module = &test_module;
|
||||
CHECK(test_module.snapshot);
|
||||
return test_module;
|
||||
}
|
||||
|
||||
void TearDownTest() {
|
||||
current_module = nullptr;
|
||||
current_data = nullptr;
|
||||
}
|
||||
|
||||
} // namespace android::snapshot
|
||||
|
||||
DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) {
|
||||
using namespace android::snapshot;
|
||||
|
||||
[[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit();
|
||||
auto test_module = SetUpTest(snapshot_fuzz_data);
|
||||
SnapshotManagerAction::ExecuteAll(test_module.snapshot.get(), snapshot_fuzz_data.actions());
|
||||
TearDownTest();
|
||||
}
|
||||
|
||||
namespace android::snapshot {
|
||||
|
||||
// Work-around to cast a 'void' value to Result<void>.
|
||||
template <typename T>
|
||||
struct GoodResult {
|
||||
template <typename F>
|
||||
static Result<T> Cast(F&& f) {
|
||||
return f();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct GoodResult<void> {
|
||||
template <typename F>
|
||||
static Result<void> Cast(F&& f) {
|
||||
f();
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class LibsnapshotFuzzerTest : public ::testing::Test {
|
||||
protected:
|
||||
static void SetUpTestCase() {
|
||||
// Do initialization once.
|
||||
(void)GetSnapshotFuzzEnv();
|
||||
}
|
||||
void SetUp() override {
|
||||
bool is_virtual_ab = GetBoolProperty("ro.virtual_ab.enabled", false);
|
||||
if (!is_virtual_ab) GTEST_SKIP() << "Test only runs on Virtual A/B devices.";
|
||||
}
|
||||
void SetUpFuzzData(const std::string& fn) {
|
||||
auto path = android::base::GetExecutableDirectory() + "/corpus/"s + fn;
|
||||
std::string proto_text;
|
||||
ASSERT_TRUE(ReadFileToString(path, &proto_text));
|
||||
snapshot_fuzz_data_ = std::make_unique<SnapshotFuzzData>();
|
||||
ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(proto_text,
|
||||
snapshot_fuzz_data_.get()));
|
||||
test_module_ = android::snapshot::SetUpTest(*snapshot_fuzz_data_);
|
||||
}
|
||||
void TearDown() override { android::snapshot::TearDownTest(); }
|
||||
template <typename FuzzFunction>
|
||||
Result<typename FuzzFunction::ReturnType> Execute(int action_index) {
|
||||
if (action_index >= snapshot_fuzz_data_->actions_size()) {
|
||||
return Error() << "Index " << action_index << " is out of bounds ("
|
||||
<< snapshot_fuzz_data_->actions_size() << " actions in corpus";
|
||||
}
|
||||
const auto& action_proto = snapshot_fuzz_data_->actions(action_index);
|
||||
const auto* field_desc =
|
||||
android::fuzz::GetValueFieldDescriptor<typename FuzzFunction::ActionType>(
|
||||
action_proto);
|
||||
if (field_desc == nullptr) {
|
||||
return Error() << "Action at index " << action_index << " has no value defined.";
|
||||
}
|
||||
if (FuzzFunction::tag != field_desc->number()) {
|
||||
return Error() << "Action at index " << action_index << " is expected to be "
|
||||
<< FuzzFunction::name << ", but it is " << field_desc->name()
|
||||
<< " in corpus.";
|
||||
}
|
||||
return GoodResult<typename FuzzFunction::ReturnType>::Cast([&]() {
|
||||
return android::fuzz::ActionPerformer<FuzzFunction>::Invoke(test_module_.snapshot.get(),
|
||||
action_proto, field_desc);
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<SnapshotFuzzData> snapshot_fuzz_data_;
|
||||
SnapshotTestModule test_module_;
|
||||
};
|
||||
|
||||
#define SNAPSHOT_FUZZ_FN_NAME(name) FUZZ_FUNCTION_CLASS_NAME(SnapshotManagerAction, name)
|
||||
|
||||
MATCHER_P(ResultIs, expected, "") {
|
||||
if (!arg.ok()) {
|
||||
*result_listener << arg.error();
|
||||
return false;
|
||||
}
|
||||
*result_listener << "expected: " << expected;
|
||||
return arg.value() == expected;
|
||||
}
|
||||
|
||||
#define ASSERT_RESULT_TRUE(actual) ASSERT_THAT(actual, ResultIs(true))
|
||||
|
||||
// Check that launch_device.txt is executed correctly.
|
||||
TEST_F(LibsnapshotFuzzerTest, LaunchDevice) {
|
||||
SetUpFuzzData("launch_device.txt");
|
||||
|
||||
int i = 0;
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(BeginUpdate)>(i++));
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(CreateUpdateSnapshots)>(i++));
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "sys_b";
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "vnd_b";
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "prd_b";
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(FinishedSnapshotWrites)>(i++));
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "sys_b";
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "vnd_b";
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "prd_b";
|
||||
ASSERT_RESULT_OK(Execute<SNAPSHOT_FUZZ_FN_NAME(SwitchSlot)>(i++));
|
||||
ASSERT_EQ("_b", test_module_.device_info->GetSlotSuffix());
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(NeedSnapshotsInFirstStageMount)>(i++));
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(CreateLogicalAndSnapshotPartitions)>(i++));
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(InitiateMerge)>(i++));
|
||||
ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(ProcessUpdateState)>(i++));
|
||||
ASSERT_EQ(i, snapshot_fuzz_data_->actions_size()) << "Not all actions are executed.";
|
||||
}
|
||||
|
||||
} // namespace android::snapshot
|
|
@ -1,513 +0,0 @@
|
|||
// 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 <chrono>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/properties.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <fs_mgr.h>
|
||||
#include <libsnapshot/auto_device.h>
|
||||
#include <libsnapshot/snapshot.h>
|
||||
#include <storage_literals/storage_literals.h>
|
||||
|
||||
#include "snapshot_fuzz_utils.h"
|
||||
#include "utility.h"
|
||||
|
||||
// Prepends the errno string, but it is good enough.
|
||||
#ifndef PCHECK
|
||||
#define PCHECK(x) CHECK(x) << strerror(errno) << ": "
|
||||
#endif
|
||||
|
||||
using namespace android::storage_literals;
|
||||
using namespace std::chrono_literals;
|
||||
using namespace std::string_literals;
|
||||
|
||||
using android::base::Basename;
|
||||
using android::base::ReadFileToString;
|
||||
using android::base::SetProperty;
|
||||
using android::base::Split;
|
||||
using android::base::StartsWith;
|
||||
using android::base::StringPrintf;
|
||||
using android::base::unique_fd;
|
||||
using android::base::WriteStringToFile;
|
||||
using android::dm::DeviceMapper;
|
||||
using android::dm::DmTarget;
|
||||
using android::dm::LoopControl;
|
||||
using android::fiemap::IImageManager;
|
||||
using android::fiemap::ImageManager;
|
||||
using android::fs_mgr::BlockDeviceInfo;
|
||||
using android::fs_mgr::FstabEntry;
|
||||
using android::fs_mgr::IPartitionOpener;
|
||||
using chromeos_update_engine::DynamicPartitionMetadata;
|
||||
|
||||
static const char MNT_DIR[] = "/mnt";
|
||||
static const char BLOCK_SYSFS[] = "/sys/block";
|
||||
|
||||
static const char FAKE_ROOT_NAME[] = "snapshot_fuzz";
|
||||
static const auto SUPER_IMAGE_SIZE = 16_MiB;
|
||||
static const auto DATA_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;
|
||||
}
|
||||
|
||||
std::string GetLinearBaseDeviceString(const DeviceMapper::TargetInfo& target) {
|
||||
if (target.spec.target_type != "linear"s) return {};
|
||||
auto tokens = Split(target.data, " ");
|
||||
CHECK_EQ(2, tokens.size());
|
||||
return tokens[0];
|
||||
}
|
||||
|
||||
std::vector<std::string> GetSnapshotBaseDeviceStrings(const DeviceMapper::TargetInfo& target) {
|
||||
if (target.spec.target_type != "snapshot"s && target.spec.target_type != "snapshot-merge"s)
|
||||
return {};
|
||||
auto tokens = Split(target.data, " ");
|
||||
CHECK_EQ(4, tokens.size());
|
||||
return {tokens[0], tokens[1]};
|
||||
}
|
||||
|
||||
bool ShouldDeleteLoopDevice(const std::string& node) {
|
||||
std::string backing_file;
|
||||
if (ReadFileToString(StringPrintf("%s/loop/backing_file", node.data()), &backing_file)) {
|
||||
if (StartsWith(backing_file, std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<DeviceMapper::TargetInfo> GetTableInfoIfExists(const std::string& dev_name) {
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
std::vector<DeviceMapper::TargetInfo> table;
|
||||
if (!dm.GetTableInfo(dev_name, &table)) {
|
||||
PCHECK(errno == ENODEV || errno == ENXIO);
|
||||
return {};
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
std::set<std::string> GetAllBaseDeviceStrings(const std::string& child_dev) {
|
||||
std::set<std::string> ret;
|
||||
for (const auto& child_target : GetTableInfoIfExists(child_dev)) {
|
||||
auto snapshot_bases = GetSnapshotBaseDeviceStrings(child_target);
|
||||
ret.insert(snapshot_bases.begin(), snapshot_bases.end());
|
||||
|
||||
auto linear_base = GetLinearBaseDeviceString(child_target);
|
||||
if (!linear_base.empty()) {
|
||||
ret.insert(linear_base);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
using PropertyList = std::set<std::string>;
|
||||
void InsertProperty(const char* key, const char* /*name*/, void* cookie) {
|
||||
reinterpret_cast<PropertyList*>(cookie)->insert(key);
|
||||
}
|
||||
|
||||
// Attempt to delete all devices that is based on dev_name, including itself.
|
||||
void CheckDeleteDeviceMapperTree(const std::string& dev_name, bool known_allow_delete = false,
|
||||
uint64_t depth = 100) {
|
||||
CHECK(depth > 0) << "Reaching max depth when deleting " << dev_name
|
||||
<< ". There may be devices referencing itself. Check `dmctl list devices -v`.";
|
||||
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
auto table = GetTableInfoIfExists(dev_name);
|
||||
if (table.empty()) {
|
||||
PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!known_allow_delete) {
|
||||
for (const auto& target : table) {
|
||||
auto base_device_string = GetLinearBaseDeviceString(target);
|
||||
if (base_device_string.empty()) continue;
|
||||
if (ShouldDeleteLoopDevice(
|
||||
StringPrintf("/sys/dev/block/%s", base_device_string.data()))) {
|
||||
known_allow_delete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!known_allow_delete) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string dev_string;
|
||||
PCHECK(dm.GetDeviceString(dev_name, &dev_string));
|
||||
|
||||
std::vector<DeviceMapper::DmBlockDevice> devices;
|
||||
PCHECK(dm.GetAvailableDevices(&devices));
|
||||
for (const auto& child_dev : devices) {
|
||||
auto child_bases = GetAllBaseDeviceStrings(child_dev.name());
|
||||
if (child_bases.find(dev_string) != child_bases.end()) {
|
||||
CheckDeleteDeviceMapperTree(child_dev.name(), true /* known_allow_delete */, depth - 1);
|
||||
}
|
||||
}
|
||||
|
||||
PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
|
||||
}
|
||||
|
||||
// Attempt to clean up residues from previous runs.
|
||||
void CheckCleanupDeviceMapperDevices() {
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
std::vector<DeviceMapper::DmBlockDevice> devices;
|
||||
PCHECK(dm.GetAvailableDevices(&devices));
|
||||
|
||||
for (const auto& dev : devices) {
|
||||
CheckDeleteDeviceMapperTree(dev.name());
|
||||
}
|
||||
}
|
||||
|
||||
void CheckUmount(const std::string& path) {
|
||||
PCHECK(TEMP_FAILURE_RETRY(umount(path.data()) == 0) || errno == ENOENT || errno == EINVAL)
|
||||
<< path;
|
||||
}
|
||||
|
||||
void CheckDetachLoopDevices(const std::set<std::string>& exclude_names = {}) {
|
||||
// ~SnapshotFuzzEnv automatically does the following.
|
||||
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(BLOCK_SYSFS), closedir);
|
||||
PCHECK(dir != nullptr) << BLOCK_SYSFS;
|
||||
LoopControl loop_control;
|
||||
dirent* dp;
|
||||
while ((dp = readdir(dir.get())) != nullptr) {
|
||||
if (exclude_names.find(dp->d_name) != exclude_names.end()) {
|
||||
continue;
|
||||
}
|
||||
if (!ShouldDeleteLoopDevice(StringPrintf("%s/%s", BLOCK_SYSFS, dp->d_name).data())) {
|
||||
continue;
|
||||
}
|
||||
PCHECK(loop_control.Detach(StringPrintf("/dev/block/%s", dp->d_name).data()));
|
||||
}
|
||||
}
|
||||
|
||||
void CheckUmountAll() {
|
||||
CheckUmount(std::string(MNT_DIR) + "/snapshot_fuzz_data");
|
||||
CheckUmount(std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME);
|
||||
}
|
||||
|
||||
class AutoDeleteDir : public AutoDevice {
|
||||
public:
|
||||
static std::unique_ptr<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;
|
||||
PCHECK(rmdir(name_.c_str()) == 0 || errno == ENOENT) << name_;
|
||||
}
|
||||
|
||||
private:
|
||||
AutoDeleteDir(const std::string& path) : AutoDevice(path) {}
|
||||
};
|
||||
|
||||
class AutoUnmount : public AutoDevice {
|
||||
public:
|
||||
~AutoUnmount() {
|
||||
if (!HasDevice()) return;
|
||||
CheckUmount(name_);
|
||||
}
|
||||
AutoUnmount(const std::string& path) : AutoDevice(path) {}
|
||||
};
|
||||
|
||||
class AutoUnmountTmpfs : public AutoUnmount {
|
||||
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));
|
||||
}
|
||||
private:
|
||||
using AutoUnmount::AutoUnmount;
|
||||
};
|
||||
|
||||
// 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_ = AutoUnmountTmpfs::New(ret->mount_path(), size);
|
||||
if (!ret->auto_umount_mount_point_->HasDevice()) {
|
||||
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
|
||||
}
|
||||
// tmp_path() and persist_path does not need to be deleted upon destruction, hence it is
|
||||
// not wrapped with AutoDeleteDir.
|
||||
if (!Mkdir(ret->tmp_path())) {
|
||||
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
|
||||
}
|
||||
if (!Mkdir(ret->persist_path())) {
|
||||
return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
// Return the temporary scratch directory.
|
||||
std::string tmp_path() const {
|
||||
CHECK(HasDevice());
|
||||
return mount_path() + "/tmp";
|
||||
}
|
||||
// Return the temporary scratch directory.
|
||||
std::string persist_path() const {
|
||||
CHECK(HasDevice());
|
||||
return mount_path() + "/persist";
|
||||
}
|
||||
// Delete all contents in tmp_path() and start over. tmp_path() itself is re-created.
|
||||
void CheckSoftReset() {
|
||||
PCHECK(RmdirRecursive(tmp_path()));
|
||||
PCHECK(Mkdir(tmp_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() {
|
||||
CheckCleanupDeviceMapperDevices();
|
||||
CheckDetachLoopDevices();
|
||||
CheckUmountAll();
|
||||
|
||||
fake_root_ = AutoMemBasedDir::New(FAKE_ROOT_NAME, FAKE_ROOT_SIZE);
|
||||
CHECK(fake_root_ != nullptr);
|
||||
CHECK(fake_root_->HasDevice());
|
||||
loop_control_ = std::make_unique<LoopControl>();
|
||||
|
||||
fake_data_mount_point_ = MNT_DIR + "/snapshot_fuzz_data"s;
|
||||
auto_delete_data_mount_point_ = AutoDeleteDir::New(fake_data_mount_point_);
|
||||
CHECK(auto_delete_data_mount_point_ != nullptr);
|
||||
CHECK(auto_delete_data_mount_point_->HasDevice());
|
||||
|
||||
const auto& fake_persist_path = fake_root_->persist_path();
|
||||
mapped_super_ = CheckMapImage(fake_persist_path + "/super.img", SUPER_IMAGE_SIZE,
|
||||
loop_control_.get(), &fake_super_);
|
||||
mapped_data_ = CheckMapImage(fake_persist_path + "/data.img", DATA_IMAGE_SIZE,
|
||||
loop_control_.get(), &fake_data_block_device_);
|
||||
mounted_data_ = CheckMountFormatData(fake_data_block_device_, fake_data_mount_point_);
|
||||
}
|
||||
|
||||
SnapshotFuzzEnv::~SnapshotFuzzEnv() {
|
||||
CheckCleanupDeviceMapperDevices();
|
||||
mounted_data_ = nullptr;
|
||||
auto_delete_data_mount_point_ = nullptr;
|
||||
mapped_data_ = nullptr;
|
||||
mapped_super_ = nullptr;
|
||||
CheckDetachLoopDevices();
|
||||
loop_control_ = nullptr;
|
||||
fake_root_ = nullptr;
|
||||
CheckUmountAll();
|
||||
}
|
||||
|
||||
void CheckZeroFill(const std::string& file, size_t size) {
|
||||
std::string zeros(size, '\0');
|
||||
PCHECK(WriteStringToFile(zeros, file)) << "Cannot write zeros to " << file;
|
||||
}
|
||||
|
||||
void SnapshotFuzzEnv::CheckSoftReset() {
|
||||
fake_root_->CheckSoftReset();
|
||||
CheckZeroFill(super(), SUPER_IMAGE_SIZE);
|
||||
CheckCleanupDeviceMapperDevices();
|
||||
CheckDetachLoopDevices({Basename(fake_super_), Basename(fake_data_block_device_)});
|
||||
}
|
||||
|
||||
std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager() {
|
||||
auto metadata_dir = fake_root_->tmp_path() + "/images_manager_metadata";
|
||||
auto data_dir = fake_data_mount_point_ + "/image_manager_data";
|
||||
PCHECK(Mkdir(metadata_dir));
|
||||
PCHECK(Mkdir(data_dir));
|
||||
return SnapshotFuzzImageManager::Open(metadata_dir, data_dir);
|
||||
}
|
||||
|
||||
// Helper to create a loop device for a file.
|
||||
static void CheckCreateLoopDevice(LoopControl* control, const std::string& file,
|
||||
const std::chrono::milliseconds& timeout_ms, std::string* path) {
|
||||
static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
|
||||
android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags));
|
||||
PCHECK(file_fd >= 0) << "Could not open file: " << file;
|
||||
CHECK(control->Attach(file_fd, timeout_ms, path))
|
||||
<< "Could not create loop device for: " << file;
|
||||
}
|
||||
|
||||
class AutoDetachLoopDevice : public AutoDevice {
|
||||
public:
|
||||
AutoDetachLoopDevice(LoopControl* control, const std::string& device)
|
||||
: AutoDevice(device), control_(control) {}
|
||||
~AutoDetachLoopDevice() { PCHECK(control_->Detach(name_)) << name_; }
|
||||
|
||||
private:
|
||||
LoopControl* control_;
|
||||
};
|
||||
|
||||
std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMapImage(const std::string& img_path,
|
||||
uint64_t size, LoopControl* control,
|
||||
std::string* mapped_path) {
|
||||
CheckZeroFill(img_path, size);
|
||||
CheckCreateLoopDevice(control, img_path, 1s, mapped_path);
|
||||
|
||||
return std::make_unique<AutoDetachLoopDevice>(control, *mapped_path);
|
||||
}
|
||||
|
||||
SnapshotTestModule SnapshotFuzzEnv::CheckCreateSnapshotManager(const SnapshotFuzzData& data) {
|
||||
SnapshotTestModule ret;
|
||||
auto partition_opener = std::make_unique<TestPartitionOpener>(super());
|
||||
ret.opener = partition_opener.get();
|
||||
CheckWriteSuperMetadata(data, *partition_opener);
|
||||
auto metadata_dir = fake_root_->tmp_path() + "/snapshot_metadata";
|
||||
PCHECK(Mkdir(metadata_dir));
|
||||
if (data.has_metadata_snapshots_dir()) {
|
||||
PCHECK(Mkdir(metadata_dir + "/snapshots"));
|
||||
}
|
||||
|
||||
ret.device_info = new SnapshotFuzzDeviceInfo(this, data.device_info_data(),
|
||||
std::move(partition_opener), metadata_dir);
|
||||
auto snapshot = SnapshotManager::New(ret.device_info /* takes ownership */);
|
||||
ret.snapshot = std::move(snapshot);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::string& SnapshotFuzzEnv::super() const {
|
||||
return fake_super_;
|
||||
}
|
||||
|
||||
void SnapshotFuzzEnv::CheckWriteSuperMetadata(const SnapshotFuzzData& data,
|
||||
const IPartitionOpener& opener) {
|
||||
if (!data.is_super_metadata_valid()) {
|
||||
// Leave it zero.
|
||||
return;
|
||||
}
|
||||
|
||||
BlockDeviceInfo super_device("super", SUPER_IMAGE_SIZE, 0, 0, 4096);
|
||||
std::vector<BlockDeviceInfo> devices = {super_device};
|
||||
auto builder = MetadataBuilder::New(devices, "super", 65536, 2);
|
||||
CHECK(builder != nullptr);
|
||||
|
||||
// Attempt to create a super partition metadata using proto. All errors are ignored.
|
||||
for (const auto& group_proto : data.super_data().dynamic_partition_metadata().groups()) {
|
||||
(void)builder->AddGroup(group_proto.name(), group_proto.size());
|
||||
for (const auto& partition_name : group_proto.partition_names()) {
|
||||
(void)builder->AddPartition(partition_name, group_proto.name(),
|
||||
LP_PARTITION_ATTR_READONLY);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& partition_proto : data.super_data().partitions()) {
|
||||
auto p = builder->FindPartition(partition_proto.partition_name());
|
||||
if (p == nullptr) continue;
|
||||
(void)builder->ResizePartition(p, partition_proto.new_partition_info().size());
|
||||
}
|
||||
|
||||
auto metadata = builder->Export();
|
||||
// metadata may be nullptr if it is not valid (e.g. partition name too long).
|
||||
// In this case, just use empty super partition data.
|
||||
if (metadata == nullptr) {
|
||||
builder = MetadataBuilder::New(devices, "super", 65536, 2);
|
||||
CHECK(builder != nullptr);
|
||||
metadata = builder->Export();
|
||||
CHECK(metadata != nullptr);
|
||||
}
|
||||
CHECK(FlashPartitionTable(opener, super(), *metadata.get()));
|
||||
}
|
||||
|
||||
std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMountFormatData(const std::string& blk_device,
|
||||
const std::string& mount_point) {
|
||||
FstabEntry entry{
|
||||
.blk_device = blk_device,
|
||||
.length = static_cast<off64_t>(DATA_IMAGE_SIZE),
|
||||
.fs_type = "ext4",
|
||||
.mount_point = mount_point,
|
||||
};
|
||||
CHECK(0 == fs_mgr_do_format(entry));
|
||||
CHECK(0 == fs_mgr_do_mount_one(entry));
|
||||
return std::make_unique<AutoUnmount>(mount_point);
|
||||
}
|
||||
|
||||
SnapshotFuzzImageManager::~SnapshotFuzzImageManager() {
|
||||
// Remove relevant gsid.mapped_images.* props.
|
||||
for (const auto& name : mapped_) {
|
||||
CHECK(UnmapImageIfExists(name)) << "Cannot unmap " << name;
|
||||
}
|
||||
}
|
||||
|
||||
bool SnapshotFuzzImageManager::MapImageDevice(const std::string& name,
|
||||
const std::chrono::milliseconds& timeout_ms,
|
||||
std::string* path) {
|
||||
if (impl_->MapImageDevice(name, timeout_ms, path)) {
|
||||
mapped_.insert(name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace android::snapshot
|
|
@ -1,214 +0,0 @@
|
|||
// 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 <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android/snapshot/snapshot_fuzz.pb.h>
|
||||
#include <libdm/loop_control.h>
|
||||
#include <libfiemap/image_manager.h>
|
||||
#include <liblp/liblp.h>
|
||||
#include <libsnapshot/auto_device.h>
|
||||
#include <libsnapshot/test_helpers.h>
|
||||
|
||||
// libsnapshot-specific code for fuzzing. Defines fake classes that are depended
|
||||
// by SnapshotManager.
|
||||
|
||||
#include "android/snapshot/snapshot_fuzz.pb.h"
|
||||
#include "libsnapshot/snapshot.h"
|
||||
|
||||
namespace android::snapshot {
|
||||
|
||||
class AutoMemBasedDir;
|
||||
class SnapshotFuzzDeviceInfo;
|
||||
|
||||
class NoOpAutoDevice : public AutoDevice {
|
||||
public:
|
||||
NoOpAutoDevice(bool mounted) : AutoDevice(mounted ? "no_op" : "") {}
|
||||
};
|
||||
|
||||
struct SnapshotTestModule {
|
||||
std::unique_ptr<ISnapshotManager> snapshot;
|
||||
SnapshotFuzzDeviceInfo* device_info = nullptr;
|
||||
TestPartitionOpener* opener = nullptr;
|
||||
};
|
||||
|
||||
// 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();
|
||||
|
||||
// Soft reset part of the environment before running the next test.
|
||||
// Abort if fails.
|
||||
void CheckSoftReset();
|
||||
|
||||
// Create a snapshot manager for this test run.
|
||||
// Client is responsible for maintaining the lifetime of |data| over the life time of
|
||||
// ISnapshotManager.
|
||||
SnapshotTestModule CheckCreateSnapshotManager(const SnapshotFuzzData& data);
|
||||
|
||||
std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager();
|
||||
|
||||
// Return path to super partition.
|
||||
const std::string& super() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AutoMemBasedDir> fake_root_;
|
||||
std::unique_ptr<android::dm::LoopControl> loop_control_;
|
||||
std::string fake_data_mount_point_;
|
||||
std::unique_ptr<AutoDevice> auto_delete_data_mount_point_;
|
||||
std::unique_ptr<AutoDevice> mapped_super_;
|
||||
std::string fake_super_;
|
||||
std::unique_ptr<AutoDevice> mapped_data_;
|
||||
std::string fake_data_block_device_;
|
||||
std::unique_ptr<AutoDevice> mounted_data_;
|
||||
|
||||
static std::unique_ptr<AutoDevice> CheckMapImage(const std::string& fake_persist_path,
|
||||
uint64_t size,
|
||||
android::dm::LoopControl* control,
|
||||
std::string* mapped_path);
|
||||
static std::unique_ptr<AutoDevice> CheckMountFormatData(const std::string& blk_device,
|
||||
const std::string& mount_point);
|
||||
|
||||
void CheckWriteSuperMetadata(const SnapshotFuzzData& proto,
|
||||
const android::fs_mgr::IPartitionOpener& opener);
|
||||
};
|
||||
|
||||
class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
|
||||
public:
|
||||
using MergeStatus = ISnapshotManager::IDeviceInfo::MergeStatus;
|
||||
// Client is responsible for maintaining the lifetime of |data|.
|
||||
SnapshotFuzzDeviceInfo(SnapshotFuzzEnv* env, const FuzzDeviceInfoData& data,
|
||||
std::unique_ptr<TestPartitionOpener>&& partition_opener,
|
||||
const std::string& metadata_dir)
|
||||
: env_(env),
|
||||
data_(&data),
|
||||
partition_opener_(std::move(partition_opener)),
|
||||
metadata_dir_(metadata_dir),
|
||||
dm_(android::dm::DeviceMapper::Instance()) {}
|
||||
|
||||
// Following APIs are mocked.
|
||||
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 CurrentSlotIsA() ? "_a" : "_b"; }
|
||||
std::string GetOtherSlotSuffix() const override { return CurrentSlotIsA() ? "_b" : "_a"; }
|
||||
bool IsOverlayfsSetup() const override { return data_->is_overlayfs_setup(); }
|
||||
bool SetBootControlMergeStatus(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(); }
|
||||
bool IsFirstStageInit() const override { return false; }
|
||||
android::dm::IDeviceMapper& GetDeviceMapper() override { return dm_; }
|
||||
std::unique_ptr<IImageManager> OpenImageManager() const {
|
||||
return env_->CheckCreateFakeImageManager();
|
||||
}
|
||||
|
||||
void SwitchSlot() { switched_slot_ = !switched_slot_; }
|
||||
|
||||
private:
|
||||
SnapshotFuzzEnv* env_;
|
||||
const FuzzDeviceInfoData* data_;
|
||||
std::unique_ptr<TestPartitionOpener> partition_opener_;
|
||||
std::string metadata_dir_;
|
||||
bool switched_slot_ = false;
|
||||
android::dm::DeviceMapper& dm_;
|
||||
|
||||
bool CurrentSlotIsA() const { return data_->slot_suffix_is_a() != switched_slot_; }
|
||||
};
|
||||
|
||||
// A spy class on ImageManager implementation. Upon destruction, unmaps all images
|
||||
// map through this object.
|
||||
class SnapshotFuzzImageManager : public android::fiemap::IImageManager {
|
||||
public:
|
||||
static std::unique_ptr<SnapshotFuzzImageManager> Open(const std::string& metadata_dir,
|
||||
const std::string& data_dir) {
|
||||
auto impl = android::fiemap::ImageManager::Open(metadata_dir, data_dir);
|
||||
if (impl == nullptr) return nullptr;
|
||||
return std::unique_ptr<SnapshotFuzzImageManager>(
|
||||
new SnapshotFuzzImageManager(std::move(impl)));
|
||||
}
|
||||
|
||||
~SnapshotFuzzImageManager();
|
||||
|
||||
// Spied APIs.
|
||||
bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
|
||||
std::string* path) override;
|
||||
|
||||
// Other functions call through.
|
||||
android::fiemap::FiemapStatus CreateBackingImage(
|
||||
const std::string& name, uint64_t size, int flags,
|
||||
std::function<bool(uint64_t, uint64_t)>&& on_progress) override {
|
||||
return impl_->CreateBackingImage(name, size, flags, std::move(on_progress));
|
||||
}
|
||||
bool DeleteBackingImage(const std::string& name) override {
|
||||
return impl_->DeleteBackingImage(name);
|
||||
}
|
||||
bool UnmapImageDevice(const std::string& name) override {
|
||||
return impl_->UnmapImageDevice(name);
|
||||
}
|
||||
bool BackingImageExists(const std::string& name) override {
|
||||
return impl_->BackingImageExists(name);
|
||||
}
|
||||
bool IsImageMapped(const std::string& name) override { return impl_->IsImageMapped(name); }
|
||||
bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
|
||||
std::string* dev) override {
|
||||
return impl_->MapImageWithDeviceMapper(opener, name, dev);
|
||||
}
|
||||
bool GetMappedImageDevice(const std::string& name, std::string* device) override {
|
||||
return impl_->GetMappedImageDevice(name, device);
|
||||
}
|
||||
bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override {
|
||||
return impl_->MapAllImages(init);
|
||||
}
|
||||
bool DisableImage(const std::string& name) override { return impl_->DisableImage(name); }
|
||||
bool RemoveDisabledImages() override { return impl_->RemoveDisabledImages(); }
|
||||
std::vector<std::string> GetAllBackingImages() override { return impl_->GetAllBackingImages(); }
|
||||
android::fiemap::FiemapStatus ZeroFillNewImage(const std::string& name,
|
||||
uint64_t bytes) override {
|
||||
return impl_->ZeroFillNewImage(name, bytes);
|
||||
}
|
||||
bool RemoveAllImages() override { return impl_->RemoveAllImages(); }
|
||||
bool UnmapImageIfExists(const std::string& name) override {
|
||||
return impl_->UnmapImageIfExists(name);
|
||||
}
|
||||
bool IsImageDisabled(const std::string& name) override { return impl_->IsImageDisabled(name); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<android::fiemap::IImageManager> impl_;
|
||||
std::set<std::string> mapped_;
|
||||
|
||||
SnapshotFuzzImageManager(std::unique_ptr<android::fiemap::IImageManager>&& impl)
|
||||
: impl_(std::move(impl)) {}
|
||||
};
|
||||
|
||||
} // namespace android::snapshot
|
Loading…
Reference in a new issue