AVB源码学习(三):AVB2.0 Init阶段安全启动流程

参考资料

感谢前辈的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文章中介绍吧,朋友们我会记得的,这两篇文章我有时间就会补上。

(前辈的这篇内容干活太多,必须下来好好研读一些,直接就是一个瑞斯拜!!!)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TrustZone_Hcoco

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值