一、Device Mapper
1. Device Mapper概述
Device mapper是LINUX提供的一种逻辑设备到物理设备的映射框架,中间传递消息的是用户自定义的target driver插件,用户可以编写好具体的IO请求的target driver就行,用户层可以使用ioctl命令的方式向底层进行通讯。
target driver主要定义对IO请求的处理规则,在device mapper中对target driver的操作已定义好了统一的接口,可实现几个常见的方法就行。同时device mapper提供了一种底层消息上报的机制,通过target driver将事件消息传递到用户空间。
在android的init启动的第一阶段中主要调用了InitDeviceMapper函数,来进行device mapper的初始化
2. Device Mapper的使用
在android的init启动的第一阶段中主要调用了InitDeviceMapper函数,来进行device mapper的初始化。
分析一下代码@block_dev_initializer.cpp
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)); } bool BlockDevInitializer::InitDeviceMapper() { const std::string dm_path = "/devices/virtual/misc/device-mapper"; bool found = false; auto dm_callback = [this, &dm_path, &found](const Uevent& uevent) { if (uevent.path == dm_path) { device_handler_->HandleUevent(uevent); found = true; return ListenerAction::kStop; } return ListenerAction::kContinue; }; uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path, dm_callback); if (!found) { Timer t; uevent_listener_.Poll(dm_callback, 10s); } if (!found) { return false; } return true; }
调用InitDmDevice函数初始化Device mapper 设备。通过代码分析来看,直观的功能是通过uevent上报的节点,在其中去查找dtsi中配置的各个分区;
如果找到了就从device集合中删除,添加个10秒的等待,如果10秒内都找不到这个分区,说明此分区可能没有挂载到文件系统,或者配置出错。
bool BlockDevInitializer::InitDmDevice(const std::string& device) { auto uevent_callback = [&device_name, &device, this, &found](const Uevent& uevent) { if (uevent.device_name == device_name) { device_handler_->HandleUevent(uevent); found = true; return ListenerAction::kStop; } return ListenerAction::kContinue; }; uevent_listener_.RegenerateUeventsForPath(syspath, uevent_callback); if (!found) { Timer t; uevent_listener_.Poll(uevent_callback, 10s); } … return true; } ListenerAction BlockDevInitializer::HandleUevent(const Uevent& uevent, std::set<std::string>* devices) { auto name = uevent.partition_name; if (name.empty()) { size_t base_idx = uevent.path.rfind('/'); if (base_idx == std::string::npos) { return ListenerAction::kContinue; } name = uevent.path.substr(base_idx + 1); } auto iter = devices->find(name); if (iter == devices->end()) { return ListenerAction::kContinue; } devices->erase(iter); device_handler_->HandleUevent(uevent); return devices->empty() ? ListenerAction::kStop : ListenerAction::kContinue; }
二、Dm Verity
1. Dm Verity验证思想
上层应用通过文件系统读取某个block时,调用dm-verity读取对应的block,
dm-verity会根据verity-table调用block_device读取对应的block,然后逐层计算到根block,
计算得到root-hash和kernel中保存的root hash进行对比,hash值进行比较,如果相等,则说明该块没有被修改过,一切正常。
2. Hashtree脚本处理
2.1 镜像编译
编译system.img时调用了build_image.py脚本
define build-systemimage-target @echo "Target system fs image: $(1)" $(call generate-userimage-prop-dictionary, $(systemimage_intermediates)/system_image_info.txt, \ skip_fsck=true) $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \ build/make/tools/releasetools/build_image.py \ $(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1) $(TARGET_OUT) \
2.2 创建verity tree
/build/tools/releasetools/build_image.py中,调用build_verity_tree程序
def BuildImage(in_dir, prop_dict, out_file, target_out=None): if verity_supported and is_verity_partition: if not MakeVerityEnabledImage(out_file, verity_fec_supported, prop_dict): return False def MakeVerityEnabledImage(out_file, fec_supported, prop_dict): # build the verity tree and get the root hash and salt if not BuildVerityTree(out_file, verity_image_path, prop_dict): return False def BuildVerityTree(sparse_image_path, verity_image_path, prop_dict): cmd = ["build_verity_tree", "-A", FIXED_SALT, sparse_image_path, verity_image_path] output, exit_code = RunCommand(cmd) if exit_code != 0: print("Could not build verity tree! Error: %s" % output) return False root, salt = output.split() prop_dict["verity_root_hash"] = root prop_dict["verity_salt"] = salt return True
调用到system\extras\verity\build_verity_tree.cpp,
过程包括:
根据block_size大小和hash_size大小,计算得到一个block可以存放的hash个数从level 0开始,计算需要多少个level;
创建数组verity_tree_levels, 用于指示每一个level在verity_tree中的起始地址;
创建数组verity_tree_level_blocks, 用于指示每一个level在verity_tree中的长度;
计算ext4 image中各个块的hash值存到verity_tree的level0,然后逐层往上计算各层的hash值,并填入verity_tree中相应的level。
2.3 创建metadata
def build_verity_metadata(data_blocks, metadata_image, root_hash, salt, block_device, signer_path, signing_key, signer_args=None, verity_disable=False): # build the verity table verity_table = build_verity_table(block_device, data_blocks, root_hash, salt) # build the verity table signature signature = sign_verity_table(verity_table, signer_path, signing_key, signer_args) # build the metadata block metadata_block = build_metadata_block(verity_table, signature, verity_disable) # write it to the outfile with open(metadata_image, "wb") as f: f.write(metadata_block)
build_verity_table函数中:
- 第一个参数block_device描述了dm-verity设备对应了那个底层的block
- 第二个参数block_device指定了hash-tree存在于哪个block设备上,
BLOCK_SIZE参数为默认的4k, data_blocks描述了有多少个block,data_blocks + (METADATA_SIZE / BLOCK_SIZE)表示hash-tree在对应block设备上的偏移位置,
最后可找到hash-tree,root_hash为hash-tree的根hash。
另外,verity table是存在分区中的super block(超级块)之后的位置,便于找到校验的位置。
def build_verity_table(block_device, data_blocks, root_hash, salt): table = "1 %s %s %s %s %s %s sha256 %s %s" table %= ( block_device, block_device, BLOCK_SIZE, BLOCK_SIZE, data_blocks, data_blocks, root_hash, salt) return table
3. Dm verity设备的创建
3.1 SetUpDmVerity函数
调用的入口在Init的第一阶段的SetUpDmVerity函数。
Fstab::iterator end; if (!MountPartition(current, false /* erase_same_mounts */, &end)) { … } 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; } } else if (fstab_entry->fs_mgr_flags.avb) { //InitAvbHandle完成此分区的AVB验证 if (!InitAvbHandle()) return false; #然后执行创建hash tree table并建立与driver侧的ioctl交互连接。 hashtree_result = avb_handle_->SetUpAvbHashtree(fstab_entry, false /* wait_for_verity_dev */);
相关代码在:system\core\fs_mgr\libfs_avb\ avb_util.cpp
3.2 hash table处理
Hash table传入的格式如下,中间做了序列号处理,其实就是一串长值拼成的字串。
调用的入口在Init的第一阶段的SetUpDmVerity函数。
android::dm::DmTargetVerity target( 0, hashtree_desc.image_size / 512, hashtree_desc.dm_verity_version, blk_device, blk_device, hashtree_desc.data_block_size, hashtree_desc.hash_block_size, hashtree_desc.image_size / hashtree_desc.data_block_size, hashtree_desc.tree_offset / hashtree_desc.hash_block_size, hash_algorithm.str(), hashtree_desc.root_digest, hashtree_desc.salt);
CreateDevice函数里面有三个函数,分别实现了上述的几个ioctl命令,把拼接好的hashtable传到驱动侧。
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; }
对应的IOCTL命令
ioctl(fd_, DM_DEV_CREATE, &io) ioctl(fd_, DM_TABLE_LOAD, io) ioctl(fd_, DM_DEV_SUSPEND, io)
好了,以上就是Device Mapper和Dm verity的简介了,实际上的内容会更多,大家学习的过程中多多发现吧。
小结(白话文+实战)
ing