android AVB2.0(三)Init阶段安全启动流程
概要
前提:
本篇android AVB2.0学习总结系统的第三篇,前面两篇分别介绍了AVB2.0的配置,和Uboot中的AVB校验流程。
本篇将介绍Android Init阶段如何校验,以及相关会涉及的技术知识点。
本篇文档是基于android R版本代码。
============
**原创不易,转载就注明出处:https://blog.csdn.net/jackone12347/article/details/120088394
一、Init中哪个阶段校验及校验哪些分区?
在引导boot kernel后进入init,主要是在init的first stage(第一阶段)进行AVB校验的。在android需要挂载的分区挂载前执行校验。
需要校验的分区是在fstab中配置的,详细配置请查看AVB编译配置
二、Init代码分析
FirstStageMount流程
我们整体上了解一下Android是怎么设计把AVB校验流程穿插进来的,重点是理解google的设计思想和理解它的巧妙之处。
Init第一阶段的执行代码,整体流程上有点长,希望能耐心地研究一下代码。
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.cppint main(int argc, char** argv) {#if __has_feature(address_sanitizer)__asan_set_error_report_callback(AsanReportCallback);#endifif (!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,vbmetaif (IsDtVbmetaCompatible(fstab)) {return std::make_unique<FirstStageMountVBootV2>(std::move(fstab));} else {return std::make_unique<FirstStageMountVBootV1>(std::move(fstab));}}
IsDtVbmetaCompatible是检查kernel dtsi中是否有配置类似如下格式的数据:
firmware: firmware {compatible = "android,firmware";android {…};};
FirstStageMountVBootV2是FirstStageMount的子类,有三个和dm-verity相关方法InitDevices、SetUpDmVerity、InitAvbHandle,这三个方法基本上就把AVB校验的事情做完了,每一个都非常关键,建议大家重点分析。
我先大致说一下这三个方法的主要作用:
InitDevices:完成device mapper的映射,就是system/vendor分区如何映身到dm-x的
SetUpDmVerity:完成hash tree的使能,即dm-verity功能是怎么设置到kernel,将来设备运行时校验分区;
InitAvbHandle:完成AVB的libavb校验,确认vbmeta/system/vendor的签名是否合法。
如果没有这三个概念,估计也很难理解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;
}
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;
}
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)) {
那我们按顺序一个个地分析
CreateLogicalPartitions函数
函数调用中间过程,大家可以直接点击,最后调用到如下地方:
@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函数,关于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;
}
讲完了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 << "'";}
...
}
分析一下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 */);}}
InitAvbHandle:AVB调用exterlan/avb/libavb库完成分区的校验,这个流程也非常的长,细节也非常的多,也是我们学习AVB的重点,需要另起一篇文章来介绍,本篇文章写不下了~
SetUpAvbHashtree也放到Dm verity文章中介绍吧,朋友们我会记得的,这两篇文章我有时间就会补上。
为方便与大家及时交流,弄了一个微信公众号,欢迎大家留言沟通~
android AVB2.0(三)Init阶段安全启动流程相关推荐
- Android 4.0 ICS SystemUI浅析——SystemUI启动流程
阅读Android 4.0源码也有一段时间了,这次是针对SystemUI的一个学习过程.本文只是对SystemUI分析的一个开始--启动流程的分析,网上有很多关于2.3的SystemUI的分析,可4. ...
- AVB源码学习(三):AVB2.0 Init阶段安全启动流程
参考资料 感谢前辈的blog,安全相关的资料可太少了,很详细很卓越 https://blog.csdn.net/jackone12347/article/details/116241676 一.Ini ...
- Android 9.0 在init.rc中启动一个服务
现在有一个blink .bin文件,需要拷贝到/system/bin/目录下面去,然后再init.rc文件中启动该服务 一.init.rc文件中启动服务 1.在init.rc文件中启动服务 代码路径: ...
- android AVB2.0学习总结
看了一阵子android AVB2.0相关的内容,准备梳理一下相关知识点.平时一般都用word整理,现在想想感觉还是用CSDN来整理看着直观些,方便自己查看的同时,也把自己学到的知识与其他人分享,共勉 ...
- android AVB2.0(四)libavb库介绍
本篇android AVB2.0学习总结系统的第四篇,接上篇android AVB2.0(三)Init阶段安全启动流程, 这里介绍一下libavb库的详细实现. 请支持原创,原文链接:https:// ...
- android AVB2.0(二)Uboot阶段AVB2.0校验流程
android AVB2.0学习总结传送门 本篇属于android AVB2.0学习总结系列的第二篇文章,本篇主要介绍一下UBOOT或者UEFI阶段AVB2.0的介绍. 支持原创,转载请标明链接 ht ...
- android AVB2.0(一)工作原理及编译配置
android AVB2.0介绍,本篇主要介绍AVB2.0的概述和工作原理.配置和编译. 有关AVB2.0的其他子系统的介绍,请查看android AVB2.0学习总结 一.AVB2.0概述 什么是A ...
- Android10.0系统启动之Launcher(桌面)启动流程-[Android取经之路]
摘要:上一节我们讲完了Android10.0的ActivityManagerService的启动流程,在AMS的最后启动了Launcher进程,今天我们就来看看Launcher的真正启动流程. 阅读本 ...
- android avb2.0问题解答 汇总
前言android 系统安全内容总结 1.android vbmeta结构深入解析 2.android libavb深入解读 学习android avb2.0,看完上面结构与代码分析后,肯定对avb有 ...
最新文章
- html5 呼吸灯效果,jQuery仿地铁线路指示灯效果
- 标签传播算法(Label Propagation)及Python实现
- 《大众创业做电商——淘宝与微店 开店 运营 推广 一册通》一一1.3 选择创业的行业...
- 打造一个属于自己的应用服务自动监控警报程序
- STM32F1笔记(五)外部中断EXTI
- mysql 根据时间 获取上个月_MySQL[0]
- 品质LOGO模板素材|想知道平面设计师如何设计徽标的秘密吗?
- c语言中声明外部函数需要添加的关键字,C语言中声明和定义的区别——分析extern关键词。...
- 【STM32 .Net MF开发板学习-16】Zigbee遥控智能小车
- 冲击波病毒简介及解决方法
- java疯狂讲义pdf_《疯狂Java讲义(第3版)》PDF 下载
- Kubernets k8s中yml格式与pod yml格式
- [转]页面回传与js调用服务器端事件
- 拉卡拉遭联想控股减持:套现3亿 总经理陈烈辞职
- 霹雳猿教程网站正式上线
- 设计模式学习笔记(一)
- 由百家讲坛的《大隋风云-之流星王朝》想到的
- ufs2.2 协议扫盲(三)
- 成为一名CV(计算机视觉)工程师,你需要具备哪些能力?
- Python(Python+Qt)学习随笔:使用xlwings新建Execl文件和sheet的方法
热门文章
- 十大最具幸福感城市出炉 广州、深圳均榜上无名
- HTML input 默认值设置
- windows用户管理
- Frida安装到使用一目了然
- [已解决] Adding visible gpu devecies:
- CListCtrl控件详解
- 电路设计漫谈之:高频喜欢低感抗,低频喜欢低(电)阻抗 - 再谈接地
- 计算几何之大圆包含小圆问题
- Ubuntu 20.04 64位 Google Protocol ProtoV3 bufbuild buf 工具安装使用指南
- 怎样运行sql2008服务器,怎么开启SQL数据库服务