概述
Android 4.4 及更高版本支持通过可选的 device-mapper-verity (dm-verity) 内核功能进行的验证启动,以便对块设备进行透明的完整性检查。
验证启动要求在使用前以加密形式验证要启动的 Android 版本包含的所有可执行代码和数据,其中包括内核(从 boot 分区加载)、设备树(从 dtbo 分区加载)、system 分区和 vendor 分区等。
仅读取一次的小分区(例如 boot 和 dtbo)通常是通过将整个内容加载到内存中,然后计算相应哈希值来进行验证的。接下来,系统会比较这个计算出的哈希值与预期哈希值。如果值不一致,则 Android(kernel) 将无法加载。如需了解详情,请参阅 启动流程 。
内存装不下的较大分区(如system文件系统)可以使用哈希树;此时,验证流程会在将数据加载到内存时持续进行。在这种情况下,系统会在运行时计算哈希树的根哈希值,并对照预期根哈希值进行检查。Android 包含用于验证较大分区的 dm-verity 驱动程序。如果在某个时间点计算出的根哈希值与预期根哈希值不一致,系统便不会使用相应数据,而且 Android 会出现错误。如需了解详情,请参阅 dm-verity 损坏 。
预期哈希值通常存储在每个经过验证的分区的末尾或开头、专用分区中,或同时位于以上两个位置。最重要的是,这些哈希值是由信任根以直接或间接的方式签名的。
system等大分区是由kernel来验证的, 而kernel的验证是由bootloader来完成的, bootloader的验证又是由硬件来验证的(烧录了key的HW)。
本文只关心system分区的验证, 从原理上来说, 生成带hash/签名的system.img分为一下几个步骤:
- 生成 EXT4 system.img。
- 为system.img生成哈希树。
- 为该哈希树构建 dm-verity 表。
- 为该 dm-verity 表签名以生成表签名。
- 将表签名和 dm-verity 表绑定到 Verity 元数据。
- 将系统映像、Verity 元数据和哈希树组合起来。
概要设计
本文基于Android 6.0
详细设计
步骤/原理 及 定制
AOSP build系统中已经全部实现, 以下仅作原理解释, 定制部分参考 其他部分
生成 EXT4 system.img
主控脚本: build/tools/releasetools/build_image.py#BuildImage
通过 prop_dict
属性,判断是否启用 verity 功能,启用的话,调整 system 分区大小,以便后面添加相关文件到 system.img 末尾,然后 RunCommand(build_command) 生成初始的 system.img。
prop_dict
文件路径:
out/target/product/XXXX/obj/PACKAGING/systemimage_intermediates/system_image_info.txt
为system.img生成哈希树
1 | build/tools/releasetools/build_image.py#BuildVerityTree |
转到: system/extras/verity/build_verity_tree.cpp
为该哈希树构建 dm-verity 表
1 | build/tools/releasetools/build_image.py#BuildVerityMetadata |
转到 system/extras/verity/build_verity_metadata.py
1 | system/extras/verity/build_verity_metadata.py |
可见 dm-verity 表 其实就是包含了root hash和FIXED_SALT的一行字符串
为该 dm-verity 表签名以生成表签名
1 | system/extras/verity/build_verity_metadata.py |
将表签名和 dm-verity 表绑定到 Verity 元数据
1 | system/extras/verity/build_verity_metadata.py |
将系统映像、Verity 元数据和哈希树组合起来
回到 build/tools/releasetools/build_image.py
1
2
3
4
5
6
7
8
9133 def BuildVerifiedImage(data_image_path, verity_image_path,
134 verity_metadata_path):
135 if not Append2Simg(data_image_path, verity_metadata_path,
136 "Could not append verity metadata!"): #附上 dm-verity 表
137 return False
138 if not Append2Simg(data_image_path, verity_image_path,
139 "Could not append verity tree!"): #附上 hash tree
140 return False
141 return True
通过Append2Simg
, 包含hash tree 和 verity table的system.img就生成了。
如果是全局执行的make, 那么boot.img也已经生成了, 而用于验签system.img的pub key就位于boot image的 /verity_key 文件。
其他(定制部分)
定制 FIXED_SALT
用于生成hash tree时加的盐, 位于1
build/tools/releasetools/build_image.py
make 输出的结尾可以看到这个盐的值:
build_verity_tree -A 000087a5be3b982978c923f566a94613496b417f2af592639bc80d141e340000 out/target/product/XXX/obj/PACKAGING/systemimage_intermediates/system.img /tmp/tmptTpmT7_verity_images/verity.img
定制 keys
首先确定openssl 已经安装:
1
2$ openssl version
OpenSSL 1.0.1f 6 Jan 2014生成key pair:
1
2
3
4$ development/tools/make_key mykey '/C=CN/ST=NanJing/L=XinJieKou/O=MyCompany/OU=MyCompany/CN=MyCompany/emailAddress=Finalx@MyCompany.com.cn'
> 成果物:
> mykey.pk8 mykey.x509.pem从生成的 mykey.x509.pem 中解出public key:
1
2$ mmm system/extras/verity/ #build出来 generate_verity_key
$ out/host/linux-x86/bin/generate_verity_key -convert mykey.x509.pem verity_key将pub key, private key, CA copy到
build/target/product/security
以便后续make/build_image.py使用:1
2
3$ cp -v ./mykey.pk8 xxx/build/target/product/security/verity.pk8 #私钥, build image 时用于sign
$ cp -v ./verity_key.pub xxx/build/target/product/security/verity_key #公钥, 会被build 到ramdisk里用于开机时验签
$ cp -v ./mykey.x509.pem xxx/build/target/product/security/verity.x509.pem # 证书, generate_verity_key从此中解出pub key
使能
分区使能
修改fstab
需要在 fstab 中的fs_mgr_flags增加verity flag:1
2
3# Android fstab file.
#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
/dev/block/mmcblk2p2 /system ext4 ro,barrier=1 wait,verify
修改verity data的mount point
1 | # setup dm-verity configs. |
Kernel 使能
1 | diff --git a/arch/arm/configs/imx_v7_x37_android_defconfig b/arch/arm/configs/imx_v7_x37_android_defconfig |
切换到基于块(默认是基于文件)的OTA
1 | diff --git a/core/Makefile b/core/Makefile |
验证
如果boot.img中的/verity_key和buildsystem.img时使用的verity.pk8不匹配,无法boot, 串口输出:
[ 1.163454] bootwatch: Current version: 3.1.4.20180908112541.rc1.f0307h
[ 1.181602] init:acc_status = 1
[ 1.184756] init:wakesrc = 0x0
[ 1.497576] fs_mgr: Couldn’t verify table.fs_mgr: Enabling dm-verity for system (mode 0)
[ 1.512108] device-mapper: verity: 179:2: metadata block 634959 is corrupted
[ 1.519796] EXT4-fs (dm-0): unable to read superblock
[ 1.525896] device-mapper: verity: 179:2: metadata block 634959 is corrupted
[ 1.534057] device-mapper: verity: 179:2: metadata block 634959 is corrupted
[ 1.541641] Buffer I/O error on dev dm-0, logical block 0, async page read
[ 1.548696] fs_mgr: Failed to mount an un-encryptable or wiped partition on/dev/block/dm-0 at /system options: barrier=1 error: Invalid argument
[ 2.281519] init: fs_mgr_mount_all returned an error在获得了root权限的情况下, 即使remount成功, system.img也无法被修改
Known Issue(x37)
$ adb disable-verity
之后无法boot
[ 1.500791] fs_mgr: Couldn’t find verity metadata at offset 2600759296!
[ 1.507880] fs_mgr: Could not set up verified partition, skipping!
[ 1.701514] init: mount_all ret-0.