Android中开启DM-VERITY

概述

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
2
3
4
5
build/tools/releasetools/build_image.py#BuildVerityTree
89 def BuildVerityTree(sparse_image_path, verity_image_path, prop_dict):
90 cmd = "build_verity_tree -A %s %s %s" % (
91 FIXED_SALT, sparse_image_path, verity_image_path)
93 status, output = commands.getstatusoutput(cmd)

转到: system/extras/verity/build_verity_tree.cpp


为该哈希树构建 dm-verity 表

1
2
3
4
5
6
7
8
build/tools/releasetools/build_image.py#BuildVerityMetadata
102 def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt,
103 block_device, signer_path, key):
104 cmd_template = (
105 "system/extras/verity/build_verity_metadata.py %s %s %s %s %s %s %s")
106 cmd = cmd_template % (image_size, verity_metadata_path, root_hash, salt,
107 block_device, signer_path, key)
109 status, output = commands.getstatusoutput(cmd)

转到 system/extras/verity/build_verity_metadata.py

1
2
3
4
5
6
system/extras/verity/build_verity_metadata.py
40 def build_verity_table(block_device, data_blocks, root_hash, salt):
41 table = "1 %s %s %s %s %s %s sha256 %s %s"
42 table %= ( block_device, block_device, BLOCK_SIZE, BLOCK_SIZE, data_blocks,
47 data_blocks + (METADATA_SIZE / BLOCK_SIZE), root_hash, salt)
50 return table

可见 dm-verity 表 其实就是包含了root hashFIXED_SALT的一行字符串


为该 dm-verity 表签名以生成表签名

1
2
3
4
5
6
7
system/extras/verity/build_verity_metadata.py
30 def sign_verity_table(table, signer_path, key_path):
31 with tempfile.NamedTemporaryFile(suffix='.table') as table_file:
32 with tempfile.NamedTemporaryFile(suffix='.sig') as signature_file:
33 table_file.write(table)
34 table_file.flush()
35 cmd = " ".join((signer_path, table_file.name, key_path, signature_file.name))

将表签名和 dm-verity 表绑定到 Verity 元数据

1
2
3
4
5
system/extras/verity/build_verity_metadata.py
23 def build_metadata_block(verity_table, signature):
24 table_len = len(verity_table)
25 block = struct.pack("II256sI", MAGIC_NUMBER, VERSION, signature, table_len)
26 block += verity_table

将系统映像、Verity 元数据和哈希树组合起来

回到 build/tools/releasetools/build_image.py

1
2
3
4
5
6
7
8
9
133 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
2
3
4
 # setup dm-verity configs.
ifneq ($(BUILD_TARGET_DEVICE),sd)
- PRODUCT_SYSTEM_VERITY_PARTITION := /dev/block/mmcblk3p5
+ PRODUCT_SYSTEM_VERITY_PARTITION := /dev/block/mmcblk2p2

Kernel 使能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
diff --git a/arch/arm/configs/imx_v7_x37_android_defconfig b/arch/arm/configs/imx_v7_x37_android_defconfig
index 05270584dec2..475566bd5da9 100644
--- a/arch/arm/configs/imx_v7_x37_android_defconfig
+++ b/arch/arm/configs/imx_v7_x37_android_defconfig
@@ -1376,11 +1376,11 @@ CONFIG_MD=y
# CONFIG_BLK_DEV_MD is not set
# CONFIG_BCACHE is not set
CONFIG_BLK_DEV_DM_BUILTIN=y
-CONFIG_BLK_DEV_DM=m
+CONFIG_BLK_DEV_DM=y
# CONFIG_DM_MQ_DEFAULT is not set
# CONFIG_DM_DEBUG is not set
-CONFIG_DM_BUFIO=m
-CONFIG_DM_CRYPT=m
+CONFIG_DM_BUFIO=y
+CONFIG_DM_CRYPT=y
# CONFIG_DM_SNAPSHOT is not set
# CONFIG_DM_THIN_PROVISIONING is not set
# CONFIG_DM_CACHE is not set
@@ -1392,7 +1392,7 @@ CONFIG_DM_CRYPT=m
# CONFIG_DM_DELAY is not set
CONFIG_DM_UEVENT=y
# CONFIG_DM_FLAKEY is not set
-CONFIG_DM_VERITY=m
+CONFIG_DM_VERITY=y
# CONFIG_DM_SWITCH is not set
# CONFIG_DM_LOG_WRITES is not set
# CONFIG_TARGET_CORE is not set

切换到基于块(默认是基于文件)的OTA

1
2
3
4
5
6
7
8
9
10
11
diff --git a/core/Makefile b/core/Makefile
index 99444342c..731a2270b 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -2000,7 +2000,7 @@ ota_name := update-$(ota_name)

block_file :=
ifneq ($(TARGET_USERIMAGES_USE_UBIFS),true)
-#block_file := --block
+ block_file := --block
endif

验证

  • 如果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.