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 の詳細については、 Strictly Enforced Verified Boot with Error Correction を参照してください。

実装

概要

  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. レベルを 0 で 4k ブロック境界までパディングします。
  6. レベルをハッシュ ツリーに連結します。
  7. ハッシュが 1 つだけになるまで、前のレベルを次のソースとして使用して、手順 2 ~ 6 を繰り返します。

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

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

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

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

dm-verity テーブルへの署名

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

この署名とキーの組み合わせでパーティションを検証するには:

  1. libmincrypt 互換フォーマットの RSA-2048 キーを/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 設定を試してください。