Android Verified Boot 2.0 の検証メカニズムと実装詳細

Android Verified Boot (AVB) の概要

Android Verified Boot (AVB) は、デバイス上の各パーティションイメージが改ざんされていないことを保証するための仕組みです。もしイメージに不正な変更が加えられた場合、デバイスの起動プロセスは異常として処理され、セキュリティリスクを防ぎます。

起動プロセスにおける検証の連鎖は以下の通り構成されています。

  • PBL (Primary Bootloader): 電源投入後に最初に実行され、Secboot (Secure Boot) 処理を行います。ここではブートローダーの正当性を検証し、問題なければ制御を渡します。
  • Bootloader: 次の段階で必要なイメージ(boot.img, dtbo.img など)の検証を行います。これが Android Verified Boot (AVB) の主要な処理箇所であり、検証成功後に Kernel へ制御を移します。
  • Kernel / Init: システムパーティション(system.img, vendor.img など)の検証を行います。検証通過後にのみパーティションのマウントが許可されます。

つまり、起動の各段階において、次に必要なリソースに対してその都度整合性チェックが行われる構造になっています。

Secboot と Root of Trust

Secboot はブートローダーの信頼性を担保する根幹部分です。SoC 製造時に書き込まれる「Root of Trust」と呼ばれる根証明書が一度だけ記録され、これがブートローダーの署名検証に利用されます。これにより、ハードウェアレベルで信頼の連鎖が開始されます。

Bootloader における AVB 実装

ブートローダー段階では、特定のパーティション変数に基づいて検証対象が決定されます。一般的に以下のパーティションが候補となります。

static const char* g_target_partition_list[] = {
     "boot",
     "dtbo",
     "vbmeta",
     "recovery",
     "vendor_boot"
};

実際にどのイメージを検証するかは、vbmeta イメージの内容に依存します。avbtool を使用して vbmeta.img を解析すると、検証必要なパーティション名、_salt_、_digest_ などの情報が含まれていることが確認できます。

ブートローダーは vbmetabootdtbovendor_boot などのイメージを検証対象とし、次の起動段階で必要となるリソースの完全性を保証します。

UEFI (Bootloader) での検証フロー

検証の核心となる関数は vbmeta の整合性を確認する処理です。信頼性を確保するため、以下の手順を踏みます。

static AvbStatus validate_vbmeta_structure(...)
{
  ...
  /* 署名の検証と公開鍵の取得 */
  // vbmeta から公開鍵データ (pk_data) と長さ (pk_len) を抽出
  AvbResult vbmeta_ret =
     avb_vbmeta_image_verify(vbmeta_buf, vbmeta_num_read, &pk_data, &pk_len);
   ...
   // 取得した鍵が信頼済みか確認 (OEMPublicKey と比較)
   // OEMPublicKey はコード内にハードコードされている
   IoResult io_ret = ops->validate_vbmeta_public_key(ops,
                                         pk_data,
                                         pk_len,
                                         pk_metadata,
                                         pk_metadata_len,
                                         &key_is_trusted);
   ...
   // 検証対象パーティションの記述子を取得 (例:boot, dtbo, vendor_boot)
   descriptors =
      avb_descriptor_get_all(vbmeta_buf, vbmeta_num_read, &num_descriptors);
   ...
    // 各記述子に対してハッシュ値の比較検証を実行
   for (n = 0; n < num_descriptors; n++) {
    ...
    // 記述子の妥当性を確認
    if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) {
      avb_errorv(full_partition_name, ": Descriptor is invalid.\n", NULL);
      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
      goto out;
    }
   ...
    switch (desc.tag) {
      case AVB_DESCRIPTOR_TAG_HASH: {
        AvbStatus sub_ret;
        // vbmeta 内の Digest と実際のイメージの Digest を比較
        // 一致すれば検証成功
        sub_ret = process_hash_partition_verification(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;
    }
   ...
}

具体的なハッシュ検証処理(例:boot イメージ)は以下の手順で進められます。

  1. vbmeta から対象パーティションの塩(salt)とダイジェスト(digest)を取得します。
  2. 対象パーティション(例:boot)のイメージデータを読み込みます。
  3. 読み込んだイメージデータと塩を用いてハッシュ値を再計算します。
  4. 計算されたハッシュ値と vbmeta 内のダイジェスト値を比較します。
// 1. 記述子から salt と digest を抽出
if (!avb_hash_descriptor_validate_and_byteswap(
          (const AvbHashDescriptor*)descriptor, &hash_desc)) {
    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
    goto out;
  }

  desc_partition_name =
      ((const uint8_t*)descriptor) + sizeof(AvbHashDescriptor);
  desc_salt = desc_partition_name + hash_desc.partition_name_len;
  desc_digest = desc_salt + hash_desc.salt_len;

// 2. パーティションデータを読み込み
io_ret = ops->read_from_partition(
      ops, part_name, 0 /* offset */, image_size, image_buf, &part_num_read);

// 3. ハッシュ値を計算
hash_context_init(&ctx);
hash_context_update(&ctx, desc_salt, hash_desc.salt_len);
hash_context_update(&ctx, image_buf, hash_desc.image_size);
computed_digest = hash_context_finalize(&ctx);

// 4. 比較検証
if (avb_safe_memcmp(computed_digest, desc_digest, digest_len) != 0) {
    avb_errorv(part_name,
               ": Hash of data does not match digest in descriptor.\n",
               NULL);
    ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION;
    goto out;
  } else {
    avb_debugv (part_name, ": success: Image verification completed\n", NULL);
  }

この仕組みにより、boot.img のわずか 1 ビットでも変更されればハッシュ値が不一致となり、起動が阻止されます。また、vbmeta.img 自体も秘密鍵で署名されているため、改ざんは不可能です。

Kernel / Init 段階での AVB 処理

Init プロセスにおける AVB 検証は、Device Tree (DTS) や fstab 設定によって制御されます。

DTS 設定例:

android {
        compatible = "android,firmware";
        vbmeta {
            compatible = "android,vbmeta";
            parts = "vbmeta,boot,system,vendor,dtbo";
        };

        fstab {
            compatible = "android,fstab";
            vendor {
                compatible = "android,vendor";
                dev = "/dev/block/platform/soc/1d84000.ufshc/by-name/vendor";
                type = "ext4";
                mnt_flags = "ro,barrier=1,discard";
                fsmgr_flags = "wait,slotselect,avb";
                status = "ok";
            };
        };
    };

fsmgr_flagsavb が含まれている場合、Init プロセスはそのパーティションに対して AVB 検証を実行します。

fstab ファイルにおけるマウントオプションも重要です。

system      /system  ext4    ro,barrier=1,discard    wait,slotselect,avb=vbmeta_system,logical,first_stage_mount,avb_keys=/avb/q-gsi.avbpubkey
system_ext  /system_ext   ext4    ro,barrier=1,discard         wait,slotselect,avb=vbmeta_system,logical,first_stage_mount
product     /product      ext4    ro,barrier=1,discard         wait,slotselect,avb=vbmeta_system,logical,first_stage_mount
vendor      /vendor       ext4    ro,barrier=1,discard         wait,slotselect,avb,logical,first_stage_mount

avb=vbmeta_system が指定されている場合、systemsystem_extproduct などは vbmeta_system イメージを参照して検証されます。パラメータがない場合は通常の vbmeta が参照されます。

動的パーティションを採用しているプラットフォームでは、logical フラグにより、super パーティションから論理的に分割された領域としてマウントされます。Init 段階では vbmetavbmeta_systemvendorproductodmsystem_extsystem などのイメージが検証対象となります。検証に失敗した場合、dm-verity device corrupted というログと共に Fastboot モードへ遷移します。

検証の無効化

開発段階などで検証を bypass するには、以下のコマンドを使用します。

fastboot --disable-verification flash vbmeta vbmeta.img
fastboot --disable-verity flash vbmeta vbmeta.img
adb disable-verity

これらのコマンドは vbmeta イメージ内のフラグビットを変更します。

/* vbmeta イメージのフラグ定義 */
typedef enum {
  AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = (1 << 0),
  AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED = (1 << 1)
} AvbVBMetaImageFlags;

ソースコード上では、disable-veritydisable-verification は最終的に同じ検証バイパス処理に収束します。

// system/core/fs_mgr/libfs_avb/fs_avb.cpp
AvbHashtreeResult AvbHandle::SetUpAvbHashtree(FstabEntry* fstab_entry, bool wait_for_verity_dev) {
    ...
    if (status_ == AvbHandleStatus::kHashtreeDisabled ||
        status_ == AvbHandleStatus::kVerificationDisabled) {
        LINFO << "AVB HASHTREE disabled on: " << fstab_entry->mount_point;
        return AvbHashtreeResult::kDisabled;
    }
    ...
}

device-mapper-verity (dm-verity) の仕組み

Init 段階以降、systemvendor といった数 GB 規模のイメージを全体ハッシュで検証するのは起動時間に影響します。そこで、アクセス単位で検証を行う dm-verity が採用されています。

コンパイル段階では、システムイメージを 4KB ブロックごとに分割し、各ブロックのハッシュ値を計算してツリー構造(Hash Tree)を構築します。最下層のハッシュ値から順に上位層のハッシュ値を計算し、最終的に 1 つのルートハッシュ(Root Hash)を生成します。このルートハッシュが vbmeta などに記録されます。

実行段階では、ファイルアクセス時に該当する 4KB ブロックのハッシュ値をリアルタイムで計算し、ハッシュツリー上の対応する値と比較します。不一致 detected された場合、そのデータは改ざんまたは破損していると判断され、アクセスが拒否されます。これにより、パフォーマンスを維持しつつストレージ上のデータ整合性を保証しています。

タグ: Android AVB dm-verity BootLoader Security

7月22日 03:26 投稿