blob: 54c6a005368a8f9ab0998caad8d3470322414a88 [file] [log] [blame] [edit]
// 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