dm-verity というのは linux カーネルに実装されたドライブ改竄防止の仕組みです。Android や組み込み機器によく使われます。
最も簡単に改竄防止を行うには、ドライブを書き込み禁止でマウントすれば良いでしょう。すると攻撃者が外部から侵入できてもドライブの内容を変更できません。しかし、もしも攻撃者がごっそりとドライブ自体を取り替えてしまったら書き込みの禁止だけでは役に立ちません。
dm-verity はドライブ全体のハッシュ値を使ってドライブの改竄を検知します。ドライブの一部を書き換えるとハッシュ値も変わるので改竄を見つける事ができます。ただドライブの容量は大きいので、いちいち全体のハッシュ値を計算していては大変です。
dm-verity はここに一工夫あります。ドライブを 4k ごとの細かいブロックに分けてハッシュ値を求め、さらにそのハッシュ値を 4k ごとに連結してハッシュ値を求め。。。と最後の一つ(Root hash)になるまでツリー状にハッシュ値を求めます。すると検証時には読み込んだ分のブロックのハッシュ値だけを検証すれば良いので効率よく改竄の検出ができます。
An Introduction to Dm-verity in Embedded Device Security — Star Lab Software にわかりやすい解説があるのでこれに沿って試してみます。
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 オプションで必要な容量やツリー階層が表示されます。
$ 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 になります。
$ 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 を有効にしても、マウントできませんでした。
$ 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
その他のコマンド
ハッシュパーティションの内容を表示
$ 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
デバイスマッパーの情報を表示
$ 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