fastbootd: Disallow certain operations during snapshot updates.

When a snapshot is applied or is merging, requests to erase or flash
userdata, metadata, or misc must be protected. In addition, the
set_active command must be restricted when a merge is in progress.

In addition, introduce a "snapshot-update merge" command for assisting
with erase requests when a merge is in progress. As in recovery, this
will force a merge to complete.

Bug: 139154945
Test: apply update
      fastboot erase userdata
      fastboot erase metadata
      fastboot erase misc
      fastboot set_active

Change-Id: I152446464335c62c39ffb4cc6366be9de19eac30
This commit is contained in:
David Anderson 2019-10-31 18:02:41 -07:00
parent b58df7dac3
commit 220ddb1f0f
9 changed files with 119 additions and 28 deletions

View file

@ -137,12 +137,14 @@ cc_binary {
"libhidlbase",
"liblog",
"liblp",
"libprotobuf-cpp-lite",
"libsparse",
"libutils",
],
static_libs: [
"libhealthhalutils",
"libsnapshot_nobinder",
],
header_libs: [

View file

@ -19,6 +19,8 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <unordered_set>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
@ -33,6 +35,7 @@
#include <libgsi/libgsi.h>
#include <liblp/builder.h>
#include <liblp/liblp.h>
#include <libsnapshot/snapshot.h>
#include <uuid/uuid.h>
#include "constants.h"
@ -48,6 +51,7 @@ using ::android::hardware::boot::V1_0::Slot;
using ::android::hardware::boot::V1_1::MergeStatus;
using ::android::hardware::fastboot::V1_0::Result;
using ::android::hardware::fastboot::V1_0::Status;
using android::snapshot::SnapshotManager;
using IBootControl1_1 = ::android::hardware::boot::V1_1::IBootControl;
struct VariableHandlers {
@ -57,6 +61,24 @@ struct VariableHandlers {
std::function<std::vector<std::vector<std::string>>(FastbootDevice*)> get_all_args;
};
static bool IsSnapshotUpdateInProgress(FastbootDevice* device) {
auto hal = device->boot1_1();
if (!hal) {
return false;
}
auto merge_status = hal->getSnapshotMergeStatus();
return merge_status == MergeStatus::SNAPSHOTTED || merge_status == MergeStatus::MERGING;
}
static bool IsProtectedPartitionDuringMerge(FastbootDevice* device, const std::string& name) {
static const std::unordered_set<std::string> ProtectedPartitionsDuringMerge = {
"userdata", "metadata", "misc"};
if (ProtectedPartitionsDuringMerge.count(name) == 0) {
return false;
}
return IsSnapshotUpdateInProgress(device);
}
static void GetAllVars(FastbootDevice* device, const std::string& name,
const VariableHandlers& handlers) {
if (!handlers.get_all_args) {
@ -142,8 +164,14 @@ bool EraseHandler(FastbootDevice* device, const std::vector<std::string>& args)
return device->WriteStatus(FastbootResult::FAIL, "Erase is not allowed on locked devices");
}
const auto& partition_name = args[1];
if (IsProtectedPartitionDuringMerge(device, partition_name)) {
auto message = "Cannot erase " + partition_name + " while a snapshot update is in progress";
return device->WriteFail(message);
}
PartitionHandle handle;
if (!OpenPartition(device, args[1], &handle)) {
if (!OpenPartition(device, partition_name, &handle)) {
return device->WriteStatus(FastbootResult::FAIL, "Partition doesn't exist");
}
if (wipe_block_device(handle.fd(), get_block_device_size(handle.fd())) == 0) {
@ -208,9 +236,9 @@ bool SetActiveHandler(FastbootDevice* device, const std::vector<std::string>& ar
"set_active command is not allowed on locked devices");
}
// Slot suffix needs to be between 'a' and 'z'.
Slot slot;
if (!GetSlotNumber(args[1], &slot)) {
// Slot suffix needs to be between 'a' and 'z'.
return device->WriteStatus(FastbootResult::FAIL, "Bad slot suffix");
}
@ -223,6 +251,32 @@ bool SetActiveHandler(FastbootDevice* device, const std::vector<std::string>& ar
if (slot >= boot_control_hal->getNumberSlots()) {
return device->WriteStatus(FastbootResult::FAIL, "Slot out of range");
}
// If the slot is not changing, do nothing.
if (slot == boot_control_hal->getCurrentSlot()) {
return device->WriteOkay("");
}
// Check how to handle the current snapshot state.
if (auto hal11 = device->boot1_1()) {
auto merge_status = hal11->getSnapshotMergeStatus();
if (merge_status == MergeStatus::MERGING) {
return device->WriteFail("Cannot change slots while a snapshot update is in progress");
}
// Note: we allow the slot change if the state is SNAPSHOTTED. First-
// stage init does not have access to the HAL, and uses the slot number
// and /metadata OTA state to determine whether a slot change occurred.
// Booting into the old slot would erase the OTA, and switching A->B->A
// would simply resume it if no boots occur in between. Re-flashing
// partitions implicitly cancels the OTA, so leaving the state as-is is
// safe.
if (merge_status == MergeStatus::SNAPSHOTTED) {
device->WriteInfo(
"Changing the active slot with a snapshot applied may cancel the"
" update.");
}
}
CommandResult ret;
auto cb = [&ret](CommandResult result) { ret = result; };
auto result = boot_control_hal->setActiveBootSlot(slot, cb);
@ -466,6 +520,11 @@ bool FlashHandler(FastbootDevice* device, const std::vector<std::string>& args)
}
const auto& partition_name = args[1];
if (IsProtectedPartitionDuringMerge(device, partition_name)) {
auto message = "Cannot flash " + partition_name + " while a snapshot update is in progress";
return device->WriteFail(message);
}
if (LogicalPartitionExists(device, partition_name)) {
CancelPartitionSnapshot(device, partition_name);
}
@ -555,12 +614,9 @@ bool GsiHandler(FastbootDevice* device, const std::vector<std::string>& args) {
bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string>& args) {
// Note that we use the HAL rather than mounting /metadata, since we want
// our results to match the bootloader.
auto hal = device->boot_control_hal();
auto hal = device->boot1_1();
if (!hal) return device->WriteFail("Not supported");
android::sp<IBootControl1_1> hal11 = IBootControl1_1::castFrom(hal);
if (!hal11) return device->WriteFail("Not supported");
// If no arguments, return the same thing as a getvar. Note that we get the
// HAL first so we can return "not supported" before we return the less
// specific error message below.
@ -573,18 +629,34 @@ bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string
return device->WriteOkay("");
}
if (args.size() != 2 || args[1] != "cancel") {
MergeStatus status = hal->getSnapshotMergeStatus();
if (args.size() != 2) {
return device->WriteFail("Invalid arguments");
}
if (args[1] == "cancel") {
switch (status) {
case MergeStatus::SNAPSHOTTED:
case MergeStatus::MERGING:
hal->setSnapshotMergeStatus(MergeStatus::CANCELLED);
break;
default:
break;
}
} else if (args[1] == "merge") {
if (status != MergeStatus::MERGING) {
return device->WriteFail("No snapshot merge is in progress");
}
MergeStatus status = hal11->getSnapshotMergeStatus();
switch (status) {
case MergeStatus::SNAPSHOTTED:
case MergeStatus::MERGING:
hal11->setSnapshotMergeStatus(MergeStatus::CANCELLED);
break;
default:
break;
auto sm = SnapshotManager::NewForFirstStageMount();
if (!sm) {
return device->WriteFail("Unable to create SnapshotManager");
}
if (!sm->HandleImminentDataWipe()) {
return device->WriteFail("Unable to finish snapshot merge");
}
} else {
return device->WriteFail("Invalid parameter to snapshot-update");
}
return device->WriteStatus(FastbootResult::OKAY, "Success");
}

View file

@ -60,7 +60,11 @@ FastbootDevice::FastbootDevice()
boot_control_hal_(IBootControl::getService()),
health_hal_(get_health_service()),
fastboot_hal_(IFastboot::getService()),
active_slot_("") {}
active_slot_("") {
if (boot_control_hal_) {
boot1_1_ = android::hardware::boot::V1_1::IBootControl::castFrom(boot_control_hal_);
}
}
FastbootDevice::~FastbootDevice() {
CloseDevice();

View file

@ -23,6 +23,7 @@
#include <vector>
#include <android/hardware/boot/1.0/IBootControl.h>
#include <android/hardware/boot/1.1/IBootControl.h>
#include <android/hardware/fastboot/1.0/IFastboot.h>
#include <android/hardware/health/2.0/IHealth.h>
@ -51,6 +52,7 @@ class FastbootDevice {
android::sp<android::hardware::boot::V1_0::IBootControl> boot_control_hal() {
return boot_control_hal_;
}
android::sp<android::hardware::boot::V1_1::IBootControl> boot1_1() { return boot1_1_; }
android::sp<android::hardware::fastboot::V1_0::IFastboot> fastboot_hal() {
return fastboot_hal_;
}
@ -63,6 +65,7 @@ class FastbootDevice {
std::unique_ptr<Transport> transport_;
android::sp<android::hardware::boot::V1_0::IBootControl> boot_control_hal_;
android::sp<android::hardware::boot::V1_1::IBootControl> boot1_1_;
android::sp<android::hardware::health::V2_0::IHealth> health_hal_;
android::sp<android::hardware::fastboot::V1_0::IFastboot> fastboot_hal_;
std::vector<char> download_data_;

View file

@ -432,19 +432,13 @@ bool GetSnapshotUpdateStatus(FastbootDevice* device, const std::vector<std::stri
std::string* message) {
// Note that we use the HAL rather than mounting /metadata, since we want
// our results to match the bootloader.
auto hal = device->boot_control_hal();
auto hal = device->boot1_1();
if (!hal) {
*message = "not supported";
return false;
}
android::sp<IBootControl1_1> hal11 = IBootControl1_1::castFrom(hal);
if (!hal11) {
*message = "not supported";
return false;
}
MergeStatus status = hal11->getSnapshotMergeStatus();
MergeStatus status = hal->getSnapshotMergeStatus();
switch (status) {
case MergeStatus::SNAPSHOTTED:
*message = "snapshotted";

View file

@ -399,6 +399,9 @@ static int show_help() {
" snapshot-update cancel On devices that support snapshot-based updates, cancel\n"
" an in-progress update. This may make the device\n"
" unbootable until it is reflashed.\n"
" snapshot-update merge On devices that support snapshot-based updates, finish\n"
" an in-progress update if it is in the \"merging\"\n"
" phase.\n"
"\n"
"boot image:\n"
" boot KERNEL [RAMDISK [SECOND]]\n"
@ -2089,8 +2092,8 @@ int FastBootTool::Main(int argc, char* argv[]) {
if (!args.empty()) {
arg = next_arg(&args);
}
if (!arg.empty() && arg != "cancel") {
syntax_error("expected: snapshot-update [cancel]");
if (!arg.empty() && (arg != "cancel" && arg != "merge")) {
syntax_error("expected: snapshot-update [cancel|merge]");
}
fb->SnapshotUpdateCommand(arg);
} else {

View file

@ -124,8 +124,11 @@ RetCode FastBootDriver::SetActive(const std::string& slot, std::string* response
RetCode FastBootDriver::SnapshotUpdateCommand(const std::string& command, std::string* response,
std::vector<std::string>* info) {
prolog_(StringPrintf("Snapshot %s", command.c_str()));
std::string raw = FB_CMD_SNAPSHOT_UPDATE ":" + command;
return RawCommand(raw, response, info);
auto result = RawCommand(raw, response, info);
epilog_(result);
return result;
}
RetCode FastBootDriver::FlashPartition(const std::string& partition,

View file

@ -224,7 +224,8 @@ class SnapshotManager final {
bool CreateLogicalAndSnapshotPartitions(const std::string& super_device);
// This method should be called preceding any wipe or flash of metadata or
// userdata. It is only valid in recovery.
// userdata. It is only valid in recovery or fastbootd, and it ensures that
// a merge has been completed.
//
// When userdata will be wiped or flashed, it is necessary to clean up any
// snapshot state. If a merge is in progress, the merge must be finished.

View file

@ -2185,6 +2185,15 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callba
return true;
}
// Check this early, so we don't accidentally start trying to populate
// the state file in recovery. Note we don't call GetUpdateState since
// we want errors in acquiring the lock to be propagated, instead of
// returning UpdateState::None.
auto state_file = GetStateFilePath();
if (access(state_file.c_str(), F_OK) != 0 && errno == ENOENT) {
return true;
}
auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
auto super_path = device_->GetSuperDevice(slot_number);
if (!CreateLogicalAndSnapshotPartitions(super_path)) {