dm-verity の実装

Android 4.4 以降は、オプションの device-mapper-verity (dm-verity) カーネル機能を介して検証ブートをサポートし、ブロック デバイスの透過的な整合性チェックを提供します。 dm-verity は、root 権限を保持してデバイスを侵害する可能性のある永続的なルートキットの防止に役立ちます。この機能は、Android ユーザーがデバイスを起動するときに、最後に使用したときと同じ状態であることを確認するのに役立ちます。

root 権限を持つ潜在的に有害なアプリケーション (PHA) は、検出プログラムから隠れたり、自身をマスクしたりすることができます。ルート化ソフトウェアがこれを実行できるのは、多くの場合、ソフトウェアが検出プログラムよりも特権が高く、ソフトウェアが検出プログラムに「嘘をつく」ことができるためです。

dm-verity 機能を使用すると、ファイル システムの基礎となるストレージ層であるブロック デバイスを調べて、それが予想される構成と一致するかどうかを判断できます。これは、暗号化ハッシュ ツリーを使用して行われます。すべてのブロック (通常は 4k) に SHA256 ハッシュがあります。

ハッシュ値はページのツリーに保存されるため、ツリーの残りの部分を検証するには最上位の「ルート」ハッシュのみを信頼する必要があります。ブロックのいずれかを変更できることは、暗号化ハッシュを解読することと同じです。この構造を示す次の図を参照してください。

dm-verity-ハッシュテーブル

図 1. dm-verity ハッシュ テーブル

公開キーはブート パーティションに含まれており、デバイスの製造元によって外部で検証される必要があります。このキーは、そのハッシュの署名を検証し、デバイスのシステム パーティションが保護され、変更されていないことを確認するために使用されます。

手術

dm-verity 保護はカーネル内に存在します。そのため、カーネルが起動する前に root 化ソフトウェアがシステムを侵害しても、そのアクセスは保持されます。このリスクを軽減するために、ほとんどのメーカーは、デバイスに焼き付けられたキーを使用してカーネルを検証します。このキーは、デバイスが工場から出荷されると変更できません。

メーカーはそのキーを使用して最初のレベルのブートローダーの署名を検証し、次にそれ以降のレベル、アプリケーション ブートローダー、そして最終的にはカーネルの署名を検証します。検証済みブートを利用したい各メーカーは、カーネルの整合性を検証する方法を備えている必要があります。カーネルが検証されていると仮定すると、カーネルはブロック デバイスを調べて、それがマウントされているときにそれを検証できます。

ブロック デバイスを検証する 1 つの方法は、その内容を直接ハッシュし、保存されている値と比較することです。ただし、ブロック デバイス全体を検証しようとすると、長時間かかり、デバイスの電力を大量に消費する可能性があります。デバイスの起動には長い時間がかかり、使用前に大幅に電力が消費されてしまいます。

代わりに、dm-verity はブロックを個別に検証し、各ブロックがアクセスされた場合にのみ検証します。メモリに読み込まれると、ブロックは並行してハッシュ化されます。次に、ハッシュがツリー上で検証されます。また、ブロックの読み取りは非常に高価な操作であるため、このブロックレベルの検証によって発生する待ち時間は比較的わずかです。

検証が失敗した場合、デバイスはブロックを読み取れないことを示す I/O エラーを生成します。予想どおり、ファイルシステムが破損しているように見えます。

アプリケーションは、それらの結果がアプリケーションの主な機能に必要ない場合など、結果のデータなしで続行することを選択する場合があります。ただし、データがないとアプリケーションを続行できない場合、アプリケーションは失敗します。

前方誤り訂正

Android 7.0 以降では、前方誤り訂正 (FEC) により dm-verity の堅牢性が向上しています。 AOSP の実装は、一般的なリードソロモン誤り訂正コードから始まり、インターリーブと呼ばれる技術を適用してスペースのオーバーヘッドを削減し、回復できる破損したブロックの数を増やします。 FEC の詳細については、 「エラー修正を伴う厳密に強制された検証済みブート」を参照してください。

実装

まとめ

  1. ext4 システムイメージを生成します。
  2. その画像のハッシュ ツリーを生成します
  3. そのハッシュ ツリーのdm-verity テーブルを構築します
  4. dm-verity テーブルに署名して、テーブル署名を生成します。
  5. テーブル署名と dm-verity テーブルを verity メタデータにバンドルします
  6. システム イメージ、verity メタデータ、およびハッシュ ツリーを連結します。

ハッシュ ツリーと dm-verity テーブルの詳細については、「The Chromium Projects - Verified Boot」を参照してください。

ハッシュツリーの生成

はじめに説明したように、ハッシュ ツリーは dm-verity に不可欠です。 cryptsetupツールはハッシュ ツリーを生成します。あるいは、互換性のあるものがここで定義されています。

<your block device name> <your block device name> <block size> <block size> <image size in blocks> <image size in blocks + 8> <root hash> <salt>

ハッシュを形成するために、システム イメージはレイヤー 0 で 4k ブロックに分割され、それぞれに SHA256 ハッシュが割り当てられます。レイヤー 1 は、SHA256 ハッシュのみを 4k ブロックに結合することによって形成され、その結果、イメージがはるかに小さくなります。レイヤ 2 は、レイヤ 1 の SHA256 ハッシュを使用して同様に形成されます。

これは、前の層の SHA256 ハッシュが 1 つのブロックに収まるまで行われます。そのブロックの SHA256 を取得すると、ツリーのルート ハッシュが得られます。

ハッシュ ツリーのサイズ (および対応するディスク領域の使用量) は、検証されたパーティションのサイズによって異なります。実際には、ハッシュ ツリーのサイズは小さくなる傾向があり、多くの場合 30 MB 未満です。

前のレイヤーのハッシュによって自然に完全に埋められていないレイヤー内のブロックがある場合は、期待される 4k を達成するためにゼロを埋め込む必要があります。これにより、ハッシュ ツリーが削除されておらず、空のデータで完成していることがわかります。

ハッシュ ツリーを生成するには、レイヤー 2 のハッシュをレイヤー 1 のハッシュに連結し、レイヤー 3 のハッシュをレイヤー 2 のハッシュに連結する、というように連結します。これらすべてをディスクに書き込みます。これはルート ハッシュのレイヤー 0 を参照しないことに注意してください。

要約すると、ハッシュ ツリーを構築する一般的なアルゴリズムは次のとおりです。

  1. ランダム ソルト (16 進エンコード) を選択します。
  2. システム イメージを 4K ブロックにアンスパースします。
  3. ブロックごとに、その (ソルト化された) SHA256 ハッシュを取得します。
  4. これらのハッシュを連結してレベルを形成します
  5. レベルを 4K ブロック境界まで 0 でパディングします。
  6. レベルをハッシュ ツリーに連結します。
  7. ハッシュが 1 つだけになるまで、前のレベルを次のレベルのソースとして使用して、手順 2 ~ 6 を繰り返します。

この結果、単一のハッシュ、つまりルート ハッシュが得られます。これとソルトは、dm-verity マッピング テーブルの構築中に使用されます。

dm-verity マッピング テーブルの構築

dm-verity マッピング テーブルを構築します。これは、カーネルのブロック デバイス (またはターゲット) とハッシュ ツリーの場所 (同じ値) を識別します。このマッピングは、 fstab生成と起動に使用されます。このテーブルでは、ブロックのサイズと、ハッシュ ツリーの開始位置である hash_start (具体的には、イメージの先頭からのブロック番号) も特定します。

Verity ターゲット マッピング テーブルのフィールドの詳細については、 「cryptsetup」を参照してください。

dm-verity テーブルへの署名

dm-verity テーブルに署名してテーブル署名を生成します。パーティションを検証するときは、最初にテーブルの署名が検証されます。これは、固定の場所にあるブート イメージ上のキーに対して行われます。通常、キーはメーカーのビルド システムに含まれており、固定された場所にあるデバイスに自動的に組み込まれます。

この署名とキーの組み合わせを使用してパーティションを検証するには、次の手順を実行します。

  1. RSA-2048 キーを libmincrypt 互換形式で/verity_key/bootパーティションに追加します。ハッシュ ツリーの検証に使用されるキーの場所を特定します。
  2. 関連するエントリの fstab で、 fs_mgrフラグにverifyを追加します。

テーブル署名をメタデータにバンドルする

テーブル署名と dm-verity テーブルを verity メタデータにバンドルします。メタデータのブロック全体はバージョン管理されているため、2 番目の種類の署名を追加したり、順序を変更したりするなど、拡張することができます。

健全性チェックとして、テーブルの識別に役立つマジック ナンバーがテーブル メタデータの各セットに関連付けられます。長さは ext4 システム イメージ ヘッダーに含まれているため、データ自体の内容を知らなくてもメタデータを検索する方法が提供されます。

これにより、未検証のパーティションを検証するように選択されていないことが確認されます。その場合、このマジックナンバーが存在しないと検証プロセスが停止します。この数値は次のようになります。
0xb001b001

16 進数のバイト値は次のとおりです。

  • 最初のバイト = b0
  • 2 番目のバイト = 01
  • 3 番目のバイト = b0
  • 4 バイト目 = 01

次の図は、verity メタデータの内訳を示しています。

<magic number>|<version>|<signature>|<table length>|<table>|<padding>
\-------------------------------------------------------------------/
\----------------------------------------------------------/   |
                            |                                  |
                            |                                 32K
                       block content

次の表では、それらのメタデータ フィールドについて説明します。

表 1. Verity メタデータ フィールド

分野目的サイズ価値
マジックナンバーfs_mgr によって健全性チェックとして使用されます4バイト0xb001b001
バージョンメタデータ ブロックのバージョン管理に使用されます4バイト現在0
サインPKCS1.5 パディング形式のテーブルの署名256バイト
テーブルの長さdm-verity テーブルの長さ (バイト単位) 4バイト
テーブル前に説明した dm-verity テーブルテーブルの長さのバイト
パディングこの構造体は長さが 32k になるまで 0 が埋め込まれます。 0

dm-verity の最適化

dm-verity から最高のパフォーマンスを得るには、次のことを行う必要があります。

  • カーネルで、ARMv7 の NEON SHA-2 と ARMv8 の SHA-2 拡張機能をオンにします。
  • さまざまな先読み設定と prefetch_cluster 設定を試して、デバイスに最適な構成を見つけてください。