dm-verity

dm-verity というのは linux カーネルに実装されたドライブ改竄防止の仕組みです。Android や組み込み機器によく使われます。

最も簡単に改竄防止を行うには、ドライブを書き込み禁止でマウントすれば良いでしょう。すると攻撃者が外部から侵入できてもドライブの内容を変更できません。しかし、もしも攻撃者がごっそりとドライブ自体を取り替えてしまったら書き込みの禁止だけでは役に立ちません。

dm-verity はドライブ全体のハッシュ値を使ってドライブの改竄を検知します。ドライブの一部を書き換えるとハッシュ値も変わるので改竄を見つける事ができます。ただドライブの容量は大きいので、いちいち全体のハッシュ値を計算していては大変です。

![dm-verity hash table](https://source.android.com/static/docs/security/images/dm-verity-hash-table.png)

https://source.android.com/docs/security/features/verifiedboot/dm-verity

dm-verity はここに一工夫あります。ドライブを 4k ごとの細かいブロックに分けてハッシュ値を求め、さらにそのハッシュ値を 4k ごとに連結してハッシュ値を求め。。。と最後の一つ(Root hash)になるまでツリー状にハッシュ値を求めます。すると検証時には読み込んだ分のブロックのハッシュ値だけを検証すれば良いので効率よく改竄の検出ができます。

[An Introduction to Dm-verity in Embedded Device Security — Star Lab Software](https://www.starlab.io/blog/dm-verity-in-embedded-device-security) にわかりやすい解説があるのでこれに沿って試してみます。

## dm-verity パーティションの作成

まず 1G のパーティションとして使うファイルを作成します。

    truncate -s 1G data_partition.img

このファイル上にデータ用のファイルシステムを作ります。

    mkfs -t ext4 data_partition.img

作ったファイルシステムをマウントします。

    mkdir mnt
    sudo mount -o loop data_partition.img mnt/
    sudo chmod 777 mnt/

作ったファイルシステムに適当な内容を書いてアンマウントします。

    echo "hello" > mnt/one.txt
    echo "integrity" > mnt/two.txt
    umount mnt/

次に、ハッシュツリーを保存する用のパーティションを作ります。

    truncate -s 100M hash_partition.img

veritysetup を使ってデータ data_partition.img のハッシュツリーを hash_partition.img に書き込みます。--debug オプションで必要な容量やツリー階層が表示されます。

```shell
$ veritysetup -v --debug format data_partition.img hash_partition.img
...
# Hash creation sha256, data device data_partition.img, data blocks 262144, hash_device hash_partition.img, offset 1.
# Using 3 hash levels.
# Data device size required: 1073741824 bytes.
# Hash device size required: 8462336 bytes.
# Updating VERITY header of size 512 on device hash_partition.img, offset 0.
VERITY header information for hash_partition.img
UUID:            	f0c81608-5c88-4886-9bdd-23d8439489ec
Hash type:       	1
Data blocks:     	262144
Data block size: 	4096
Hash block size: 	4096
Hash algorithm:  	sha256
Salt:            	f2e48dee4a52fed7f73fd0252f960e8c53a2f5718e3ecc08133a020bb2763f86
Root hash:      	472a20e0c4304a99d1e5ad23b1b8bce7e06f7f7e0e31eabc5e24fe51ea2917b7
# Releasing crypt device hash_partition.img context.
# Releasing device-mapper backend.
Command successful.
```

## dm-verity パーティションのマウント

早速守られたパーティションを使ってみます。次の情報が必要です。

* デバイスマッパー名: なんでも良いので `verity-test` とします。
* Root hash: 上の例では `472a20e0c4304a99d1e5ad23b1b8bce7e06f7f7e0e31eabc5e24fe51ea2917b7` です。

これを `veritysetup` コマンドに与えると `/dev/mapper/verity-test` ができます。

    sudo veritysetup open data_partition.img verity-test hash_partition.img 472a20e0c4304a99d1e5ad23b1b8bce7e06f7f7e0e31eabc5e24fe51ea2917b7

あとは `/dev/mapper/verity-test` を普通にマウントするだけです。`data_partition.img` をマウントするのと同じですが、強制的に read only になります。

```shell
$ sudo mount /dev/mapper/verity-test mnt/
mount: /home/tyamamiya/tmp/mnt: WARNING: device write-protected, mounted read-only.
$ cat mnt/one.txt mnt/two.txt
hello
integrity
```

## dm-verity パーティションの改竄

さて、いよいよ改竄してみましょう。一旦アンマウントして、`verify-test` も閉じます。

    sudo umount mnt/
    sudo veritysetup close verity-test

dm-verity は一ビットの変更でも見逃さないので、ファイルに手を加えなくても書き込み可能でマウントするだけでも改竄とみなします。試しにオリジナルの `data_partition.img` を書き込み可能マウントしてアンマウントします。

    sudo mount -o loop data_partition.img mnt/
    sudo umount mnt/

再度同じハッシュ値で dm-verity を有効にしても、マウントできませんでした。

```shell
$ sudo veritysetup open data_partition.img.bak verity-test hash_partition.img 472a20e0c4304a99d1e5ad23b1b8bce7e06f7f7e0e31eabc5e24fe51ea2917b7
Verity device detected corruption after activation.
$ sudo mount /dev/mapper/verity-test mnt/
mount: /home/tyamamiya/tmp/mnt: can't read superblock on /dev/mapper/verity-test.
```

後片付け

    sudo veritysetup close verity-test

## その他のコマンド

ハッシュパーティションの内容を表示

```shell
$ veritysetup dump hash_partition.img
VERITY header information for hash_partition.img
UUID:            	f0c81608-5c88-4886-9bdd-23d8439489ec
Hash type:       	1
Data blocks:     	262144
Data block size: 	4096
Hash block size: 	4096
Hash algorithm:  	sha256
Salt:            	f2e48dee4a52fed7f73fd0252f960e8c53a2f5718e3ecc08133a020bb2763f86
```

デバイスマッパーの情報を表示

```shell
$ sudo veritysetup status verity-test 
/dev/mapper/verity-test is active and is in use.
  type:        VERITY
  status:      verified
  hash type:   1
  data block:  4096
  hash block:  4096
  hash name:   sha256
  salt:        f2e48dee4a52fed7f73fd0252f960e8c53a2f5718e3ecc08133a020bb2763f86
  data device: /dev/loop1
  data loop:   /home/tyamamiya/tmp/data_partition.img.bak
  size:        2097152 sectors
  mode:        readonly
  hash device: /dev/loop0
  hash loop:   /home/tyamamiya/tmp/hash_partition.img
  hash offset: 8 sectors
```

## 参考

* [dm-verity — The Linux Kernel documentation](https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html)
* [DMVerity · Wiki · cryptsetup / cryptsetup · GitLab](https://gitlab.com/cryptsetup/cryptsetup/-/wikis/DMVerity)

* [Implementing dm-verity  |  Android Open Source Project](https://source.android.com/docs/security/features/verifiedboot/dm-verity)