一、Init中哪个阶段校验及校验哪些分区?
在引导boot kernel后进入init,主要是在init的first stage(第一阶段)进行AVB校验的。在android需要挂载的分区挂载前执行校验。
需要校验的分区是在fstab中配置的,详细配置请查看AVB编译配置
二、Init代码分析
1、FirstStageMount流程
我们整体上了解一下Android是怎么设计把AVB校验流程穿插进来的,重点是理解google的设计思想和理解它的巧妙之处。
Init第一阶段的执行代码,整体流程上有点长,希望能耐心地研究一下代码。
1、FirstStageMountVBootV2校验流程
先从init的main.cpp开始,init的第一阶段主要是校验和挂载system/vendor等大分区,init的第二阶段才会去处理userdata等分区。
因为system/vendor分区未挂载,需要借助于dts或者rootfs中的fstab配置才知道system/vendor分区的描述信息,比如:
- 当前需要校验哪几个分区,
- 这几个分区是否使能verity flag等,
- 和是否有super动态分区有点关系,一般如果没有打开super分区,会借助于dts的配置;如果打开了super分区,会借助于fstab中的flag
好了,下面开始从init的第一个执行的地方开始分析:(源码是最好的文档)
#### main.cpp int main(int argc, char** argv) { #if __has_feature(address_sanitizer) __asan_set_error_report_callback(AsanReportCallback); #endif if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); ###处理uevent流程相关的 } if (argc > 1) { if (!strcmp(argv[1], "subcontext")) { android::base::InitLogging(argv, &android::base::KernelLogger); const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap(); return SubcontextMain(argc, argv, &function_map); } if (!strcmp(argv[1], "selinux_setup")) { return SetupSelinux(argv); ###selinux相关的 } if (!strcmp(argv[1], "second_stage")) { return SecondStageMain(argc, argv); ## 第二阶段 } } return FirstStageMain(argc, argv); ## 第一阶段 }
main.cpp中调用FirstStageMain()函数,执行第一阶段流程
int FirstStageMain(int argc, char** argv) { … if (!DoFirstStageMount()) { LOG(FATAL) << "Failed to mount required partitions early ..."; } … } bool DoFirstStageMount() { … std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create(); if (!handle) { LOG(ERROR) << "Failed to create FirstStageMount"; return false; } return handle->DoFirstStageMount(); }
我们先看下这个handle,调用了create方法,创建FirstStageMountVBootV1或者FirstStageMountVBootV2实例,取决于IsDtVbmetaCompatible(fstab)的返回值,如果支持vbmeta,则使用FirstStageMountVBootV2那套流程,我们分析使能了AVB的情况。
std::unique_ptr<FirstStageMount> FirstStageMount::Create() { auto fstab = ReadFirstStageFstab(); ###判断device tree中是否有vbmeta/compatible结构,值是android,vbmeta if (IsDtVbmetaCompatible(fstab)) { return std::make_unique<FirstStageMountVBootV2>(std::move(fstab)); } else { return std::make_unique<FirstStageMountVBootV1>(std::move(fstab)); } }
IsDtVbmetaCompatible是检查kernel dtsi中是否有配置类似如下格式的数据(前面说了借助于dts或者rootfs中的fstab配置):
firmware: firmware { compatible = "android,firmware"; android { … }; };
FirstStageMountVBootV2是FirstStageMount的子类,有三个和dm-verity相关方法:
- InitDevices:完成device mapper的映射,就是system/vendor分区如何映身到dm-x的
- SetUpDmVerity:完成hash tree的使能,即dm-verity功能是怎么设置到kernel,将来设备运行时校验分区;
- InitAvbHandle:完成AVB的libavb校验,确认vbmeta/system/vendor的签名是否合法
这三个方法基本上就把AVB校验的事情做完了,每一个都非常关键,建议大家重点分析。
如果没有这三个概念,估计也很难理解google的设计思路了。
好了,了解了这接下来可以继续分析代码了。
FirstStageMountVBootV2类的构建函数
先看一下FirstStageMountVBootV2类的构造函数
功能:
- 主要是解析device tree中的vbmeta parts节点数据,存在device_tree_vbmeta_parts指针中,然后解析数据,最后全部插入到对象的vbmeta_partitions这个vector中保存起来。
FirstStageMountVBootV2::FirstStageMountVBootV2(Fstab fstab) : FirstStageMount(std::move(fstab)), avb_handle_(nullptr) { std::string device_tree_vbmeta_parts; read_android_dt_file("vbmeta/parts", &device_tree_vbmeta_parts); for (auto&& partition : Split(device_tree_vbmeta_parts, ",")) { if (!partition.empty()) { vbmeta_partitions_.emplace_back(std::move(partition)); } } for (const auto& entry : fstab_) { if (!entry.vbmeta_partition.empty()) { vbmeta_partitions_.emplace_back(entry.vbmeta_partition); } } … }
分析完FirstStageMountVBootV2类的构造函数后,我们接着看上面的handle->DoFirstStageMount(),也就是FirstStageMountVBootV2的DoFirstStageMount()方法,但这两个子类没有重载此方法,使用的父类FirstStageMount的DoFirstStageMount()方法,如下:
bool FirstStageMount::DoFirstStageMount() { if (!IsDmLinearEnabled() && fstab_.empty()) { // Nothing to mount. LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)"; return true; } if (!InitDevices()) return false; if (!MountPartitions()) return false; return true; }
其中IsDmLinearEnabled()方法也比较简单,就是判断fstab中分区是否有配置logical关键字,如果有配置表示当前分区为动态分区的逻辑子分区,andriod R是要求使用动态分区的。
bool FirstStageMount::IsDmLinearEnabled() { for (const auto& entry : fstab_) { if (entry.fs_mgr_flags.logical) return true; } return false; }
1、InitDevices()函数
接下来分析第一个重点函数,InitDevices函数,这个函数主要做的一件事情就是找到super物理分区的节点,以及完成这个分区的节点映射,在dev/block下面生成super分区的节点出来。
bool FirstStageMount::InitDevices() { std::set<std::string> devices; GetSuperDeviceName(&devices); if (!GetDmVerityDevices(&devices)) { return false; } if (!InitRequiredDevices(std::move(devices))) { return false; } if (IsDmLinearEnabled()) { auto super_symlink = "/dev/block/by-name/"s + super_partition_name_; if (!android::base::Realpath(super_symlink, &super_path_)) { PLOG(ERROR) << "realpath failed: " << super_symlink; return false; } } return true; }
那我们按顺序一个个的分析,GetSuperDeviceName()函数看下面调用关系,super_partition_name_最终就是个常量“super”,或者从kernel的cmdline传递过来的 可能带有“_a”或者“_b”,这个由boot的上一级uboot来决定的。
void FirstStageMount::GetSuperDeviceName(std::set<std::string>* devices) { // Add any additional devices required for dm-linear mappings. if (!IsDmLinearEnabled()) { return; } devices->emplace(super_partition_name_); } std::string fs_mgr_get_super_partition_name(int slot) { // Devices upgrading to dynamic partitions are allowed to specify a super // partition name. This includes cuttlefish, which is a non-A/B device. std::string super_partition; if (fs_mgr_get_boot_config_from_kernel_cmdline("super_partition", &super_partition)) { if (fs_mgr_get_slot_suffix().empty()) { return super_partition; } std::string suffix; if (slot == 0) { suffix = "_a"; } else if (slot == 1) { suffix = "_b"; } else if (slot == -1) { suffix = fs_mgr_get_slot_suffix(); } return super_partition + suffix; } return LP_METADATA_DEFAULT_PARTITION_NAME; } #define LP_METADATA_DEFAULT_PARTITION_NAME "super"
接着看GetDmVerityDevices函数,这是虚函数,由子类FirstStageMountVBootV2实现了,其实就是遍历fstab配置中的带avb和logical字样的分区,把system/vendor等分区找出来。
bool FirstStageMountVBootV2::GetDmVerityDevices(std::set<std::string>* devices) { need_dm_verity_ = false; std::set<std::string> logical_partitions; // fstab_rec->blk_device has A/B suffix. for (const auto& fstab_entry : fstab_) { if (fstab_entry.fs_mgr_flags.avb) { ##判断fstab分区中是否有avb字段 need_dm_verity_ = true; } if (fstab_entry.fs_mgr_flags.logical) { // Don't try to find logical partitions via uevent regeneration. logical_partitions.emplace(basename(fstab_entry.blk_device.c_str())); } else { devices->emplace(basename(fstab_entry.blk_device.c_str())); } } if (need_dm_verity_) { if (vbmeta_partitions_.empty()) { LOG(ERROR) << "Missing vbmeta partitions"; return false; } std::string ab_suffix = fs_mgr_get_slot_suffix(); for (const auto& partition : vbmeta_partitions_) { std::string partition_name = partition + ab_suffix; if (logical_partitions.count(partition_name)) { continue; } devices->emplace(partition_name); } } return true; }
接着看比较重要的InitRequiredDevices函数,这个主要的作用:底层查看system/vendor逻辑子分区的物理节点,如果10秒内没有找到system/vendor等分区,直接报异常。
bool FirstStageMount::InitRequiredDevices(std::set<std::string> devices) { if (!block_dev_init_.InitDeviceMapper()) { return false; } if (devices.empty()) { return true; } return block_dev_init_.InitDevices(std::move(devices)); }
更多的细节请见DeviceHandler类的实现细节,代码在devices.cpp中
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; }
这里截取一部分code,其余的细节如底层怎么编译到system/vendor分区节点的,请大家自已看一下这块的代码,不然本篇文章会拉的非常长~~
void DeviceHandler::HandleUevent(const Uevent& uevent) { if (uevent.action == "add" || uevent.action == "change" || uevent.action == "online") { FixupSysPermissions(uevent.path, uevent.subsystem); } ... if (uevent.subsystem == "block") { block = true; devpath = "/dev/block/" + Basename(uevent.path); if (StartsWith(uevent.path, "/devices")) { links = GetBlockDeviceSymlinks(uevent); }
好了,到此FirstStageMount::InitDevices就执行完成了,而且super_symlink成功的获取到底层的super分区节点名。
bool FirstStageMount::InitDevices() { std::set<std::string> devices; GetSuperDeviceName(&devices); if (!GetDmVerityDevices(&devices)) { return false; } if (!InitRequiredDevices(std::move(devices))) { return false; } if (IsDmLinearEnabled()) { auto super_symlink = "/dev/block/by-name/"s + super_partition_name_; if (!android::base::Realpath(super_symlink, &super_path_)) { PLOG(ERROR) << "realpath failed: " << super_symlink; return false; } } return true; }
2、SetUpDmVerity函数
initDevices执行完后,执行挂载system/vendor等分区
bool FirstStageMount::DoFirstStageMount() { ... if (!InitDevices()) return false; if (!MountPartitions()) return false; ##initDevices执行完后,执行挂载分区 return true; }
MountPartitions函数里面有几个重要的函数,功能如下:
- CreateLogicalPartitions:创建super子分区(system/vendor等)的dm-x节点,并设置到kernel层;
- TrySwitchSystemAsRoot:将system分区挂载到设备的“/”根目录,其中也有校验和挂载system分区;
- MountPartition:挂载fstab中其他子logic分区,如vendor/system_ext/product等分区,流程和system分区的校验和挂载是类似的。
bool FirstStageMount::MountPartitions() { ... LOG(INFO) << "MountPartitions CreateLogicalPartitions."; if (!CreateLogicalPartitions()) return false; LOG(INFO) << "MountPartitions TrySwitchSystemAsRoot."; if (!TrySwitchSystemAsRoot()) return false; if (!SkipMountingPartitions(&fstab_)) return false; for (auto current = fstab_.begin(); current != fstab_.end();) { ... Fstab::iterator end; if (!MountPartition(current, false /* erase_same_mounts */, &end)) {
那我们按顺序一个个地分析
1、CreateLogicalPartitions函数
创建super子分区(system/vendor等)的dm-x节点,并设置到kernel层;
函数调用中间过程,大家可以直接点击,最后调用到如下地方:
@fs_mgr_dm_linear.cpp bool CreateLogicalPartition(CreateLogicalPartitionParams params, std::string* path) { CreateLogicalPartitionParams::OwnedData owned_data; if (!params.InitDefaults(&owned_data)) return false; DmTable table; if (!CreateDmTableInternal(params, &table)) { return false; } DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.CreateDevice(params.device_name, table, path, params.timeout_ms)) { return false; } LINFO << "Created logical partition " << params.device_name << " on device " << *path; return true; }
dm.CreateDevice函数
重点是dm.CreateDevice函数,关于DeviceMapper的内容,后面我会专门写一个章节,期待下吧~~
调用到了dm.cpp,这里面有三个函数,分别是CreateDevice、LoadTableAndActivate、GetDeviceUniquePath,本质上做了三个ioctl命令:
ioctl(fd_, DM_DEV_CREATE, &io) :创建hash-table
ioctl(fd_, DM_TABLE_LOAD, io):将hash-table,映身到kernel的drivers/md驱动中保存
ioctl(fd_, DM_DEV_SUSPEND, io):suppend建立映射关系
这块的内容也比较多,后面专门搞一篇文章来介绍,在DeviceMapper中介绍吧~
bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table, std::string* path, const std::chrono::milliseconds& timeout_ms) { std::string uuid = GenerateUuid(); if (!CreateDevice(name, uuid)) { return false; } ... std::string unique_path; if (!LoadTableAndActivate(name, table) || !GetDeviceUniquePath(name, &unique_path) || !GetDmDevicePathByName(name, path)) { DeleteDevice(name); return false; } ... return true; }
2、TrySwitchSystemAsRoot函数
讲完了CreateLogicalPartition函数后,接下来看TrySwitchSystemAsRoot函数,其实就是挂载system分区,并挂载到根目录上。
bool FirstStageMount::TrySwitchSystemAsRoot() { UseDsuIfPresent(); // Preloading all AVB keys from the ramdisk before switching root to /system. PreloadAvbKeys(); auto system_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) { return entry.mount_point == "/system"; }); if (system_partition == fstab_.end()) return true; if (MountPartition(system_partition, false /* erase_same_mounts */)) { ... SwitchRoot("/system"); } else { PLOG(ERROR) << "Failed to mount /system"; return false; } return true; } void SwitchRoot(const std::string& new_root) { auto mounts = GetMounts(new_root); for (const auto& mount_path : mounts) { auto new_mount_path = new_root + mount_path; mkdir(new_mount_path.c_str(), 0755); if (mount(mount_path.c_str(), new_mount_path.c_str(), nullptr, MS_MOVE, nullptr) != 0) { PLOG(FATAL) << "Unable to move mount at '" << mount_path << "'"; } } if (chdir(new_root.c_str()) != 0) { PLOG(FATAL) << "Could not chdir to new_root, '" << new_root << "'"; } if (mount(new_root.c_str(), "/", nullptr, MS_MOVE, nullptr) != 0) { PLOG(FATAL) << "Unable to move root mount to new_root, '" << new_root << "'"; } ... }
3、MountPartition函数
分析一下MountPartition函数,后面的system/vendor/product等分区都会走到这个函数。里面比较关键的有两个函数,SetUpDmVerity和fs_mgr_do_mount_one
老规矩,先介绍这两个函数的总体功能:
- SetUpDmVerity:AVB2.0的avb校验功能,基本就在这个函数中了,重头戏,所以想绕过android的dm-verity校验就可以在这里搞点东东啦~
- fs_mgr_do_mount_one:这个没什么特别,就是fsck校验一下分区然后mount挂载。
所以,我们的重点是分析SetUpDmVerity函数。
bool FirstStageMount::MountPartition(const Fstab::iterator& begin, bool erase_same_mounts, Fstab::iterator* end) { ... if (!SetUpDmVerity(&(*begin))) { PLOG(ERROR) << "Failed to setup verity for '" << begin->mount_point << "'"; return false; } bool mounted = (fs_mgr_do_mount_one(*begin) == 0); // Try other mounts with the same mount point. Fstab::iterator current = begin + 1; for (; current != fstab_.end() && current->mount_point == begin->mount_point; current++) { if (!mounted) { // blk_device is already updated to /dev/dm-<N> by SetUpDmVerity() above. // Copy it from the begin iterator. current->blk_device = begin->blk_device; mounted = (fs_mgr_do_mount_one(*current) == 0); } } ... return mounted; }
SetUpDmVerity也是虚函数,在子类中实现的。咋一看下面代码挺头痛,第一看我也觉得写的啥这是~~
但仔细看还是能看明白是干嘛的了,我先挑出几个重点的介绍一下:
fstab_entry->avb_keys.empty():判断fstab中是否有配置avb_keys,这个是vbmeta的公钥
InitAvbHandle:AVB调用libavb库完成分区的校验,重头戏。
avb_handle_->SetUpAvbHashtree:就是把system/vendor分区的root hashtree值设置到kernel驱动中。
bool FirstStageMountVBootV2::SetUpDmVerity(FstabEntry* fstab_entry) { AvbHashtreeResult hashtree_result; // It's possible for a fstab_entry to have both avb_keys and avb flag. // In this case, try avb_keys first, then fallback to avb flag. if (!fstab_entry->avb_keys.empty()) { if (!InitAvbHandle()) return false; // Checks if hashtree should be disabled from the top-level /vbmeta. if (avb_handle_->status() == AvbHandleStatus::kHashtreeDisabled || avb_handle_->status() == AvbHandleStatus::kVerificationDisabled) { LOG(ERROR) << "Top-level vbmeta is disabled, skip Hashtree setup for " << fstab_entry->mount_point; return true; // Returns true to mount the partition directly. } else { auto avb_standalone_handle = AvbHandle::LoadAndVerifyVbmeta( *fstab_entry, preload_avb_key_blobs_[fstab_entry->avb_keys]); if (!avb_standalone_handle) { LOG(ERROR) << "Failed to load offline vbmeta for " << fstab_entry->mount_point; // Fallbacks to built-in hashtree if fs_mgr_flags.avb is set. if (!fstab_entry->fs_mgr_flags.avb) return false; LOG(INFO) << "Fallback to built-in hashtree for " << fstab_entry->mount_point; hashtree_result = avb_handle_->SetUpAvbHashtree(fstab_entry, false /* wait_for_verity_dev */); } else { // Sets up hashtree via the standalone handle. if (IsStandaloneImageRollback(*avb_handle_, *avb_standalone_handle, *fstab_entry)) { return false; } hashtree_result = avb_standalone_handle->SetUpAvbHashtree( fstab_entry, false /* wait_for_verity_dev */); } }
3、InitAvbHandle:
InitAvbHandle:AVB调用exterlan/avb/libavb库完成分区的校验,这个流程也非常的长,细节也非常的多,也是我们学习AVB的重点,需要另起一篇文章来介绍,本篇文章写不下了~
SetUpAvbHashtree也放到Dm verity文章中介绍吧,朋友们我会记得的,这两篇文章我有时间就会补上。
(前辈的这篇内容干活太多,必须下来好好研读一些,直接就是一个瑞斯拜!!!)