From e9de3100619c37107c112ecc88e50cfbb210d803 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Tue, 22 Oct 2024 14:34:30 -0700 Subject: [PATCH] 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=[/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 --- init/README.ueventd.md | 27 +++++++++++ init/block_dev_initializer.cpp | 44 ++++++++++++++++- init/block_dev_initializer.h | 1 + init/devices.cpp | 89 ++++++++++++++++++++++++++++++++-- init/devices.h | 10 +++- init/first_stage_mount.cpp | 4 ++ init/ueventd.cpp | 21 ++++++-- 7 files changed, 185 insertions(+), 11 deletions(-) diff --git a/init/README.ueventd.md b/init/README.ueventd.md index 7d00195eb..aac4acb6a 100644 --- a/init/README.ueventd.md +++ b/init/README.ueventd.md @@ -39,6 +39,33 @@ for the node path: `device_id` is `uevent MINOR % 128 + 1`. 3. All other devices are created as `/dev/` +Whether a device is considered a "boot device" is a bit complicated. + + - The recommended way to specify the boot device is to provide the "partition UUID" containing the + kernel (or, really, any parition on the boot device) and then boot device is the block device + containing that partition. This is passed via `androidboot.boot_part_uuid` which can be provided + either via the kernel bootconfig or via the kernel commandline. As an example, you could set + `androidboot.boot_part_uuid=12345678-abcd-ef01-0234-6789abcdef01`. + - Though using `boot_part_uuid` is preferred, you can also specify the boot device via + `androidboot.boot_device` or `androidboot.boot_devices`. These can be passed via the kernel + bootconfig or the kernel command line. It is also possible to pass this via device tree by + creating a `boot_devices` property in the Android firmware node. In most cases the `boot_device` + is the sysfs path (without the `/sys/devices` or `/sys/devices/platform` prefix) to the closest + parent of the block device that's on the "platform" bus. As an example, if the block device is + `/sys/devices/platform/soc@0/7c4000.mmc/mmc_host/mmc1/mmc1:0001/block/mmcblk1` then the + `boot_device` is `soc@0/7c4000.mmc` since we strip off the `/sys/devices/platform` and nothing + past the `7c4000.mmc` directory represents a device on the "platform" bus. In the case that none + of the parents are on the "platform" bus there are special rules for block devices under PCI + and VBD (Virtual Block Device). NOTE: sysfs paths for block devices are not guaranteed to be + stable between kernel versions, which is one of the reasons why it is suggested to use + `boot_part_uuid` instead of `boot_devices`. ALSO NOTE: If more than one device matches (either + because multiple `boot_devices` were listed or because there was more than one block device + under the found sysfs directory) and these multiple matching devices provide some of the same + named partitions then the behavior is unspecified. + - There is a further fallback to determine "boot devices" via the vstab, but providing at least + `boot_devices` has been required since Android 12 so this further fallback will not be described + here. + The permissions can be modified using a ueventd.rc script and a line that beings with `/dev`. These lines take the format of diff --git a/init/block_dev_initializer.cpp b/init/block_dev_initializer.cpp index 8f5215856..4ca5b8f0e 100644 --- a/init/block_dev_initializer.cpp +++ b/init/block_dev_initializer.cpp @@ -33,7 +33,49 @@ BlockDevInitializer::BlockDevInitializer() : uevent_listener_(16 * 1024 * 1024) auto boot_devices = android::fs_mgr::GetBootDevices(); device_handler_ = std::make_unique( std::vector{}, std::vector{}, std::vector{}, - std::move(boot_devices), false); + 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() { diff --git a/init/block_dev_initializer.h b/init/block_dev_initializer.h index cb1d36555..25107c97f 100644 --- a/init/block_dev_initializer.h +++ b/init/block_dev_initializer.h @@ -29,6 +29,7 @@ class BlockDevInitializer final { public: BlockDevInitializer(); + bool InitBootDevicesFromPartUuid(); bool InitDeviceMapper(); bool InitDmUser(const std::string& name); bool InitDevices(std::set devices); diff --git a/init/devices.cpp b/init/devices.cpp index 501657ac6..0b1e13dba 100644 --- a/init/devices.cpp +++ b/init/devices.cpp @@ -192,7 +192,22 @@ void SysfsPermissions::SetPermissions(const std::string& path) const { BlockDeviceInfo DeviceHandler::GetBlockDeviceInfo(const std::string& uevent_path) const { BlockDeviceInfo info; - if (FindPlatformDevice(uevent_path, &info.str)) { + if (!boot_part_uuid_.empty()) { + // Only use the more specific "MMC" or "SCSI" match if a partition UUID + // was passed. Old bootloaders that aren't passing the partition UUID + // instead pass the path to the closest "platform" device. It would + // break them if we chose this deeper (more specific) path. + // + // When we have a UUID we _want_ the more specific path since it can + // handle, for instance, differentiating two USB disks that are on + // the same USB controller. Using the closest platform device would + // classify them both the same by using the path to the USB controller. + if (FindMmcDevice(uevent_path, &info.str)) { + info.type = "mmc"; + } else if (FindScsiDevice(uevent_path, &info.str)) { + info.type = "scsi"; + } + } else if (FindPlatformDevice(uevent_path, &info.str)) { info.type = "platform"; } else if (FindPciDevicePrefix(uevent_path, &info.str)) { info.type = "pci"; @@ -290,6 +305,22 @@ bool DeviceHandler::FindPlatformDevice(std::string path, std::string* platform_d return FindSubsystemDevice(path, platform_device_path, subsystem_paths); } +bool DeviceHandler::FindMmcDevice(std::string path, std::string* mmc_device_path) const { + const std::set subsystem_paths = { + sysfs_mount_point_ + "/bus/mmc", + }; + + return FindSubsystemDevice(path, mmc_device_path, subsystem_paths); +} + +bool DeviceHandler::FindScsiDevice(std::string path, std::string* scsi_device_path) const { + const std::set subsystem_paths = { + sysfs_mount_point_ + "/bus/scsi", + }; + + return FindSubsystemDevice(path, scsi_device_path, subsystem_paths); +} + void DeviceHandler::FixupSysPermissions(const std::string& upath, const std::string& subsystem) const { // upaths omit the "/sys" that paths in this list @@ -567,6 +598,48 @@ void DeviceHandler::HandleAshmemUevent(const Uevent& uevent) { } } +// Check Uevents looking for the kernel's boot partition UUID +// +// When we can stop checking uevents (either because we're done or because +// we weren't looking for the kernel's boot partition UUID) then return +// true. Return false if we're not done yet. +bool DeviceHandler::CheckUeventForBootPartUuid(const Uevent& uevent) { + // If we aren't using boot_part_uuid then we're done. + if (boot_part_uuid_.empty()) { + return true; + } + + // Finding the boot partition is a one-time thing that we do at init + // time, not steady state. This is because the boot partition isn't + // allowed to go away or change. Once we found the boot partition we don't + // expect to run again. + if (found_boot_part_uuid_) { + LOG(WARNING) << __PRETTY_FUNCTION__ + << " shouldn't run after kernel boot partition is found"; + return true; + } + + // We only need to look at newly-added block devices. Note that if someone + // is replaying events all existing devices will get "add"ed. + if (uevent.subsystem != "block" || uevent.action != "add") { + return false; + } + + // If it's not the partition we care about then move on. + if (uevent.partition_uuid != boot_part_uuid_) { + return false; + } + + auto device = GetBlockDeviceInfo(uevent.path); + + LOG(INFO) << "Boot device " << device.str << " found via partition UUID"; + found_boot_part_uuid_ = true; + boot_devices_.clear(); + boot_devices_.insert(device.str); + + return true; +} + void DeviceHandler::HandleUevent(const Uevent& uevent) { if (uevent.action == "add" || uevent.action == "change" || uevent.action == "bind" || uevent.action == "online") { @@ -629,17 +702,25 @@ void DeviceHandler::ColdbootDone() { DeviceHandler::DeviceHandler(std::vector dev_permissions, std::vector sysfs_permissions, std::vector subsystems, std::set boot_devices, - bool skip_restorecon) + std::string boot_part_uuid, bool skip_restorecon) : dev_permissions_(std::move(dev_permissions)), sysfs_permissions_(std::move(sysfs_permissions)), subsystems_(std::move(subsystems)), boot_devices_(std::move(boot_devices)), + boot_part_uuid_(boot_part_uuid), skip_restorecon_(skip_restorecon), - sysfs_mount_point_("/sys") {} + sysfs_mount_point_("/sys") { + // If both a boot partition UUID and a list of boot devices are + // specified then we ignore the boot_devices in favor of boot_part_uuid. + if (boot_devices_.size() && !boot_part_uuid.empty()) { + LOG(WARNING) << "Both boot_devices and boot_part_uuid provided; ignoring bootdevices"; + boot_devices_.clear(); + } +} DeviceHandler::DeviceHandler() : DeviceHandler(std::vector{}, std::vector{}, - std::vector{}, std::set{}, false) {} + std::vector{}, std::set{}, "", false) {} } // namespace init } // namespace android diff --git a/init/devices.h b/init/devices.h index 7eee87a12..cac52bc17 100644 --- a/init/devices.h +++ b/init/devices.h @@ -128,10 +128,12 @@ class DeviceHandler : public UeventHandler { DeviceHandler(); DeviceHandler(std::vector dev_permissions, - std::vector sysfs_permissions, std::vector subsystems, - std::set boot_devices, bool skip_restorecon); + std::vector sysfs_permissions, + std::vector subsystems, std::set boot_devices, + std::string boot_part_uuid, bool skip_restorecon); virtual ~DeviceHandler() = default; + bool CheckUeventForBootPartUuid(const Uevent& uevent); void HandleUevent(const Uevent& uevent) override; // `androidboot.partition_map` allows associating a partition name for a raw block device @@ -146,6 +148,8 @@ class DeviceHandler : public UeventHandler { bool FindSubsystemDevice(std::string path, std::string* device_path, const std::set& subsystem_paths) const; bool FindPlatformDevice(std::string path, std::string* platform_device_path) const; + bool FindMmcDevice(std::string path, std::string* mmc_device_path) const; + bool FindScsiDevice(std::string path, std::string* scsi_device_path) const; std::tuple GetDevicePermissions( const std::string& path, const std::vector& links) const; void MakeDevice(const std::string& path, bool block, int major, int minor, @@ -160,6 +164,8 @@ class DeviceHandler : public UeventHandler { std::vector sysfs_permissions_; std::vector subsystems_; std::set boot_devices_; + std::string boot_part_uuid_; + bool found_boot_part_uuid_; bool skip_restorecon_; std::string sysfs_mount_point_; }; diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp index 4f1af3038..aa6b55166 100644 --- a/init/first_stage_mount.cpp +++ b/init/first_stage_mount.cpp @@ -288,6 +288,10 @@ static bool IsMicrodroidStrictBoot() { } bool FirstStageMountVBootV2::InitDevices() { + if (!block_dev_init_.InitBootDevicesFromPartUuid()) { + return false; + } + std::set devices; GetSuperDeviceName(&devices); diff --git a/init/ueventd.cpp b/init/ueventd.cpp index 3f0d0e95b..286e47266 100644 --- a/init/ueventd.cpp +++ b/init/ueventd.cpp @@ -353,10 +353,25 @@ int ueventd_main(int argc, char** argv) { auto ueventd_configuration = GetConfiguration(); - uevent_handlers.emplace_back(std::make_unique( + UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size); + + // Right after making DeviceHandler, replay all events looking for which + // block device has the boot partition. This lets us make symlinks + // for all of the other partitions on the same disk. Note that by the time + // we get here we know that the boot partition has already shown up (if + // we're looking for it) so just regenerating events is enough to know + // we'll see it. + std::unique_ptr device_handler = std::make_unique( std::move(ueventd_configuration.dev_permissions), std::move(ueventd_configuration.sysfs_permissions), - std::move(ueventd_configuration.subsystems), android::fs_mgr::GetBootDevices(), true)); + std::move(ueventd_configuration.subsystems), android::fs_mgr::GetBootDevices(), + android::fs_mgr::GetBootPartUuid(), true); + uevent_listener.RegenerateUevents([&](const Uevent& uevent) -> ListenerAction { + bool uuid_check_done = device_handler->CheckUeventForBootPartUuid(uevent); + return uuid_check_done ? ListenerAction::kStop : ListenerAction::kContinue; + }); + + uevent_handlers.emplace_back(std::move(device_handler)); uevent_handlers.emplace_back(std::make_unique( std::move(ueventd_configuration.firmware_directories), std::move(ueventd_configuration.external_firmware_handlers))); @@ -365,8 +380,6 @@ int ueventd_main(int argc, char** argv) { std::vector base_paths = {"/odm/lib/modules", "/vendor/lib/modules"}; uevent_handlers.emplace_back(std::make_unique(base_paths)); } - UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size); - if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) { ColdBoot cold_boot(uevent_listener, uevent_handlers, ueventd_configuration.enable_parallel_restorecon,