avb校驗功能主要是由external/avb/libavb庫實現的,該庫主要完成的工作包括各個分區鏡像的校驗,簽名驗證,以及vbmeta數據的解析,包括了各種flags的處理以及dm-verity所需要的參數解析。avb校驗庫的主入口爲
avb_slot_verify(AvbOps* ops,
const char* const* requested_partitions,
const char* ab_suffix,
AvbSlotVerifyFlags flags,
AvbHashtreeErrorMode hashtree_error_mode,
AvbSlotVerifyData** out_data)
以高通平臺爲例,avb校驗在一次啓動過程中總共進行了兩次,第一次是在bootloader中進行校驗,通過上面的接口校驗各個分區的根hash和簽名。第二次是在上層init中進行的,這次校驗也是調用相同的接口,可能個別傳入的參數會有不同。
之所以在init中再做一次的原因是什麼呢?因爲我們掛載分區和dm-verity相關的參數都在鏡像的vbmeta結構中保存,因此可以使用該接口進行解析和掛載,但是該接口和校驗功能是捆綁的,所以必須要再進行一次校驗。不管是在bootloader還是在上層init中,所有校驗都是從vbmeta分區開始,然後讀取其中包含的各個分區的信息,然後依次循環讀取並校驗其他的各個分區,所有vbmeta.img中包含的分區都必須校驗通過才認爲是校驗成功。
bootloader
CONST CHAR8 *RequestedPartitionMission[] = {"boot", "dtbo", NULL};
CONST CHAR8 *RequestedPartitionRecovery[] = {"recovery", "dtbo", NULL};
if ((!Info->MultiSlotBoot) &&
Info->BootIntoRecovery) {
RequestedPartition = RequestedPartitionRecovery;
NumRequestedPartition = ARRAY_SIZE (RequestedPartitionRecovery) - 1;
if (Info->NumLoadedImages) {
/* fastboot boot option, skip Index 0, as boot image already
* loaded */
RequestedPartition = &RequestedPartitionRecovery[1];
}
} else {
RequestedPartition = RequestedPartitionMission;
NumRequestedPartition = ARRAY_SIZE (RequestedPartitionMission) - 1;
if (Info->NumLoadedImages) {
/* fastboot boot option, skip Index 0, as boot image already
* loaded */
RequestedPartition = &RequestedPartitionMission[1];
}
}
if (Info->NumLoadedImages) {
NumRequestedPartition--;
}
if (FixedPcdGetBool (AllowEio)) {
VerityFlags = IsEnforcing () ? AVB_HASHTREE_ERROR_MODE_RESTART
: AVB_HASHTREE_ERROR_MODE_EIO;
} else {
VerityFlags = AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE;
}
Result = avb_slot_verify (Ops, RequestedPartition, SlotSuffix, VerifyFlags,
VerityFlags, &SlotData);
avb校驗是所有分區都要進行校驗的,這裏傳入的RequestedPartition並不代表要校驗的分區,進一步跟進可以發現最終和RequestedPartition只會對Hash descriptor的解析有影響,其他類型的descriptor解析方法都是一樣的。
高通平臺的vbmeta.img包含如下信息:
Minimum libavb version: 1.0
Header Block: 256 bytes
Authentication Block: 576 bytes
Auxiliary Block: 3456 bytes
Algorithm: SHA256_RSA4096
Rollback Index: 0
Flags: 0
Release String: 'avbtool 1.1.0'
Descriptors:
Chain Partition descriptor:
Partition Name: system
Rollback Index Location: 2
Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d
Chain Partition descriptor:
Partition Name: recovery
Rollback Index Location: 1
Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011
Hash descriptor:
Image Size: 35553280 bytes
Hash Algorithm: sha256
Partition Name: boot
Salt: baa1ce5d7db69d1b3943a78b5b142ae4d77b4ed60b9885c8661e845172b29a13
Digest: ec7cb1ad89fed3104a03191434d5487b5b4acc78e6bfeb84d7178af40df7db75
Flags: 0
Hashtree descriptor:
Version of dm-verity: 1
Image Size: 1056714752 bytes
Tree Offset: 1056714752
Tree Size: 8327168 bytes
Data Block Size: 4096 bytes
Hash Block Size: 4096 bytes
FEC num roots: 2
FEC offset: 1065041920
FEC size: 8421376 bytes
Hash Algorithm: sha1
Partition Name: vendor
Salt: abbf0829ed7bc08913b83f9a994a37ad2a85b5e9
Root Digest: 39a22a035ebff2d339dc682603adedb91da01374
Flags: 0
Hash descriptor:
Image Size: 176641 bytes
Hash Algorithm: sha256
Partition Name: dtbo
Salt: 386837807aa5a7d9cbe51e7f768009f4e5fca5190af4b3e856a7c96a96c33e0a
Digest: dabdbe5be19c38a3428efd046182d215f8522ab7cd3804e84f196fe73e9052f7
Flags: 0
因此受到影響的只有boot/dtbo/recovery,recovery這裏是Chain Partition descriptor,但是解析recovery.img發現它校驗數據也是生成的Hash,而不是Hashtree的方式。 RequestedPartition代表的是需要被校驗的hash分區,只有其中包含的Hash分區,那麼纔會被校驗。因此根據boot mode方式的不同,需要校驗的hash分區也不同,正常啓動需要校驗:boot/dtbo,recovery啓動需要校驗:recovery/dtbo,而其他分區的校驗方式是一樣的。
init
針對init中的校驗,由於我們已經boot成功到上層了,雖然該函數依然會輪詢vbmeta包含的各個分區,但是此時kernel和dtbo都已經加載完畢了,沒有必要在對Hash分區進一步進行校驗了,所以我們可以傳入RequestedPartition爲空指針,這樣init當檢測到是Hash descriptor會直接return OK,並繼續下一個分區的校驗。
init中和AVB相關的主要是進行校驗和掛載分區的功能,終極目的還是爲了掛載,可以分開來看,有firststagemount和secondstagemount兩種方式,第一種方式是爲了掛載vendor分區,需要通過libavb對system/vendor進行校驗,system分區已經由kernel掛載過了,因此這裏只需要利用dm-verity參數掛載vendor分區,因爲vendor中包含了很多init rc文件,所以vendor必須要在這些rc執行之前被掛載上,所以必須要在第一階段把vendor分區掛載起來,然後在init的第二階段去執行init rc。第二種方式是其他分區的掛載,主要是通過讀取fstab中的掛載項進行掛載,這就和之前版本的android掛載方式一樣了。
需要注意的是,由於第一階段的掛載,我們沒有辦法通過fstab文件傳遞給init,因爲fstab在vendor中,而vendor還沒有被掛載起來,所以我們可以通過device tree的方式來傳遞fstab參數。這樣init在第一階段通過讀取device tree來獲取要在第一階段掛載的分區,也就是我們的vendor分區,由於avb和dm-verity是綁定的,所以還需要傳入對應的vbmeta節點,目的是開啓dm-verity功能,同時也會對vbmeta.img中存在的各個分區進行校驗。當然我們校驗是傳入RequestedPartition爲空指針,以跳過Hash分區的校驗,只進行其他分區的校驗。
這裏需要特別注意的是device tree中的vbmeta節點的配置,必須要和vbmeta.img中所包含的分區一致,因爲這個匹配校驗也會做,否則會報校驗失敗,從而無法啓動android。
firmware: firmware {
android {
compatible = "android,firmware";
vbmeta {
compatible = "android,vbmeta";
parts = "vbmeta,boot,system,vendor,dtbo,recovery";
};
fstab {
compatible = "android,fstab";
vendor {
compatible = "android,vendor";
dev = "/dev/block/platform/soc/8804000.sdhci/by-name/vendor";
type = "ext4";
mnt_flags = "ro,barrier=1,discard";
fsmgr_flags = "wait,avb";
status = "ok";
};
};
};
};
這個vbmeta中包含"vbmeta,boot,system,vendor,dtbo,recovery"這幾個分區,恰好和上面解析出來的一直,否則會報如下錯誤:
[ 11.254728] init: init first stage started!
[ 11.260572] init: Using Android DT directory /proc/device-tree/firmware/android/
[ 11.274173] init: [libfs_mgr]fs_mgr_read_fstab_default(): failed to find device default fstab
[ 11.463441] init: [libfs_mgr]by-name symlink not found for partition: 'recovery'
[ 11.471083] init: [libfs_mgr]avb_slot_verify failed, result: 2
這是一個去掉recovery分區的一個報錯實例。
init調用libavb庫的主入口函數如下:
AvbSlotVerifyResult FsManagerAvbOps::AvbSlotVerify(const std::string& ab_suffix,
AvbSlotVerifyFlags flags,
AvbSlotVerifyData** out_data) {
// Invokes avb_slot_verify() to load and verify all vbmeta images.
// Sets requested_partitions to nullptr as it's to copy the contents
// of HASH partitions into handle>avb_slot_data_, which is not required as
// fs_mgr only deals with HASHTREE partitions.
const char* requested_partitions[] = {nullptr};
// The |hashtree_error_mode| field doesn't matter as it only
// influences the generated kernel cmdline parameters.
return avb_slot_verify(&avb_ops_, requested_partitions, ab_suffix.c_str(), flags,
AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, out_data);
}
可以看出,其傳入的requested_partitions確實是爲空nullptr,這樣就會跳過Hash分區校驗。
解析vbmeta數據中的Hash descriptor:
switch (desc.tag) {
case AVB_DESCRIPTOR_TAG_HASH: {
AvbSlotVerifyResult sub_ret;
sub_ret = load_and_verify_hash_partition(ops,
requested_partitions,
ab_suffix,
allow_verification_error,
descriptors[n],
slot_data);
if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) {
ret = sub_ret;
if (!allow_verification_error || !result_should_continue(ret)) {
goto out;
}
}
} break;
load_and_verify_hash_partition中判斷requested_partitions是否存在對應的分區名,不存在則返回OK跳轉:
/* Don't bother loading or validating unless the partition was
* requested in the first place.
*/
found = avb_strv_find_str(requested_partitions,
(const char*)desc_partition_name,
hash_desc.partition_name_len);
if (found == NULL) {
ret = AVB_SLOT_VERIFY_RESULT_OK;
goto out;
}
禁止verification校驗功能
通過我的另一篇文章《Android P 如何掛載system鏡像到根目錄》的介紹,大家應該會有所瞭解,vbmeta.img中保存有一個flags:
typedef enum {
AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = (1 << 0),
AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED = (1 << 1)
} AvbVBMetaImageFlags;
該flags會有兩個bit,分別代表是否使能dm-verity和verification功能,本文介紹的AVB校驗功能就是verification使能的時候纔會進行。
我們可以通過修改此bit位來禁止AVB校驗功能,利用fastboot重新刷寫vbmeta.img:
fastboot --disable-verification flash vbmeta vbmeta.img
跳過AVB的代碼如下所示,在函數avb_slot_verify中:
if (toplevel_vbmeta.flags & AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED) {
/* Since verification is disabled we didn't process any
* descriptors and thus there's no cmdline... so set root= such
* that the system partition is mounted.
*/
avb_assert(slot_data->cmdline == NULL);
slot_data->cmdline =
avb_strdup("root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)");
if (slot_data->cmdline == NULL) {
ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
goto fail;
}
} else {
...... //verification operation
}
這裏發現禁止校驗之後,函數就結束了,校驗操作在else中進行的。
禁止dm-verity功能
兩種方法:
adb disable-verity
fastboot --disable-verity flash vbmeta vbmeta.img
最終都是更新了vbmeta分區中保存的對應的flags來確定是否禁止dm-verity的。
備註: