参考资料
感谢前辈的blog,安全相关的资料可太少了,很详细很卓越
https://blog.csdn.net/jackone12347/article/details/116241676
一、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文章中介绍吧,朋友们我会记得的,这两篇文章我有时间就会补上。
(前辈的这篇内容干活太多,必须下来好好研读一些,直接就是一个瑞斯拜!!!)