blob: 5a9f391a4ade62949e8ba1754accf713298d94c6 [file] [log] [blame]
/*
* Copyright (C) 2019 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 <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/vfs.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android/os/IVold.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <bootloader_message/bootloader_message.h>
#include <cutils/android_reboot.h>
#include <fs_mgr_overlayfs.h>
#include <fs_mgr_priv.h>
#include <fstab/fstab.h>
#include <libavb_user/libavb_user.h>
#include <libgsi/libgsid.h>
#include "fs_mgr_priv_overlayfs.h"
using namespace std::literals;
using android::fs_mgr::Fstab;
using android::fs_mgr::FstabEntry;
namespace {
void usage() {
const std::string progname = getprogname();
if (progname == "disable-verity" || progname == "enable-verity" ||
progname == "set-verity-state") {
std::cout << "Usage: disable-verity\n"
<< " enable-verity\n"
<< " set-verity-state [0|1]\n"
<< R"(
Options:
-h --help this help
-R --reboot automatic reboot if needed for new settings to take effect
-v --verbose be noisy)"
<< std::endl;
} else {
std::cout << "Usage: " << progname << " [-h] [-R] [-T fstab_file] [partition]...\n"
<< R"(
Options:
-h --help this help
-R --reboot disable verity & reboot to facilitate remount
-v --verbose be noisy
-T --fstab custom fstab file location
partition specific partition(s) (empty does all)
Remount specified partition(s) read-write, by name or mount point.
-R notwithstanding, verity must be disabled on partition(s).
-R within a DSU guest system reboots into the DSU instead of the host system,
this command would enable DSU (one-shot) if not already enabled.)"
<< std::endl;
}
}
const std::string system_mount_point(const android::fs_mgr::FstabEntry& entry) {
if (entry.mount_point == "/") return "/system";
return entry.mount_point;
}
const FstabEntry* GetWrappedEntry(const Fstab& overlayfs_candidates, const FstabEntry& entry) {
auto mount_point = system_mount_point(entry);
auto it = std::find_if(overlayfs_candidates.begin(), overlayfs_candidates.end(),
[&mount_point](const auto& entry) {
return android::base::StartsWith(mount_point,
system_mount_point(entry) + "/");
});
if (it == overlayfs_candidates.end()) return nullptr;
return &(*it);
}
class MyLogger {
public:
explicit MyLogger(bool verbose) : verbose_(verbose) {}
void operator()(android::base::LogId id, android::base::LogSeverity severity, const char* tag,
const char* file, unsigned int line, const char* message) {
// By default, print ERROR logs and logs of this program (does not start with '[')
// Print [libfs_mgr] INFO logs only if -v is given.
if (verbose_ || severity >= android::base::ERROR || message[0] != '[') {
fprintf(stderr, "%s\n", message);
}
logd_(id, severity, tag, file, line, message);
}
private:
android::base::LogdLogger logd_;
bool verbose_;
};
[[noreturn]] void reboot(const std::string& name) {
LOG(INFO) << "Rebooting device for new settings to take effect";
::sync();
android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot," + name);
::sleep(60);
LOG(ERROR) << "Failed to reboot";
::exit(1);
}
static android::sp<android::os::IVold> GetVold() {
while (true) {
if (auto sm = android::defaultServiceManager()) {
if (auto binder = sm->getService(android::String16("vold"))) {
if (auto vold = android::interface_cast<android::os::IVold>(binder)) {
return vold;
}
}
}
std::this_thread::sleep_for(2s);
}
}
static bool ReadFstab(const char* fstab_file, android::fs_mgr::Fstab* fstab) {
if (fstab_file) {
return android::fs_mgr::ReadFstabFromFile(fstab_file, fstab);
}
if (!android::fs_mgr::ReadDefaultFstab(fstab)) {
return false;
}
// Manufacture a / entry from /proc/mounts if missing.
if (!GetEntryForMountPoint(fstab, "/system") && !GetEntryForMountPoint(fstab, "/")) {
android::fs_mgr::Fstab mounts;
if (android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
if (auto entry = GetEntryForMountPoint(&mounts, "/")) {
if (entry->fs_type != "rootfs") fstab->emplace_back(*entry);
}
}
}
return true;
}
bool VerifyCheckpointing() {
if (!android::base::GetBoolProperty("ro.virtual_ab.enabled", false) &&
!android::base::GetBoolProperty("ro.virtual_ab.retrofit", false)) {
return true;
}
// Virtual A/B devices can use /data as backing storage; make sure we're
// not checkpointing.
auto vold = GetVold();
bool checkpointing = false;
if (!vold->isCheckpointing(&checkpointing).isOk()) {
LOG(ERROR) << "Could not determine checkpointing status.";
return false;
}
if (checkpointing) {
LOG(ERROR) << "Cannot use remount when a checkpoint is in progress.";
LOG(ERROR) << "To force end checkpointing, call 'vdc checkpoint commitChanges'";
LOG(ERROR) << "Warning: this can lead to data corruption if rolled back.";
return false;
}
return true;
}
static bool IsRemountable(Fstab& candidates, const FstabEntry& entry) {
if (entry.fs_mgr_flags.vold_managed || entry.fs_mgr_flags.recovery_only ||
entry.fs_mgr_flags.slot_select_other) {
return false;
}
if (!(entry.flags & MS_RDONLY)) {
return false;
}
if (entry.fs_type == "vfat") {
return false;
}
if (auto candidate_entry = GetEntryForMountPoint(&candidates, entry.mount_point)) {
return candidate_entry->fs_type == entry.fs_type;
}
if (GetWrappedEntry(candidates, entry)) {
return false;
}
return true;
}
static Fstab::const_iterator FindPartition(const Fstab& fstab, const std::string& partition) {
Fstab mounts;
if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
LOG(ERROR) << "Failed to read /proc/mounts";
return fstab.end();
}
for (auto iter = fstab.begin(); iter != fstab.end(); iter++) {
const auto mount_point = system_mount_point(*iter);
if (partition == mount_point || partition == android::base::Basename(mount_point)) {
// In case fstab has multiple entries, pick the one that matches the
// actual mounted filesystem type.
auto proc_mount_point = (iter->mount_point == "/system") ? "/" : iter->mount_point;
auto mounted = GetEntryForMountPoint(&mounts, proc_mount_point);
if (mounted && mounted->fs_type == iter->fs_type) {
return iter;
}
}
}
return fstab.end();
}
static Fstab GetAllRemountablePartitions(Fstab& fstab) {
auto candidates = fs_mgr_overlayfs_candidate_list(fstab);
Fstab partitions;
for (const auto& entry : fstab) {
if (IsRemountable(candidates, entry)) {
partitions.emplace_back(entry);
}
}
return partitions;
}
bool GetRemountList(const Fstab& fstab, const std::vector<std::string>& argv, Fstab* partitions) {
auto candidates = fs_mgr_overlayfs_candidate_list(fstab);
for (const auto& arg : argv) {
std::string partition = arg;
if (partition == "/") {
partition = "/system";
}
auto it = FindPartition(fstab, partition);
if (it == fstab.end()) {
LOG(ERROR) << "Unknown partition " << arg;
return false;
}
const FstabEntry* entry = &*it;
if (auto wrap = GetWrappedEntry(candidates, *entry); wrap != nullptr) {
LOG(INFO) << "partition " << arg << " covered by overlayfs for " << wrap->mount_point
<< ", switching";
entry = wrap;
}
// If it's already remounted, include it so it gets gracefully skipped
// later on.
if (!fs_mgr_overlayfs_already_mounted(entry->mount_point) &&
!IsRemountable(candidates, *entry)) {
LOG(ERROR) << "Invalid partition " << arg;
return false;
}
if (GetEntryForMountPoint(partitions, entry->mount_point) != nullptr) {
continue;
}
partitions->emplace_back(*entry);
}
return true;
}
struct RemountCheckResult {
bool reboot_later = false;
bool setup_overlayfs = false;
bool disabled_verity = false;
bool verity_error = false;
bool remounted_anything = false;
};
bool CheckOverlayfs(Fstab* partitions, RemountCheckResult* result) {
bool ok = true;
for (auto it = partitions->begin(); it != partitions->end();) {
auto& entry = *it;
const auto& mount_point = entry.mount_point;
if (fs_mgr_wants_overlayfs(&entry)) {
bool want_reboot = false;
bool force = result->disabled_verity;
if (!fs_mgr_overlayfs_setup(*partitions, mount_point.c_str(), &want_reboot, force)) {
LOG(ERROR) << "Overlayfs setup for " << mount_point << " failed, skipping";
ok = false;
it = partitions->erase(it);
continue;
}
if (want_reboot) {
LOG(INFO) << "Using overlayfs for " << mount_point;
result->reboot_later = true;
result->setup_overlayfs = true;
}
}
it++;
}
return ok;
}
bool EnableDsuIfNeeded() {
auto gsid = android::gsi::GetGsiService();
if (!gsid) {
return true;
}
auto dsu_running = false;
if (auto status = gsid->isGsiRunning(&dsu_running); !status.isOk()) {
LOG(ERROR) << "Failed to get DSU running state: " << status;
return false;
}
auto dsu_enabled = false;
if (auto status = gsid->isGsiEnabled(&dsu_enabled); !status.isOk()) {
LOG(ERROR) << "Failed to get DSU enabled state: " << status;
return false;
}
if (dsu_running && !dsu_enabled) {
std::string dsu_slot;
if (auto status = gsid->getActiveDsuSlot(&dsu_slot); !status.isOk()) {
LOG(ERROR) << "Failed to get active DSU slot: " << status;
return false;
}
LOG(INFO) << "DSU is running but disabled, enable DSU so that we stay within the "
"DSU guest system after reboot";
int error = 0;
if (auto status = gsid->enableGsi(/* oneShot = */ true, dsu_slot, &error); !status.isOk()) {
LOG(ERROR) << "Failed to enable DSU: " << status;
return false;
}
if (error != android::gsi::IGsiService::INSTALL_OK) {
LOG(ERROR) << "Failed to enable DSU, error code: " << error;
return false;
}
LOG(INFO) << "Successfully enabled DSU (one-shot mode)";
}
return true;
}
bool RemountPartition(Fstab& fstab, Fstab& mounts, FstabEntry& entry) {
// unlock the r/o key for the mount point device
if (entry.fs_mgr_flags.logical) {
fs_mgr_update_logical_partition(&entry);
}
auto blk_device = entry.blk_device;
auto mount_point = entry.mount_point;
auto found = false;
for (auto it = mounts.rbegin(); it != mounts.rend(); ++it) {
auto& rentry = *it;
if (mount_point == rentry.mount_point) {
blk_device = rentry.blk_device;
found = true;
break;
}
// Find overlayfs mount point?
if ((mount_point == "/" && rentry.mount_point == "/system") ||
(mount_point == "/system" && rentry.mount_point == "/")) {
blk_device = rentry.blk_device;
mount_point = "/system";
found = true;
break;
}
}
if (!found) {
PLOG(INFO) << "skip unmounted partition dev:" << blk_device << " mnt:" << mount_point;
return true;
}
if (blk_device == "/dev/root") {
auto from_fstab = GetEntryForMountPoint(&fstab, mount_point);
if (from_fstab) blk_device = from_fstab->blk_device;
}
fs_mgr_set_blk_ro(blk_device, false);
// Find system-as-root mount point?
if ((mount_point == "/system") && !GetEntryForMountPoint(&mounts, mount_point) &&
GetEntryForMountPoint(&mounts, "/")) {
mount_point = "/";
}
// Now remount!
for (const auto& mnt_point : {mount_point, entry.mount_point}) {
if (::mount(blk_device.c_str(), mnt_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
nullptr) == 0) {
LOG(INFO) << "Remounted " << mnt_point << " as RW";
return true;
}
if (errno != EINVAL || mount_point == entry.mount_point) {
break;
}
}
PLOG(ERROR) << "failed to remount partition dev:" << blk_device << " mnt:" << mount_point;
return false;
}
struct SetVerityStateResult {
bool success = false;
bool want_reboot = false;
};
SetVerityStateResult SetVerityState(bool enable_verity) {
const auto ab_suffix = android::base::GetProperty("ro.boot.slot_suffix", "");
std::unique_ptr<AvbOps, decltype(&avb_ops_user_free)> ops(avb_ops_user_new(),
&avb_ops_user_free);
if (!ops) {
LOG(ERROR) << "Error getting AVB ops";
return {};
}
if (!avb_user_verity_set(ops.get(), ab_suffix.c_str(), enable_verity)) {
LOG(ERROR) << "Error setting verity state";
return {};
}
bool verification_enabled = false;
if (!avb_user_verification_get(ops.get(), ab_suffix.c_str(), &verification_enabled)) {
LOG(ERROR) << "Error getting verification state";
return {};
}
if (!verification_enabled) {
LOG(WARNING) << "AVB verification is disabled, "
<< (enable_verity ? "enabling" : "disabling")
<< " verity state may have no effect";
return {.success = true, .want_reboot = false};
}
const auto verity_mode = android::base::GetProperty("ro.boot.veritymode", "");
const bool was_enabled = (verity_mode != "disabled");
if ((was_enabled && enable_verity) || (!was_enabled && !enable_verity)) {
LOG(INFO) << "Verity is already " << (enable_verity ? "enabled" : "disabled");
return {.success = true, .want_reboot = false};
}
LOG(INFO) << "Successfully " << (enable_verity ? "enabled" : "disabled") << " verity";
return {.success = true, .want_reboot = true};
}
bool SetupOrTeardownOverlayfs(bool enable) {
bool want_reboot = false;
if (enable) {
Fstab fstab;
if (!ReadDefaultFstab(&fstab)) {
LOG(ERROR) << "Could not read fstab.";
return want_reboot;
}
if (!fs_mgr_overlayfs_setup(fstab, nullptr, &want_reboot)) {
LOG(ERROR) << "Overlayfs setup failed.";
return want_reboot;
}
if (want_reboot) {
printf("enabling overlayfs\n");
}
} else {
auto rv = fs_mgr_overlayfs_teardown(nullptr, &want_reboot);
if (rv == OverlayfsTeardownResult::Error) {
LOG(ERROR) << "Overlayfs teardown failed.";
return want_reboot;
}
if (rv == OverlayfsTeardownResult::Busy) {
LOG(ERROR) << "Overlayfs is still active until reboot.";
return true;
}
if (want_reboot) {
printf("disabling overlayfs\n");
}
}
return want_reboot;
}
bool do_remount(Fstab& fstab, const std::vector<std::string>& partition_args,
RemountCheckResult* check_result) {
Fstab partitions;
if (partition_args.empty()) {
partitions = GetAllRemountablePartitions(fstab);
} else {
if (!GetRemountList(fstab, partition_args, &partitions)) {
return false;
}
}
// Disable verity.
auto verity_result = SetVerityState(false /* enable_verity */);
// Treat error as fatal and suggest reboot only if verity is enabled.
// TODO(b/260041315): We check the device mapper for any "<partition>-verity" device present
// instead of checking ro.boot.veritymode because emulator has incorrect property value.
bool must_disable_verity = false;
for (const auto& partition : partitions) {
if (fs_mgr_is_verity_enabled(partition)) {
must_disable_verity = true;
break;
}
}
if (must_disable_verity) {
if (!verity_result.success) {
return false;
}
if (verity_result.want_reboot) {
check_result->reboot_later = true;
check_result->disabled_verity = true;
}
}
// Optionally setup overlayfs backing.
bool ok = CheckOverlayfs(&partitions, check_result);
if (partitions.empty() || check_result->disabled_verity) {
if (partitions.empty()) {
LOG(WARNING) << "No remountable partitions were found.";
}
return ok;
}
// Mount overlayfs.
if (!fs_mgr_overlayfs_mount_all(&partitions)) {
LOG(WARNING) << "Cannot mount overlayfs for some partitions";
// Continue regardless to handle raw remount case.
}
// Get actual mounts _after_ overlayfs has been added.
android::fs_mgr::Fstab mounts;
if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts) || mounts.empty()) {
PLOG(ERROR) << "Failed to read /proc/mounts";
return false;
}
// Remount selected partitions.
for (auto& entry : partitions) {
if (RemountPartition(fstab, mounts, entry)) {
check_result->remounted_anything = true;
} else {
ok = false;
}
}
return ok;
}
} // namespace
int main(int argc, char* argv[]) {
// Do not use MyLogger() when running as clean_scratch_files, as stdout/stderr of daemon process
// are discarded.
if (argc > 0 && android::base::Basename(argv[0]) == "clean_scratch_files"s) {
android::fs_mgr::CleanupOldScratchFiles();
return EXIT_SUCCESS;
}
android::base::InitLogging(argv, MyLogger(false /* verbose */));
const char* fstab_file = nullptr;
bool auto_reboot = false;
bool verbose = false;
std::vector<std::string> partition_args;
struct option longopts[] = {
{"fstab", required_argument, nullptr, 'T'},
{"help", no_argument, nullptr, 'h'},
{"reboot", no_argument, nullptr, 'R'},
{"verbose", no_argument, nullptr, 'v'},
{0, 0, nullptr, 0},
};
for (int opt; (opt = ::getopt_long(argc, argv, "hRT:v", longopts, nullptr)) != -1;) {
switch (opt) {
case 'h':
usage();
return EXIT_SUCCESS;
case 'R':
auto_reboot = true;
break;
case 'T':
if (fstab_file) {
LOG(ERROR) << "Cannot supply two fstabs: -T " << fstab_file << " -T " << optarg;
usage();
return EXIT_FAILURE;
}
fstab_file = optarg;
break;
case 'v':
verbose = true;
break;
default:
LOG(ERROR) << "Bad argument -" << char(opt);
usage();
return EXIT_FAILURE;
}
}
if (verbose) {
android::base::SetLogger(MyLogger(verbose));
}
bool remount = false;
bool enable_verity = false;
const std::string progname = getprogname();
if (progname == "enable-verity") {
enable_verity = true;
} else if (progname == "disable-verity") {
enable_verity = false;
} else if (progname == "set-verity-state") {
if (optind < argc && (argv[optind] == "1"s || argv[optind] == "0"s)) {
enable_verity = (argv[optind] == "1"s);
} else {
usage();
return EXIT_FAILURE;
}
} else {
remount = true;
for (; optind < argc; ++optind) {
partition_args.emplace_back(argv[optind]);
}
}
// Make sure we are root.
if (::getuid() != 0) {
LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
return EXIT_FAILURE;
}
// If somehow this executable is delivered on a "user" build, it can
// not function, so providing a clear message to the caller rather than
// letting if fall through and provide a lot of confusing failure messages.
if (!ALLOW_ADBD_DISABLE_VERITY || !android::base::GetBoolProperty("ro.debuggable", false)) {
LOG(ERROR) << "Device must be userdebug build";
return EXIT_FAILURE;
}
if (android::base::GetProperty("ro.boot.verifiedbootstate", "") != "orange") {
LOG(ERROR) << "Device must be bootloader unlocked";
return EXIT_FAILURE;
}
// Start a threadpool to service waitForService() callbacks as
// fs_mgr_overlayfs_* might call waitForService() to get the image service.
android::ProcessState::self()->startThreadPool();
if (!remount) {
auto ret = SetVerityState(enable_verity);
// Disable any overlayfs unconditionally if we want verity enabled.
// Enable overlayfs only if verity is successfully disabled or is already disabled.
if (enable_verity || ret.success) {
ret.want_reboot |= SetupOrTeardownOverlayfs(!enable_verity);
}
if (ret.want_reboot) {
if (auto_reboot) {
reboot(progname);
}
std::cout << "Reboot the device for new settings to take effect" << std::endl;
}
return ret.success ? EXIT_SUCCESS : EXIT_FAILURE;
}
// Make sure checkpointing is disabled if necessary.
if (!VerifyCheckpointing()) {
return EXIT_FAILURE;
}
// Read the selected fstab.
Fstab fstab;
if (!ReadFstab(fstab_file, &fstab) || fstab.empty()) {
PLOG(ERROR) << "Failed to read fstab";
return EXIT_FAILURE;
}
RemountCheckResult check_result;
bool remount_success = do_remount(fstab, partition_args, &check_result);
if (check_result.disabled_verity && check_result.setup_overlayfs) {
LOG(INFO) << "Verity disabled; overlayfs enabled.";
} else if (check_result.disabled_verity) {
LOG(INFO) << "Verity disabled.";
} else if (check_result.setup_overlayfs) {
LOG(INFO) << "Overlayfs enabled.";
}
if (remount_success && check_result.remounted_anything) {
LOG(INFO) << "Remount succeeded";
} else if (!remount_success) {
LOG(ERROR) << "Remount failed";
}
if (check_result.reboot_later) {
if (auto_reboot) {
// If (1) remount requires a reboot to take effect, (2) system is currently
// running a DSU guest and (3) DSU is disabled, then enable DSU so that the
// next reboot would not take us back to the host system but stay within
// the guest system.
if (!EnableDsuIfNeeded()) {
LOG(ERROR) << "Unable to automatically enable DSU";
return EXIT_FAILURE;
}
reboot("remount");
} else {
LOG(INFO) << "Now reboot your device for settings to take effect";
}
return EXIT_SUCCESS;
}
return remount_success ? EXIT_SUCCESS : EXIT_FAILURE;
}