android_system_core/init/block_dev_initializer.cpp
Douglas Anderson e9de310061 init: Add the ability to find the boot device by partition UUID
The current mechanism for specifying boot devices on Android systems
involves passing a set of "boot_devices" though command line,
bootconfig, or device tree.

The bootdevices are specified as strings and, in general, need to
match a sysfs path but without the "/sys/devices" or
"/sys/devices/platform" prefix. The sysfs path is generally the path
to the closest parent of the block device that is a "platform" device.

As an example, if the sysfs path of the expected boot device is:
  /sys/devices/platform/soc@0/7c4000.mmc/mmc_host/mmc1/mmc1:0001/block/mmcblk1

The bootloader would specify it as "soc@0/7c4000.mmc" since:
* We strip off "/sys/devices/platform/"
* As we move up directories, we don't find one whose subsystem is
  "platform" until we get up to
  "/sys/devices/platform/soc@0/7c4000.mmc".

The current mechanism is a bit brittle. Specifically:
* The sysfs path isn't _really_ stable and can change across kernel
  upgrades. For instance, during one kernel upgrade the device tree
  for a product changed so that the root node changed from "soc" to
  "soc@0" and this changed all sysfs paths. In the past device tree
  folks have asserted that we shouldn't rely on dts node names to stay
  consistent, yet those node names are used to construct sysfs paths.
* For some devices, like USB, the path of the closest "platform"
  device tends to be the path of the USB controller. This means that
  if two USB disks are plugged in we can't guarantee which one will be
  identified as the boot device.

Add a new method of finding the boot device by passing the partition
UUID that we loaded the kernel from. Using the partition UUID to
identify the boot device is standard on Linux. You can see this
because when you're not using an initramfs you can use the syntax
"root=PARTUUID=<valid-uuid-id>[/PARTNROFF=n]" to specify the root.
Using the same idea for Android's boot code makes sense.

With this new method for finding the boot device, we can make the code
much more specific about matching sysfs paths. Once we find the sysfs
path for the kernel we can make sure that all of the other boot
partition share the same "scsi" or "mmc" parent instead of going all
the way to the closest platform device. In the above example, this
means that we'd make sure that all boot devices are found under this
sysfs node:
  /sys/devices/platform/soc@0/7c4000.mmc/mmc_host/mmc1/mmc1:0001/block/mmcblk1
...instead of just making sure they are under:
  /sys/devices/platform/soc@0/7c4000.mmc

There is the question of what we should do if the bootloader passes
_both_ an old style "boot_devices" and also a partition UUID. In this
case, we'll issue a warning and then ignore the old "boot_devices".
Considering it a warning rather than an error could allow switching to
the "boot_part_uuid" method even if an old bootloader is still
hardcoding some old "boot_devices".

NOTE: Using partition UUID won't cause any security problems even
though someone _could_ plug in an external device crafted to have the
same UUID as the normal boot device's kernel partition. We already
have "verity" in the system making sure our filesystems are not
tampered with and this would also protect us from booting a tampered
disk. That means that the worst someone could do in this case would be
to confuse the system and make the device non-bootable. Chromebooks
have been using the partition UUID to find the root filesystems for
years and this has never been a problem.

NOTE: this new method relies on the commit ("init: Add partition_uuid
to Uevent") which in turn relies upstream kernel commit 74f4a8dc0dd8
("block: add partition uuid into uevent as "PARTUUID"").

Bug: 316324155
Test: Use partition UUID to boot

Change-Id: If824cb700ca3696a442a28e6ad02d7c522c3b495
2024-11-06 13:03:15 -08:00

216 lines
7.8 KiB
C++

// 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 <chrono>
#include <string_view>
#include <vector>
#include <android-base/chrono_utils.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <fs_mgr.h>
#include "block_dev_initializer.h"
namespace android {
namespace init {
using android::base::Timer;
using namespace std::chrono_literals;
BlockDevInitializer::BlockDevInitializer() : uevent_listener_(16 * 1024 * 1024) {
auto boot_devices = android::fs_mgr::GetBootDevices();
device_handler_ = std::make_unique<DeviceHandler>(
std::vector<Permissions>{}, std::vector<SysfsPermissions>{}, std::vector<Subsystem>{},
std::move(boot_devices), android::fs_mgr::GetBootPartUuid(), false);
}
// If boot_part_uuid is specified, use it to set boot_devices
//
// When `androidboot.boot_part_uuid` is specified then that's the partition UUID
// of the kernel. Look for that partition and then set `boot_devices` to be
// exactly one item: the block device containing that partition.
//
// NOTE that `boot_part_uuid` is only specified on newer devices. Older devices
// specified `boot_devices` directly.
bool BlockDevInitializer::InitBootDevicesFromPartUuid() {
bool uuid_check_done = false;
auto boot_part_callback = [&, this](const Uevent& uevent) -> ListenerAction {
uuid_check_done = device_handler_->CheckUeventForBootPartUuid(uevent);
return uuid_check_done ? ListenerAction::kStop : ListenerAction::kContinue;
};
// Re-run already arrived uevents looking for the boot partition UUID.
//
// NOTE: If we're not using the boot partition UUID to find the boot
// device then the first uevent we analyze will cause us to stop looking
// and set `uuid_check_done`. This will shortcut all of the UUID logic.
// Replaying one uevent is not expected to be slow.
uevent_listener_.RegenerateUevents(boot_part_callback);
// If we're not done looking, poll for uevents for longer
if (!uuid_check_done) {
Timer t;
uevent_listener_.Poll(boot_part_callback, 10s);
LOG(INFO) << "Wait for boot partition returned after " << t;
}
// Give a nicer error message if we were expecting to find the kernel boot
// partition but didn't. Later code would fail too but the message there
// is a bit further from the root cause of the problem.
if (!uuid_check_done) {
LOG(ERROR) << __PRETTY_FUNCTION__ << ": boot partition not found after polling timeout.";
return false;
}
return true;
}
bool BlockDevInitializer::InitDeviceMapper() {
return InitMiscDevice("device-mapper");
}
bool BlockDevInitializer::InitDmUser(const std::string& name) {
return InitMiscDevice("dm-user!" + name);
}
bool BlockDevInitializer::InitMiscDevice(const std::string& name) {
const std::string dm_path = "/devices/virtual/misc/" + name;
bool found = false;
auto dm_callback = [this, &dm_path, &found](const Uevent& uevent) {
if (uevent.path == dm_path) {
device_handler_->HandleUevent(uevent);
found = true;
return ListenerAction::kStop;
}
return ListenerAction::kContinue;
};
uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path, dm_callback);
if (!found) {
LOG(INFO) << name << " device not found in /sys, waiting for its uevent";
Timer t;
uevent_listener_.Poll(dm_callback, 10s);
LOG(INFO) << "Wait for " << name << " returned after " << t;
}
if (!found) {
LOG(ERROR) << name << " device not found after polling timeout";
return false;
}
return true;
}
ListenerAction BlockDevInitializer::HandleUevent(const Uevent& uevent,
std::set<std::string>* devices) {
// Ignore everything that is not a block device.
if (uevent.subsystem != "block") {
return ListenerAction::kContinue;
}
auto name = uevent.partition_name;
if (name.empty()) {
size_t base_idx = uevent.path.rfind('/');
if (base_idx == std::string::npos) {
return ListenerAction::kContinue;
}
name = uevent.path.substr(base_idx + 1);
}
auto iter = devices->find(name);
if (iter == devices->end()) {
auto partition_name = DeviceHandler::GetPartitionNameForDevice(uevent.device_name);
if (!partition_name.empty()) {
iter = devices->find(partition_name);
}
if (iter == devices->end()) {
return ListenerAction::kContinue;
}
}
LOG(VERBOSE) << __PRETTY_FUNCTION__ << ": found partition: " << name;
devices->erase(iter);
device_handler_->HandleUevent(uevent);
return devices->empty() ? ListenerAction::kStop : ListenerAction::kContinue;
}
bool BlockDevInitializer::InitDevices(std::set<std::string> devices) {
auto uevent_callback = [&, this](const Uevent& uevent) -> ListenerAction {
return HandleUevent(uevent, &devices);
};
uevent_listener_.RegenerateUevents(uevent_callback);
// UeventCallback() will remove found partitions from |devices|. So if it
// isn't empty here, it means some partitions are not found.
if (!devices.empty()) {
LOG(INFO) << __PRETTY_FUNCTION__
<< ": partition(s) not found in /sys, waiting for their uevent(s): "
<< android::base::Join(devices, ", ");
Timer t;
uevent_listener_.Poll(uevent_callback, 10s);
LOG(INFO) << "Wait for partitions returned after " << t;
}
if (!devices.empty()) {
LOG(ERROR) << __PRETTY_FUNCTION__ << ": partition(s) not found after polling timeout: "
<< android::base::Join(devices, ", ");
return false;
}
return true;
}
// Creates "/dev/block/dm-XX" for dm nodes by running coldboot on /sys/block/dm-XX.
bool BlockDevInitializer::InitDmDevice(const std::string& device) {
const std::string device_name(basename(device.c_str()));
const std::string syspath = "/sys/block/" + device_name;
return InitDevice(syspath, device_name);
}
bool BlockDevInitializer::InitPlatformDevice(const std::string& dev_name) {
return InitDevice("/sys/devices/platform", dev_name);
}
bool BlockDevInitializer::InitHvcDevice(const std::string& dev_name) {
return InitDevice("/sys/devices/virtual/tty", dev_name);
}
bool BlockDevInitializer::InitDevice(const std::string& syspath, const std::string& device_name) {
bool found = false;
auto uevent_callback = [&device_name, this, &found](const Uevent& uevent) {
if (uevent.device_name == device_name) {
LOG(VERBOSE) << "Creating device : " << device_name;
device_handler_->HandleUevent(uevent);
found = true;
return ListenerAction::kStop;
}
return ListenerAction::kContinue;
};
uevent_listener_.RegenerateUeventsForPath(syspath, uevent_callback);
if (!found) {
LOG(INFO) << "device '" << device_name << "' not found in /sys, waiting for its uevent";
Timer t;
uevent_listener_.Poll(uevent_callback, 10s);
LOG(INFO) << "wait for device '" << device_name << "' returned after " << t;
}
if (!found) {
LOG(ERROR) << "device '" << device_name << "' not found after polling timeout";
return false;
}
return true;
}
} // namespace init
} // namespace android