LK 阶段的 Android Verified Boot (AVB) 校验
一、引言
Android Verified Boot 2.0(AVB,也称
libavb)是 Android 从 8.0 开始引入的一套启动完整性校验框架。在 MT8766/MT6761 平台上,AVB 的验签逻辑完全实现在 LK(Little Kernel)阶段,内核不再重复验签,仅通过 dm-verity 做运行时完整性保障。
本文将基于实际源码,从架构设计、代码实现、密钥管理、防回滚、错误处理、攻击面等维度,对 LK 中的 AVB 实现做深度分析。
二、整体架构:三层模型
MTK LK 的 AVB 实现分为三层:
┌───────────────────────────────────────────────────────────┐
│ 上层: load_vfy_boot.c (MTK 集成层) │
│ – 内存管理、分区预加载 │
│ – boot/recovery 选择 │
│ – cmdline/DTB 后处理 │
│ – 反回滚版本号记录 │
│ – boot state 管理 │
├───────────────────────────────────────────────────────────┤
│ 中间层: libavb (Google 官方库) │
│ – avb_slot_verify() 主流程 │
│ – load_and_verify_vbmeta() 递归验证 │
│ – load_and_verify_hash_partition() 哈希校验 │
│ – avb_vbmeta_image_verify() 签名验证 │
│ – chain partition 处理 │
├───────────────────────────────────────────────────────────┤
│ 底层: avb_hal.c (MTK 硬件抽象层) │
│ – 分区读写 (eMMC/UFS) │
│ – 公钥验证 (sec_set_pubk → TEE) │
│ – Rollback Index 读写 (OTP efuse) │
│ – 设备锁状态 │
│ – Persistent Value 存储 │
└───────────────────────────────────────────────────────────┘
三、顶层入口:load_vfy_boot() 详细分析
3.1 调用链
mt_boot_init()
└── boot_linux_from_storage()
└── load_vfy_boot(BOOTIMG_TYPE_BOOT, CFG_BOOTIMG_LOAD_ADDR) [load_vfy_boot.c]
3.2 load_vfy_boot() 执行流
uint32_t load_vfy_boot(uint32_t bootimg_type, uint32_t addr)
{
// [load_vfy_boot.c]
// 1. 构建请求分区列表
build_up_boot_partitions(boot_partitions, boot_partitions_sz);
// → 如果 vendor_boot 分区存在,追加到请求列表
// 2. 初始化 AVB 堆 (140MB 对齐 DRAM 保留区)
avb_heap_sz = AVB_HEAP_SZ; // 140 MB
avb_heap = mblock_reserve_ext(…, avb_heap_sz, …);
// 3. Authentication 检查
boot_authentication(bootimg_type);
// → 检查是否需要签名验证 (由安全策略决定)
// =0 时设置 AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR
// 4. dm-verity 状态检测
get_dm_verity_status(&dm_status);
if (dm_status)
avb_flag |= AVB_SLOT_VERIFY_FLAGS_RESTART_CAUSED_BY_HASHTREE_CORRUPTION;
// 5. 持久化存储初始化 (persist partition)
init_persist_value(&ops, PERSIST_PART_NAME, …);
// 6. 核心验证: avb_slot_verify()
avb_ret = avb_slot_verify(&ops,
boot_partitions, // 请求分区
get_suffix(), // A/B slot
avb_flag,
hashtree_error_mode,
&slot_data);
// 7. 验证后处理
if (avb_ret == AVB_SLOT_VERIFY_RESULT_OK) {
boot_post_processing(&ops, bootimg_type, slot_data);
// ├─ collect_rot_info() – 收集 Root of Trust
// ├─ prepare_kernel_dtb() – 准备 DTB
// ├─ avb_cmdline_postprocessing() – 写入 DTB
// └─ cmdline_append() – 追加 cmdline
g_boot_state = BOOT_STATE_GREEN;
}
// 8. 设备解锁状态覆盖
if (lock_state == LKS_UNLOCK)
g_boot_state = BOOT_STATE_ORANGE;
// 9. dm-verity 错误处理
dm_verity_handler(hashtree_error_mode);
// 10. 最终状态裁决
vboot_state_result(bootimg_type);
}
安全设计要点:
- 140MB 堆空间用于加载分区数据,足以容纳整个 boot 镜像
- avb_heap 使用 mblock_reserve_ext() 保留在物理内存中,不经过 LK 的普通堆分配,防止内存碎片
- boot_authentication() 允许在非安全构建中跳过签名验证(ALLOW_VERIFICATION_ERROR 标志),但此时 g_boot_state 保持 RED
四、核心引擎:avb_slot_verify() 深度剖析
4.1 主流程
AvbSlotVerifyResult avb_slot_verify(
AvbOps* ops,
const char* const* requested_partitions, // 请求分区列表
const char* ab_suffix, // "_a" 或 "_b"
AvbSlotVerifyFlags flags,
AvbHashtreeErrorMode hashtree_error_mode,
AvbSlotVerifyData** out_data)
{
// [avb_slot_verify.c]
// 创建 slot_data 结构体 (包含所有验证结果)
slot_data = avb_calloc(sizeof(AvbSlotVerifyData));
slot_data->vbmeta_images = avb_calloc(… MAX_NUMBER_OF_VBMETA_IMAGES); // 32
slot_data->loaded_partitions = avb_calloc(… MAX_NUMBER_OF_LOADED_PARTITIONS); // 32
// 两条路径:
if (flags & AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION) {
// 路径A: 无独立 vbmeta 分区, 直接从 boot 分区找
for (每个请求分区)
load_and_verify_vbmeta(…, requested_partitions[n], …);
} else {
// 路径B: 标准路径, 从 vbmeta 分区开始
ret = load_and_verify_vbmeta(ops,
"vbmeta", // ← 从 vbmeta 分区加载
ab_suffix,
…);
}
// 处理验证结果:
// – 将 vbmeta digest 等信息注入 cmdline
// – 设置 rollback_indexes
// – 处理 flags(VERIFICATION_DISABLED/HASHTREE_DISABLED)
}
关键设计决策: 两条路径的存在是为了兼容两种分区布局:
4.2 load_and_verify_vbmeta() — 递归验证核心
这是整个 AVB 最核心的函数,递归地从主 vbmeta 开始,沿着 chain descriptor 遍历所有子 vbmeta:
static AvbSlotVerifyResult load_and_verify_vbmeta(
AvbOps* ops,
const char* const* requested_partitions,
const char* ab_suffix,
AvbSlotVerifyFlags flags,
bool allow_verification_error,
uint32_t rollback_index_location,
const char* partition_name, // 要验证的分区名
size_t partition_name_len,
const uint8_t* expected_public_key, // chain 指定的公钥 (NULL = 主 vbmeta)
size_t expected_public_key_length,
AvbSlotVerifyData* slot_data,
AvbAlgorithmType* out_algorithm_type,
AvbCmdlineSubstList* additional_cmdline_subst)
{
// [avb_slot_verify.c]
// Step 1: 判断是否为 main vbmeta
is_main_vbmeta = (rollback_index_location == 0);
// Step 2: 判断是否需要查找 AVB Footer
// vbmeta/vbmeta_xxx 分区 → 从头开始 (no footer)
// boot/recovery 等分区 → 末尾找 AVB Footer
look_for_vbmeta_footer = true;
if (partition_name starts with "vbmeta")
look_for_vbmeta_footer = false;
// Step 3: 构造完整分区名 (加上 A/B suffix)
snprintf(full_partition_name, "vbmeta_a");
// Step 4: 读取 vbmeta 数据
// 有 footer: 先读 footer, 从 footer.vbmeta_offset 读取
// 无 footer: 从 offset 0 读取最多 64KB
if (look_for_vbmeta_footer) {
ops->read_from_partition(ops, full_partition_name, -AVB_FOOTER_SIZE, …);
if (footer 有效)
vbmeta_offset = footer.vbmeta_offset;
}
ops->read_from_partition(ops, full_partition_name, vbmeta_offset, vbmeta_size, …);
// Step 5: 签名验证 (RSA + SHA256/SHA512)
vbmeta_ret = avb_vbmeta_image_verify(vbmeta_buf, vbmeta_num_read,
&pk_data, &pk_len);
// Step 6: 公钥验证
if (expected_public_key != NULL) {
// chain partition: 比对公钥是否匹配 chain descriptor 中的公钥
if (expected_public_key match pk_data) → OK
else → AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED
} else {
// main vbmeta: 调用 ops->validate_vbmeta_public_key()
// → 进入 avb_hal_verify_public_key() [见第五章]
}
// Step 7: Rollback Index 检查
ops->read_rollback_index(ops, rollback_index_location, &stored_rollback_index);
if (vbmeta_header.rollback_index < stored_rollback_index)
→ AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
// Step 8: 处理 descriptors
for (n = 0; n < num_descriptors; n++) {
switch (descriptors[n].tag) {
case AVB_DESCRIPTOR_TAG_HASH:
// → 验证分区数据哈希 [见4.3]
load_and_verify_hash_partition(…);
break;
case AVB_DESCRIPTOR_TAG_CHAIN_PARTITION: //vbmeta_system/vendor
// → 递归调用自身
if (!is_main_vbmeta) // 只允许 main vbmeta 包含 chain
→ ERROR_INVALID_METADATA
load_and_verify_vbmeta(ops,
chain_partition_name, // "vbmeta_system"/"vbmeta_vendor"
chain_public_key, // 子 vbmeta 专用公钥
chain_desc.rollback_index_location, // 独立 rollback 位置
…);
break;
case AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE:
// 收集 cmdline 片段
break;
case AVB_DESCRIPTOR_TAG_HASHTREE:
// dm-verity 描述符,LK 不做验证,交给内核
break;
}
}
}
安全要点——chain 的限制:
// [avb_slot_verify.c]
// Only allow CHAIN_PARTITION descriptors in the main vbmeta image.
if (!is_main_vbmeta) {
ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
goto out;
}
只有 main vbmeta 可以包含 chain descriptor,子 vbmeta 发现 chain descriptor 直接报错。这防止了 chain 的深层嵌套攻击。
子 vbmeta 的 flags 也必须为 0:
// [avb_slot_verify.c]
if (vbmeta_header.flags != 0) {
ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
}
防止子 vbmeta 设置危险的 flags(如 VERIFICATION_DISABLED)。
4.3 Hash Partition 验证
当 descriptor 类型为 AVB_DESCRIPTOR_TAG_HASH 时,执行分区数据完整性校验:
static AvbSlotVerifyResult load_and_verify_hash_partition(
AvbOps* ops, …) // [avb_slot_verify.c:270-443]
{
// 解析 Hash Descriptor
avb_hash_descriptor_validate_and_byteswap(…);
// ├─ partition_name: 分区名 (如 "boot")
// ├─ hash_algorithm: "sha256" / "sha512"
// ├─ salt: 随机盐值 (防御彩虹表)
// ├─ image_size: 预期的镜像大小
// └─ digest: 预期的哈希值
// 如果分区不在请求列表中, 跳过
if (partition not in requested_partitions) return OK;
// 加载完整分区数据
load_full_partition(ops, part_name, image_size, &image_buf, …);
// 计算哈希: SHA256(salt || partition_data)
if (strcmp(hash_algorithm, "sha256") == 0) {
avb_sha256_init(&sha256_ctx);
avb_sha256_update(&sha256_ctx, salt, salt_len);
avb_sha256_update(&sha256_ctx, image_buf, image_size);
digest = avb_sha256_final(&sha256_ctx);
} else if (strcmp(hash_algorithm, "sha512") == 0) {
// 同样, SHA512(salt || data)
}
// 比对哈希
if (avb_safe_memcmp(digest, expected_digest, digest_len) != 0) {
→ AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION
// "Hash of data does not match digest in descriptor."
}
}
安全要点——allow_verification_error 下的放宽:
// [avb_slot_verify.c]
if (allow_verification_error) {
image_size = 整个分区大小; // 而不是 descriptor 中的 image_size
}
当设备解锁后允许验证错误时,加载整个分区而不是 descriptor 指定的范围,使 "fastboot flash boot" 刷不同大小的镜像也能启动。
五、底层 HAL:avb_hal.c 深度分析
5.1 AvbOps 结构体 [avb_hal.c]
AvbOps ops = {
.read_from_partition = avb_hal_read_from_partition,
.get_preloaded_partition = avb_hal_get_preloaded_partition, // 可选
.write_to_partition = avb_hal_write_to_partition, // 用于 persist
.validate_vbmeta_public_key = avb_hal_verify_public_key, // 公钥验证
.read_rollback_index = avb_hal_read_rollback_index, // rollback
.write_rollback_index = avb_hal_write_rollback_index, // rollback
.read_is_device_unlocked = avb_hal_read_is_device_unlocked,
.get_unique_guid_for_partition = avb_hal_get_unique_guid_for_partition,
.get_size_of_partition = avb_hal_get_size_of_partition,
.read_persistent_value = avb_hal_read_persistent_value,
.write_persistent_value = avb_hal_write_persistent_value,
.validate_public_key_for_partition = avb_hal_validate_public_key_for_partition,
};
5.2 公钥验证:信任锚点 [avb_hal.c:206-263]
这是 AVB 信任链的根——编译时嵌入的公钥:
avb_hal_verify_public_key() 的实现:
#ifndef __AVBKEY__
#define __AVBKEY__
#define AVB_PUBK_SZ 256
#define AVB_PUBK 0xC6, 0x55, 0x51, 0xDD, 0x32, 0x24, 0xA2, 0xE0, 0x0E, 0xBC, 0x7E, 0xFD, 0xBD, 0xA2, 0x53, 0x80, \\
0x58, 0x69, 0x7E, 0xF5, 0x4A, 0x40, 0x87, 0x95, 0x90, 0x54, 0x59, 0x3D, 0x55, 0xCA, 0xFF, 0x36, \\
0x34, 0x1A, 0xFA, 0xE1, 0xE0, 0x90, 0x2A, 0x1A, 0x32, 0x68, 0x5B, 0xF3, 0xDF, 0xAD, 0x0B, 0xF9, \\
0xB1, 0xD0, 0xF7, 0xEA, 0xAB, 0x47, 0x1F, 0x76, 0xBE, 0x1B, 0x98, 0x4B, 0x67, 0xA3, 0x62, 0xFA, \\
0xDF, 0xE6, 0xB5, 0xF8, 0xEE, 0x73, 0x16, 0x5F, 0xB8, 0xB1, 0x82, 0xDE, 0x49, 0x89, 0xD5, 0x3D, \\
0xD7, 0xA8, 0x42, 0x99, 0x81, 0x75, 0xC8, 0xD8, 0x84, 0x7B, 0xBD, 0x54, 0xA8, 0x22, 0x64, 0x44, \\
0xBC, 0x34, 0x06, 0x10, 0x3C, 0x89, 0xC2, 0xD1, 0xF3, 0x2C, 0x03, 0x65, 0x91, 0xB1, 0xA0, 0xD1, \\
0xC8, 0x21, 0x56, 0x15, 0x99, 0x48, 0x20, 0x27, 0x74, 0xEF, 0x01, 0x7A, 0x76, 0xA5, 0x0B, 0x6B, \\
0xFD, 0xE3, 0xFA, 0xED, 0x0D, 0xF9, 0x0F, 0x7A, 0x41, 0xFA, 0x76, 0x05, 0x37, 0x49, 0xFE, 0x34, \\
0x4F, 0x4B, 0x01, 0x49, 0xE4, 0x98, 0xF7, 0x89, 0x8E, 0xCD, 0x36, 0xAA, 0x39, 0x1D, 0xA9, 0x7D, \\
0x5D, 0x6B, 0x5A, 0x52, 0xD1, 0x75, 0x69, 0xA8, 0xDF, 0x7C, 0xDE, 0x1C, 0x1B, 0xF9, 0xD9, 0x19, \\
0x5B, 0xB7, 0x47, 0x4C, 0xB9, 0x70, 0x2E, 0xAD, 0xE5, 0xD6, 0x88, 0x7C, 0xED, 0x92, 0x6E, 0x46, \\
0x08, 0x10, 0xB5, 0x76, 0x03, 0x3E, 0x09, 0xAC, 0x4D, 0xB6, 0x2C, 0xCD, 0x12, 0x00, 0xBD, 0xD4, \\
0xA7, 0x03, 0xD3, 0x1B, 0x91, 0x08, 0x23, 0x36, 0x5B, 0x11, 0xFE, 0xAF, 0x59, 0x69, 0xB3, 0x3C, \\
0x88, 0x24, 0x37, 0x2D, 0x61, 0xBA, 0xC5, 0x99, 0x51, 0x18, 0x97, 0xF9, 0x23, 0x42, 0x96, 0x9F, \\
0x87, 0x2E, 0xCD, 0xB2, 0x4D, 0x5F, 0xA9, 0x24, 0xF2, 0x45, 0xDA, 0xE2, 0x65, 0x26, 0x26, 0x4D
#endif /*__AVB_KEY__ */
// 编译时从 key 文件生成并链接
uint8_t g_avb_key[AVB_PUBK_SZ] = {AVB_PUBK};
uint8_t g_avb_recovery_key[AVB_RECOVERY_PUBK_SZ] = {AVB_RECOVERY_PUBK};
avb_hal_verify_public_key() 的实现:
AvbIOResult avb_hal_verify_public_key(AvbOps *ops, …) {
// 1. 提取 keysize(检查 key 结构头和大小)
pubk_sz = avb_htobe32(key_hdr->key_num_bits) / 8;
if (pubk_sz != AVB_PUBK_SZ) {
// key 大小不匹配 → 不受信任
*out_is_trusted = FALSE;
goto end;
}
// 2. 注入 TEE: sec_set_pubk(pubk, pubk_sz) ← 关键操作
// 将公钥传递给 TEE (ATF/TrustZone), TEE 后续用此 key
// 验证其他内容的签名
sec_set_pubk(pubk, pubk_sz);
// 3. 比对编译时公钥
if (memcmp(g_avb_key, pubk, pubk_sz) == 0) {
*out_is_trusted = TRUE; // 匹配 → 信任
} else {
// 不匹配:
MTK_SECURITY_YELLOW_STATE_SUPPORT 时:
*out_is_trusted = TRUE; // 仍信任但标记 YELLOW
g_vb_custom_key_exist = 1; // 自定义 key 标志
非 YELLOW_STATE 时:
*out_is_trusted = FALSE; // 不信任 → 验证失败
}
}
安全设计分析:
|
场景 |
out_is_trusted |
g_boot_state |
结果 |
|
key 完全匹配g_avb_key |
TRUE |
GREEN |
信任,正常启动 |
|
key 不匹配 + YELLOW_SUPPORT |
TRUE |
YELLOW |
信任,显示黄色警告 |
|
key 不匹配 + !YELLOW_SUPPORT |
FALSE |
RED |
拒绝启动 |
|
key 大小异常 |
FALSE |
RED |
拒绝启动 |
关键攻击面: g_avb_key 在编译时嵌入到 LK 镜像中。如果攻击者能替换 LK 镜像的公钥(但 LK 本身被 Preloader 的签名保护),就可以用自己的 key 签名 vmbata。这正是链式信任要解决的问题——LK 自身被 Preloader 用 OTP 中的根公钥验证。
5.3 分区读写 [avb_hal.c]
AvbIOResult avb_hal_read_from_partition(AvbOps *ops, …) {
// offset < 0 → 从分区末尾偏移(读取 AVB Footer)
get_abs_offset(partition, offset, &abs_offset);
partition_read(partition, abs_offset, buffer, num_bytes);
}
AvbIOResult avb_hal_get_preloaded_partition(AvbOps *ops, …) {
// 检查分区是否被预加载(提前读入内存加速)
get_preload_partition_addr_sz(partition, &preload_addr, &preload_sz);
if (成功) {
*out_pointer = preload_addr; // 直接指向预加载地址,零拷贝
}
}
PRELOAD_PARTITION_SUPPORT 是一个优化:在进入 AVB 之前,Preloader 或 LK 早期阶段已经将 vbmeta/boot 分区预加载到内存中,AVB 层通过零拷贝直接访问,避免重复读取 eMMC。
5.4 Rollback Index 读取:OTP efuse 操作
AvbIOResult avb_hal_read_from_partition(AvbOps *ops, …) {
// offset < 0 → 从分区末尾偏移(读取 AVB Footer)
get_abs_offset(partition, offset, &abs_offset);
partition_read(partition, abs_offset, buffer, num_bytes);
}
AvbIOResult avb_hal_get_preloaded_partition(AvbOps *ops, …) {
// 检查分区是否被预加载(提前读入内存加速)
get_preload_partition_addr_sz(partition, &preload_addr, &preload_sz);
if (成功) {
*out_pointer = preload_addr; // 直接指向预加载地址,零拷贝
}
}
写入操作(avb_hal_write_rollback_index)是空的:
AvbIOResult avb_hal_write_rollback_index(AvbOps *ops, …) {
/* otp update process is not implemented here. */
return AVB_IO_RESULT_OK;
}
这意味着 OTP 版本号的更新不是在 AVB 验证过程中进行的,而是由 load_vfy_boot.c 中的 record_avb_version()(
load_vfy_boot.c
)在验证通过后通过 set_avb_otp_ver() 写入。
Rollback Index 位置映射:
|
Location |
OTP Group |
用途 |
|
0 |
AVB_GROUP (rollback_index[0]) |
主 vbmeta 版本号 |
|
1 |
RECOVERY_GROUP (rollback_index[1]) |
recovery 镜像版本号 |
|
其他 |
暂不支持 |
— |
5.5 设备锁状态
AvbIOResult avb_hal_read_is_device_unlocked(AvbOps *ops, bool *out_is_unlocked) {
get_lock_state(&lock_state);
switch (lock_state) {
case LKS_DEFAULT: // 出厂默认
case LKS_MP_DEFAULT: // MP 默认
case LKS_LOCK: // 锁定
*out_is_unlocked = FALSE;
case LKS_UNLOCK: // 解锁
*out_is_unlocked = TRUE;
}
}
解锁检测影响两个行为:
5.6 Partition 专用公钥验证
AvbIOResult avb_hal_validate_public_key_for_partition(…) {
// 目前只支持 recovery 分区
if (memcmp(partition, "recovery", strlen("recovery")) != 0)
return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
// recovery rollback_index_location 固定为 1
*out_rollback_index_location = 1;
// 用 recovery 专用公钥验证
pubk_sz = key_hdr->key_num_bits / 8;
if (memcmp(g_avb_recovery_key, pubk, pubk_sz) == 0)
*out_is_trusted = TRUE;
}
安全意义: recovery 分区使用独立的公钥 g_avb_recovery_key,与主系统分离。这在生产环境中可以让 recovery 镜像由不同的团队签名,或者在 recovery 模式下使用不同的密钥策略。
六、完整描述符处理流程
├── descriptor[1]: AVB_DESCRIPTOR_TAG_CHAIN_PARTITION
│ ├── partition_name: "vbmeta_system"
│ ├── public_key: <system 专用公钥> ← 通常由 OEM 持有
│ ├── rollback_index_location: 2
│ │
│ └── load_and_verify_vbmeta("vbmeta_system", system_pubkey, location=2)
│ ├── ① avb_vbmeta_image_verify() ← 验签名
│ ├── ② public_key 匹配 chain 指定的公钥 ← 确认是预期 key
│ ├── ③ rollback_index[2] vs OTP ← 防回滚
│ │
│ └── descriptor[0]: AVB_DESCRIPTOR_TAG_HASHTREE
│ └── 分区: system → dm-verity root hash
│
├── descriptor[2]: AVB_DESCRIPTOR_TAG_CHAIN_PARTITION
│ ├── partition_name: "vbmeta_vendor"
│ ├── public_key: <vendor 专用公钥> ← 通常由 SoC 厂商持有
│ ├── rollback_index_location: 3
│ │
│ └── load_and_verify_vbmeta("vbmeta_vendor", vendor_pubkey, location=3)
│ ├── ① avb_vbmeta_image_verify() ← 验签名 (跟 system 完全一样)
│ ├── ② public_key 匹配 chain 指定的公钥 ← 用 vendor 的公钥
│ ├── ③ rollback_index[3] vs OTP ← 独立的 rollback slot
│ │
│ └── descriptor[0]: AVB_DESCRIPTOR_TAG_HASHTREE
│ └── 分区: vendor → dm-verity root hash
Hashtree descriptor 的特殊处理
// [avb_slot_verify.c:932-970]
switch (desc.tag) {
case AVB_DESCRIPTOR_TAG_HASH:
load_and_verify_hash_partition(…);
break;
case AVB_DESCRIPTOR_TAG_HASHTREE:
// LK 不做 hashtree 验证——这由内核 dm-verity 在运行时完成
// LK 只需要验证 vbmeta 本身签名,确保 root hash 可信
break;
…
}
设计原理: 如果 LK 对 system 分区(可能 2GB+)做完整哈希树验证,将极大延长启动时间。AVB 采用"验证签名 → 信任 root hash → 内核 dm-verity 按需校验"的模型:
七、Boot State 裁决与错误处理
7.1 状态转换图
┌──────────┐
│ 启动开始 │
│ RED │ ← 初始值
└─────┬─────┘
│
AVB 验证通过?
├── YES ─→ ┌──────────┐
│ │ GREEN │
│ └─────┬─────┘
│ │
│ 设备已解锁?
│ ├── YES ─→ ORANGE (覆盖 GREEN)
│ └── NO → 保持 GREEN
│
└── NO → 设备已解锁?
├── YES ─→ ORANGE (允许继续)
└── NO → RED (不能启动)
特殊路径:
YELLOW_STATE_SUPPORT + 自定义 key → YELLOW
dm-verity corruption → 显示红色警告或重启
7.2 handle_vboot_state()
uint32_t handle_vboot_state(uint32_t bootimg_type)
{
// 1. 打印启动状态
print_boot_state(); // 输出到串口日志
// 2. 显示警告画面 (如果状态非 GREEN)
show_warning(partition_name);
// GREEN → 不显示
// ORANGE → "Your device is unlocked…"
// YELLOW → "Your device is using a custom key…"
// RED → "Your device is corrupt…"
// 3. 将状态写入 cmdline
set_boot_state_to_cmdline(); // androidboot.verifiedbootstate=green
}
7.3 dm-verity 错误处理
void dm_verity_handler(AvbHashtreeErrorMode hashtree_error_mode)
{
get_dm_verity_status(&status);
// OTA 后首次启动 → 清除之前的错误记录
if (check_ota_result() == TRUE) {
clear_dm_verity_status();
}
// 发现 dm-verity 损坏
if (status == DM_VERITY_GENERAL_ERROR ||
hashtree_error_mode == AVB_HASHTREE_ERROR_MODE_EIO) {
show_dm_verity_error();
// → 显示 "Your device is corrupt."
// → 等待按键或 5 秒后关机
}
}
Hashtree Error Mode 的选择:
void dm_verity_handler(AvbHashtreeErrorMode hashtree_error_mode)
{
get_dm_verity_status(&status);
// OTA 后首次启动 → 清除之前的错误记录
if (check_ota_result() == TRUE) {
clear_dm_verity_status();
}
// 发现 dm-verity 损坏
if (status == DM_VERITY_GENERAL_ERROR ||
hashtree_error_mode == AVB_HASHTREE_ERROR_MODE_EIO) {
show_dm_verity_error();
// → 显示 "Your device is corrupt."
// → 等待按键或 5 秒后关机
}
}
MANAGED_RESTART_AND_EIO 状态机:
第一次 dm-verity 损坏:
─→ 重启 → system 无法挂载 → OTA/Recovery
─→ persist 中记录 "dm-verity corruption"
第二次启动 (检测到 persist 标记):
─→ 不重启 → 返回 EIO 给文件系统
─→ 显示 "Your device is corrupt."
八、防回滚机制深度分析
8.1 OTP 版本更新流程
// [load_vfy_boot.c] record_avb_version()
uint32_t record_avb_version(AvbSlotVerifyData *slot_data)
{
// 取所有 rollback_index 中的最小值
min_ver = slot_data->rollback_indexes[0];
for (i = 0; i < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; i++) {
if (i == RECOVERY_ROLLBACK_INDEX) continue; // recovery 特殊处理
if (rollback_indexes[i] < min_ver)
min_ver = rollback_indexes[i];
}
// recovery 版本写入独立 OTP group
if (rollback_indexes[RECOVERY_ROLLBACK_INDEX] 非初始值) {
set_avb_otp_ver(RECOVERY_GROUP, rollback_indexes[RECOVERY_ROLLBACK_INDEX]);
}
// 主版本号写入 AVB OTP group
set_avb_otp_ver(AVB_GROUP, (uint32_t)min_ver);
}
OTP (One-Time Programmable) efuse 特性:
- 只能从 0 → 1(或 fuse 只能烧断)
- 版本值只能增大,不能减小
- 一旦写入,永久生效
8.2 防降级攻击的保证
攻击者尝试将系统从 v50 降级到 v30:
1. OTP 中已存储 min_ver = 50
2. 旧镜像的 vbmeta rollback_index = 30
3. avb_slot_verify() 发现 30 < 50
4. → AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
5. → 验证失败 → 设备处于 RED 状态 → 拒绝启动 ✓
攻击者尝试绕过:
– 修改 vbmeta 中的 rollback_index → 签名失效 ✓
– 物理熔断 OTP → 不可逆,无法恢复 ✓
– 绕过整个 AVB → LK 签名验证阻止修改后的 LK ✓
8.3 Recovery 版本的特殊处理
#ifdef MTK_OTP_FRAMEWORK_V2
// 将 recovery 版本写入独立的 recovery OTP group
set_avb_otp_ver(RECOVERY_GROUP, recovery_version);
#else
// 不分离: 将 recovery 版本纳入 min_ver 计算
if (recovery_version < min_ver)
min_ver = recovery_version;
#endif
MTK_OTP_FRAMEWORK_V2 将 recovery 的防回滚版本号写入独立的 OTP region,避免 recovery 的低版本拉低整个系统的 min_ver,导致设备即使刷了高版本 system 也无法启动。
九、后处理与内核传递
9.1 boot_post_processing()
static uint32_t boot_post_processing(AvbOps *ops, uint32_t bootimg_type,
AvbSlotVerifyData *slot_data)
{
// 1. 收集 Root of Trust 信息
collect_rot_info(slot_data);
// → MTK 实现: 通过 SMC 调用将 vbmeta digest 等传递给 TEE
// 2. 重定位 boot 镜像到目标地址
for (每个加载的分区) {
memcpy(boot_load_addr, slot_data->loaded_partitions[i].data, …);
}
// 3. 准备 DTB
load_bootinfo_bootimg(boot_load_addr);
prepare_kernel_dtb();
// 4. 追加 cmdline
cmdline_append(CMDLINE_ROOT_SYSTEM_COMMON);
// 5. 检查用户验证设置 (allow verification off)
avb_user_verification_get(ops, suffix, &out_verification_enabled);
// 6. ⭐ 将 AVB 元数据写入 DTB
if (out_verification_enabled) {
avb_cmdline_postprocessing(slot_data->cmdline, get_kernel_fdt());
// → 从 cmdline 提取 vbmeta.hash_alg, vbmeta.size, vbmeta.digest
// → 写入 /firmware/android/ 节点
}
// 7. 追加 slot 信息 (A/B)
cmdline_append("androidboot.slot_suffix=_a");
}
9.2 DTB 写入
static uint32_t avb_cmdline_postprocessing(char *cmdline, void *fdt)
{
// 1. 提取 avb 参数
avb_extract_from_cmdline(cmdline, "androidboot.vbmeta.hash_alg");
avb_extract_from_cmdline(cmdline, "androidboot.vbmeta.size");
avb_extract_from_cmdline(cmdline, "androidboot.vbmeta.digest");
// 2. 创建 /firmware/android 节点 (如不存在)
fdt_add_subnode(fdt, 0, "firmware");
fdt_add_subnode(fdt, firmware_off, "android");
// 3. 写入 DTB
fdt_setprop_string(fdt, android_off, "vbmeta.hash_alg", "sha256");
fdt_setprop_string(fdt, android_off, "vbmeta.size", "4096");
fdt_setprop_string(fdt, android_off, "vbmeta.digest", "<64-byte hex>");
}
内核启动后,可以通过 DTB 的 /firmware/android/ 节点直接读取 vbmeta digest,用于判断当前启动链是否可信。
十、攻击面分析
10.1 潜在攻击向量
|
攻击向量 |
可行性 |
防护 |
|
替换 vbmeta 镜像 |
低 |
签名验证会失败 (RSA-2048) |
|
替换 boot 镜像 |
低 |
hash descriptor 校验会失败 |
|
降级 vbmeta 到旧版本 |
低 |
rollback index OTP 保护 |
|
物理读取 OTP |
中 |
OTP 设计上允许读取,但改写不可逆 |
|
解锁 bootloader 绕过 |
可行 |
设计如此 — ORANGE 状态会显示警告 |
|
替换 LK 绕过 AVB |
低 |
Preloader 验证 LK 签名 |
|
物理篡改 eMMC 分区数据 |
中 |
重启后会被检测到 → RED 状态 |
10.2 MTK 专有签名与 AVB 的双重验证
// mt_boot.c
#if MTK_FORCE_VERIFIED_BOOT_SIG_VFY
// 非 MTK 签名: AVB 验证在 boot_linux_from_storage 中完成
g_boot_state = BOOT_STATE_RED;
#else
// MTK 专有签名 + AVB 双重验证
if (0 != sec_boot_check(0)) // ← MTK img_auth_stor()
g_boot_state = BOOT_STATE_RED;
boot_linux_from_storage(); // ← AVB avb_slot_verify()
#endif
当 MTK_FORCE_VERIFIED_BOOT_SIG_VFY 未定义时,boot 分区必须同时通过 MTK 专有签名和 AVB 签名才能启动,提供了双重保险。
10.3 竞争条件与 TOCTOU
验证攻击面上的 TOCTOU(Time-of-Check-Time-of-Use):
时间点:
T1: load_vfy_boot() 从 eMMC 读取 boot 分区并验证 hash
T2: DDoS/温度攻击 ← 理论上可以物理攻击,但极难
T3: boot_linux() 跳转到已验证的 kernel 地址
MTK 的缓解措施:
– boot 分区加载到 DRAM 后,验证和启动使用同一内存地址
– 验证通过后设置 g_boot_state,启动时不再重读分区
– WDT 防卡死验证
十一、总结与安全评估
优势
局限性
总体结论
MTK 的 LK 阶段 AVB 实现遵循 Google libavb 标准规范,在 HAL 层增加了 MTK 特有的 TEE 集成、双 OTP 防回滚、recovery 专用密钥等增强。其安全强度完全取决于 Preloader 对 LK 镜像是如何进行签名验证的——如果 Preloader 信任链被攻破,AVB 层再强的校验也会被替换后的 LK 绕过。在 Preloader 信任链完整的条件下,LK 的 AVB 为 Android 系统提供了密码学级别的启动完整性保护。





