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
179 lines
6.2 KiB
C++
179 lines
6.2 KiB
C++
/*
|
|
* Copyright (C) 2007 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.
|
|
*/
|
|
|
|
#ifndef _INIT_DEVICES_H
|
|
#define _INIT_DEVICES_H
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <algorithm>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <android-base/file.h>
|
|
#include <selinux/label.h>
|
|
|
|
#include "uevent.h"
|
|
#include "uevent_handler.h"
|
|
|
|
namespace android {
|
|
namespace init {
|
|
|
|
class Permissions {
|
|
public:
|
|
friend void TestPermissions(const Permissions& expected, const Permissions& test);
|
|
|
|
Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid, bool no_fnm_pathname);
|
|
|
|
bool Match(const std::string& path) const;
|
|
|
|
mode_t perm() const { return perm_; }
|
|
uid_t uid() const { return uid_; }
|
|
gid_t gid() const { return gid_; }
|
|
|
|
protected:
|
|
const std::string& name() const { return name_; }
|
|
|
|
private:
|
|
std::string name_;
|
|
mode_t perm_;
|
|
uid_t uid_;
|
|
gid_t gid_;
|
|
bool prefix_;
|
|
bool wildcard_;
|
|
bool no_fnm_pathname_;
|
|
};
|
|
|
|
class SysfsPermissions : public Permissions {
|
|
public:
|
|
friend void TestSysfsPermissions(const SysfsPermissions& expected, const SysfsPermissions& test);
|
|
|
|
SysfsPermissions(const std::string& name, const std::string& attribute, mode_t perm, uid_t uid,
|
|
gid_t gid, bool no_fnm_pathname)
|
|
: Permissions(name, perm, uid, gid, no_fnm_pathname), attribute_(attribute) {}
|
|
|
|
bool MatchWithSubsystem(const std::string& path, const std::string& subsystem) const;
|
|
void SetPermissions(const std::string& path) const;
|
|
|
|
private:
|
|
const std::string attribute_;
|
|
};
|
|
|
|
class Subsystem {
|
|
public:
|
|
friend class SubsystemParser;
|
|
friend void TestSubsystems(const Subsystem& expected, const Subsystem& test);
|
|
|
|
enum DevnameSource {
|
|
DEVNAME_UEVENT_DEVNAME,
|
|
DEVNAME_UEVENT_DEVPATH,
|
|
DEVNAME_SYS_NAME,
|
|
};
|
|
|
|
Subsystem() {}
|
|
Subsystem(std::string name) : name_(std::move(name)) {}
|
|
Subsystem(std::string name, DevnameSource source, std::string dir_name)
|
|
: name_(std::move(name)), devname_source_(source), dir_name_(std::move(dir_name)) {}
|
|
|
|
// Returns the full path for a uevent of a device that is a member of this subsystem,
|
|
// according to the rules parsed from ueventd.rc
|
|
std::string ParseDevPath(const Uevent& uevent) const {
|
|
std::string devname;
|
|
if (devname_source_ == DEVNAME_UEVENT_DEVNAME) {
|
|
devname = uevent.device_name;
|
|
} else if (devname_source_ == DEVNAME_UEVENT_DEVPATH) {
|
|
devname = android::base::Basename(uevent.path);
|
|
} else if (devname_source_ == DEVNAME_SYS_NAME) {
|
|
if (android::base::ReadFileToString("/sys/" + uevent.path + "/name", &devname)) {
|
|
devname.pop_back(); // Remove terminating newline
|
|
} else {
|
|
devname = uevent.device_name;
|
|
}
|
|
}
|
|
return dir_name_ + "/" + devname;
|
|
}
|
|
|
|
bool operator==(const std::string& string_name) const { return name_ == string_name; }
|
|
|
|
private:
|
|
std::string name_;
|
|
DevnameSource devname_source_ = DEVNAME_UEVENT_DEVNAME;
|
|
std::string dir_name_ = "/dev";
|
|
};
|
|
|
|
struct BlockDeviceInfo {
|
|
std::string str;
|
|
std::string type;
|
|
bool is_boot_device;
|
|
};
|
|
|
|
class DeviceHandler : public UeventHandler {
|
|
public:
|
|
friend class DeviceHandlerTester;
|
|
|
|
DeviceHandler();
|
|
DeviceHandler(std::vector<Permissions> dev_permissions,
|
|
std::vector<SysfsPermissions> sysfs_permissions,
|
|
std::vector<Subsystem> subsystems, std::set<std::string> 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
|
|
// through a comma separated and semicolon deliminated list. For example,
|
|
// `androidboot.partition_map=vdb,metadata;vdc,userdata` maps `vdb` to `metadata` and `vdc` to
|
|
// `userdata`.
|
|
static std::string GetPartitionNameForDevice(const std::string& device);
|
|
|
|
private:
|
|
void ColdbootDone() override;
|
|
BlockDeviceInfo GetBlockDeviceInfo(const std::string& uevent_path) const;
|
|
bool FindSubsystemDevice(std::string path, std::string* device_path,
|
|
const std::set<std::string>& 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<mode_t, uid_t, gid_t> GetDevicePermissions(
|
|
const std::string& path, const std::vector<std::string>& links) const;
|
|
void MakeDevice(const std::string& path, bool block, int major, int minor,
|
|
const std::vector<std::string>& links) const;
|
|
std::vector<std::string> GetBlockDeviceSymlinks(const Uevent& uevent) const;
|
|
void HandleDevice(const std::string& action, const std::string& devpath, bool block, int major,
|
|
int minor, const std::vector<std::string>& links) const;
|
|
void FixupSysPermissions(const std::string& upath, const std::string& subsystem) const;
|
|
void HandleAshmemUevent(const Uevent& uevent);
|
|
|
|
std::vector<Permissions> dev_permissions_;
|
|
std::vector<SysfsPermissions> sysfs_permissions_;
|
|
std::vector<Subsystem> subsystems_;
|
|
std::set<std::string> boot_devices_;
|
|
std::string boot_part_uuid_;
|
|
bool found_boot_part_uuid_;
|
|
bool skip_restorecon_;
|
|
std::string sysfs_mount_point_;
|
|
};
|
|
|
|
// Exposed for testing
|
|
void SanitizePartitionName(std::string* string);
|
|
|
|
} // namespace init
|
|
} // namespace android
|
|
|
|
#endif
|