概要
Raspberry Pi 4 Model B (64-Bit版 Raspberry Pi OS) で dm-integrityの使用方法について確認した。
- dm-integrityはシーケンシャルライトが40%、ランダムライトが50%の性能低下していることが確認できた。
- dm-integrityはシーケンシャルリードが10%、ランダムリードが20%の性能低下していることが確認できた。
はじめに
Raspberry Pi 4 Model B (Raspberry Pi 4) はarm64のシングルボードコンピュータであり、趣味から仕事まで幅広い用途として利用されている。
Raspberry Pi の 推奨OSの一つに DebianベースOSのRaspberry Pi OSがある。
2021年4月30日現在では、安定性や互換性の観点からRaspberry Pi OSのデフォルトが32-bit版となっている。
しかし、64-bit版のRaspberry Pi OSも用意されているので、設定を変更することで利用できる。
一方で、dm-integrityはLinuxカーネルにおける標準提供の機能であり、ブロックの整合性を保障することができる。
dm-integrity では、 Device mapperと呼ばれるブロックデバイスの仮想レイヤを作成する機能を用いて、ジャーナルログを生成する。
ブロックの整合性を保証できる一方で、性能の観点でもストレージの容量の観点でもオーバーヘッドが発生する。
目的
- dm-integrityを導入したことで読み書き性能がどれだけ変化するかを調査する
- 改ざん検知機能 (dm-verity と dm-integrity+dm-crypt) の間で読み込み性能の比較をする
dm-integrity とは
dm-integrityは、Linuxカーネル v4.12以降で利用することができるLinux標準提供の機能である。 dm-integrityのデータ構造は下記の通りとなっている。
Device-mapperの機構を利用して、データと整合性タグをまとめてジャーナルにコミットしてから、データと整合性タグを適切な位置に書き込むことになっている。
ext4といったジャーナリングファイルシステムはファイルの整合性を保障する一方で、dm-integrityではブロック単位の整合性保証する。
そのため、ビットエラーや書き込み中の電源断による整合性保証のために利用される。
また、dm-integrityはdm-cryptと共に利用されることが多い。
dm-cryptは、Linuxカーネルにおける標準提供の機能であり、ブロック単位にを暗号/複合することができる。
二つの機構を組み合わせHMAC-sha256を使うことで、不正なデータの更新(改ざん)を検出することができる。
なぜ64-bit版 のOSで試すのか
2021年4月30日現在、Raspberry Pi OSはDebian 10 (buster) がベースとなっており、cryptsetup 2.1.0がパッケージされている。 一方で、armhf と cryptsetup 2.1.0は安定していない (ように見える)。
そこで下記の観点から、64-bit版のRaspberry Pi OSを使用することにした。
- カーネルコンフィグを修正するために再ビルドが必要であること
- arm64の読み書き性能を正しく計測するために、OSも64-bit版が適していること
- 32-bit版OSの既知のバグを踏まないため
実行環境
計測対象のRaspberry Pi 4 Model Bの詳細は下記のとおりである。
名前 | 詳細 |
---|---|
Hardware | Raspberry Pi 4 Model B Rev 1.2 (4GB) |
OS | Raspberry Pi OS arm64 2021-04-09 |
Kernel | Linux version 5.10.3-v8 |
Kconfig | 付録参照 |
SDカード | HDMCSDH16GCL10UIJP-WOA |
USBメモリ | SILICON POWER SP032GBUF3B02V1K |
IPアドレス (eth0) | 172.16.1.3 |
ユーザ名 | pi |
ホスト名 | raspberry |
dosfstools | 4.1 |
cryptsetup | v2.1.0 |
integrity | v1.6.0 |
verity | v1.7.0 |
crypt | v1.22.0 |
fio | v3.12 |
カーネルのビルドや環境のセットアップについては、前回の記事を参照。
本記事では、SDカードにブートに必要なイメージを格納し、USBメモリに作成したext4ファイルシステムで計測する。
またUSBメモリ(/dev/sda)はMBRパーティションを作成し、8GB(/dev/sda1)と20GB(/dev/sda2)の二つから構成される。
測定方法
今回は以下の4つのパターンにおける読み書き性能を測定した。 (ただしdm-verityはRead-Onlyであるため、③は読み込み性能のみとする)
- Device mapperを使用しない場合 (実デバイスドライバにファイルシステムを構築する)
- dm-integrityのみ使用する場合
- dm-verityを使用する場合
- dm-integrityとdm-cryprtの両方を使用する場合
各測定パターンの構築方法については付録参照。
また、読み書きの測定計測にはfioを利用し、write, randwrite, read, randreadの4つのエンジンを用いる。
各測定は10回計測し、外れ値を除外したスループットの平均値を性能として評価する。
ここで外れ値は、スループットが平均値から100%以上のずれがあるものや、レイテンシの値が異常値(0や極端に値が振れ幅が大きい)であるものとした。
write
writeとrandwriteについては、4KBのバッファI/Oで計測を実施した。*1
read
writeとrandwriteについては、4KBのバッファI/Oで計測を実施した。
性能計測
上記のグラフは左から、「Device mapperを使用しない場合 」「dm-integrityのみ使用する場合」「dm-verityを使用する場合」「dm-integrityとdm-cryprtの両方を使用する場合」におけるwrite, readwriteのスループットを示している。
dm-verityはRead-Onlyパーティションを対象としているため、write,randwriteのスループットを0と表記している。
normal | integrity | オーバーヘッド | verity | crypt+integrity | オーバーヘッド | |
---|---|---|---|---|---|---|
write性能 | 15.7MB/s | 8.9MB/s | -43.3% | 0 | 5.9MB/s | N/A |
randwrite性能 | 14.5MB/s | 6.9MB/s | -52.5% | 0 | 4.7MB/s | N/A |
上記の測定結果より、dm-integrityによる書き込みスループットは50%程度減少することが分かった。
これは、dm-integrity用のDevice Mapper層が追加されたことによる命令数の増加や、ジャーナルの書き込みによりI/O数が増加したことが要因として挙げられる。*2
また、dm-cryptとdm-integrityを組み合わた場合、スループットは65%程度減少することが分かった。
そのため、セキュアブートのためにHMACの利用を考えている方は、これらの性能低下を意識してほしい。
上記のグラフは左から、「Device mapperを使用しない場合 」「dm-integrityのみ使用する場合」「dm-verityを使用する場合」「dm-integrityとdm-cryprtの両方を使用する場合」におけるread, readreadのスループットを示している。
normal | integrity | オーバーヘッド | verity | crypt+integrity | オーバーヘッド | |
---|---|---|---|---|---|---|
read性能 | 88.2MB/s | 73.9MB/s | -12.2% | 39.3MB/s | 18.4MB/s | -53.2% |
randread性能 | 7.2MB/s | 6.0MB/s | -17.7% | 5.9MB/s | 5.0MB/s | -15.3% |
上記の測定結果より、dm-integrityによる読み込みスループットは15%程度減少することが分かった。
これは、dm-integritによる命令数の増加やI/O数が、LInuxのページング機構によって緩和されたため、著しいスループットの減少が見られなかったと考えられる。
一方で、dm-cryptとdm-integrityを組み合わた場合、dm-verityと比較してスループットが大きく減少する(シーケンシャルリードで50%、ランダムリードで15%)ことが分かった。
特にシーケンシャルリード(read性能)に関しては、著しい減少が見られた。
これは、読み込むべきデータが増加したことにより、ページイン・ページアウトの回数が増加したからではないかと考えられる。*3
おわりに
本記事ではRaspberry Pi 4上でdm-integrityのオーバーヘッドを測定した。
- dm-integrityを導入したことでwrite性能の著しい減少が観測された
- 改ざん検知機能の間でシーケンシャルリード性能の著しい減少が観測された
変更履歴
- 2021/5/9: 記事公開
参考
- dm-integrity — The Linux Kernel documentation
- dm-integrityの公式ドキュメント
- Linuxのデータ完全性保護方式の性能比較
- dm-verityとdm-integrityの性能比較
- dm-integrity - onokatio
- dm-integrityについて
- FOSDEM 2018 - Data integrity protection with cryptsetup tools
- cryptsetupによるデータの整合性についてまめられた発表
- Arch dm-cryptでデバイスを透過的に暗号化する - u+のブログ
- dm-cryptの使い方について
- dm-crypt + dm-integrity + dm-raid = awesome! · GitHub
- secure bootのために、luks2とintegrity機能を利用した手法
付録
カーネルコンフィグ
bcm2711_defconfigから更新した箇所は下記のとおりである。
617a618 > # CONFIG_CRYPTO_CRCT10DIF_ARM64_CE is not set 735c736,737 < # CONFIG_BLK_DEV_INTEGRITY is not set --- > CONFIG_BLK_DEV_INTEGRITY=y > CONFIG_BLK_DEV_INTEGRITY_T10=y 2199c2201,2203 < # CONFIG_DM_VERITY is not set --- > CONFIG_DM_VERITY=m > # CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG is not set > # CONFIG_DM_VERITY_FEC is not set 2202c2206 < # CONFIG_DM_INTEGRITY is not set --- > CONFIG_DM_INTEGRITY=m 7414c7418 < CONFIG_CRYPTO_GF128MUL=m --- > CONFIG_CRYPTO_GF128MUL=y 7448,7449c7452,7453 < # CONFIG_CRYPTO_CFB is not set < CONFIG_CRYPTO_CTR=m --- > CONFIG_CRYPTO_CFB=y > CONFIG_CRYPTO_CTR=y 7452,7454c7456,7458 < # CONFIG_CRYPTO_LRW is not set < # CONFIG_CRYPTO_OFB is not set < # CONFIG_CRYPTO_PCBC is not set --- > CONFIG_CRYPTO_LRW=y > CONFIG_CRYPTO_OFB=y > CONFIG_CRYPTO_PCBC=y 7476c7480 < # CONFIG_CRYPTO_CRCT10DIF is not set --- > CONFIG_CRYPTO_CRCT10DIF=y 7482,7485c7486,7489 < # CONFIG_CRYPTO_RMD128 is not set < # CONFIG_CRYPTO_RMD160 is not set < # CONFIG_CRYPTO_RMD256 is not set < # CONFIG_CRYPTO_RMD320 is not set --- > CONFIG_CRYPTO_RMD128=y > CONFIG_CRYPTO_RMD160=y > CONFIG_CRYPTO_RMD256=y > CONFIG_CRYPTO_RMD320=y 7487c7491 < CONFIG_CRYPTO_SHA256=m --- > CONFIG_CRYPTO_SHA256=y 7506c7510 < # CONFIG_CRYPTO_CAST6 is not set --- > CONFIG_CRYPTO_CAST6=m 7610c7614 < # CONFIG_CRC_T10DIF is not set --- > CONFIG_CRC_T10DIF=y
環境構築
Device mapperを使用しない場合
-
pi@raspberrypi:~ $ sudo mkfs.fat /dev/sda1 mkfs.fat 4.1 (2017-01-24)
作成したファイルシステムを/mntにマウントする
pi@raspberrypi:~ $ sudo mount -t vfat /dev/sda1 /mnt/
dm-verityを使用する場合
dm-verityでは、改ざんチェックの対象となるブロックデバイス(データデバイス)とチェックのためのブロックデバイス(ハッシュデバイス)の2つを用意する必要がある。
そこで、測定対象デバイス(USBメモリ) 上でパーティション作成した。
このとき、データデバイス/dev/sda1
を16GB、ハッシュデバイス/dev/sda2
を残りの領域に割り当てた。
下記の手順にて測定環境を構築した。
USBメモリ上にext4ファイルシステムを作成し、テスト用にデータを埋めておく
pi@raspberrypi:~ $ sudo mkfs.fat /dev/sda1 mkfs.fat 4.1 (2017-01-24) pi@raspberrypi:~ $ mount /dev/sda1 /mnt pi@raspberrypi:~ $ sudo dd if=/dev/urandom of=/mnt/file bs=1M count=4K
ハッシュデバイスの構築準備
pi@raspberrypi:~ $ sudo veritysetup format /dev/sda1 /dev/sda2 VERITY header information for /dev/sda2 UUID: 68bbac35-32bc-455f-a1f9-ae2a62287847 Hash type: 1 Data blocks: 4194304 Data block size: 4096 Hash block size: 4096 Hash algorithm: sha256 Salt: 759b9ed6980255b4469c6732b05519fe09890938b8608ef7f37fa462c9e79a41 Root hash: c2ba950245bc4ed2b37de9900f19050467db3c051cd4801a10713ca193a5e1a9
device-mapper (
/dev/mapper/test
)を作成pi@raspberrypi:~ $ sudo veritysetup create test /dev/sda1 /dev/sda2 c2ba950245bc4ed2b37de9900f19050467db3c051cd4801a10713ca193a5e1a9 pi@raspberrypi:~ $ ls -la /dev/mapper/test lrwxrwxrwx 1 root root 7 12月 30 04:12 /dev/mapper/test -> ../dm-0
作成したファイルシステムを/mntにマウントする
pi@raspberrypi:~ $ sudo mount -t vfat -o ro /dev/mapper/dmtest /mnt/
dm-integrityのみ使用する場合
integrity target用のMapping tableを作成する
pi@raspberrypi:~ $ sudo integritysetup format /dev/sda1 Formatted with tag size 4, internal integrity crc32c. Wiping device to initialize integrity checksum. You can interrupt this by pressing CTRL+c (rest of not wiped device will contain invalid checksum). Finished, time 18:26.389, 8064 MiB written, speed 7.3 MiB/s
device-mapper (
/dev/mapper/test
)を作成するpi@raspberrypi:~ $ sudo integritysetup open /dev/sda1 test
作成したintegrity targetを確認する
pi@raspberrypi:~ $ sudo integritysetup status test /dev/mapper/test is active. type: INTEGRITY tag size: 4 integrity: crc32c device: /dev/sda1 sector size: 512 bytes interleave sectors: 32768 size: 16516984 sectors mode: read/write failures: 0 journal size: 67043328 bytes journal watermark: 50% journal commit time: 10000 ms
device mapper tableを表示する (値の意味についてはcryptsetupを参照)
pi@raspberrypi:~ $ sudo dmsetup table test: 0 16516984 integrity 8:1 0 4 J 6 journal_sectors:130944 interleave_sectors:32768 buffer_sectors:128 journal_watermark:50 commit_time:10000 internal_hash:crc32c
USBメモリ上にext4ファイルシステムを作成し、テスト用のファイル
file
を準備するpi@raspberrypi:~ $ sudo mkfs.fat /dev/mapper/test mkfs.fat 4.1 (2017-01-24)
/mnt以下に作成したファイルシステムをマウントする
pi@raspberrypi:~ $ sudo mount -t vfat /dev/mapper/test /mnt/
dm-integrityとdm-cryprtの両方を使用する場合
crypt target + integrity target用のMapping tableを作成する
pi@raspberrypi:~ $ sudo cryptsetup luksFormat --type luks2 /dev/sda1 --cipher aes-xts-plain64 --integrity hmac-sha256 Enter passphrase for /dev/sda1: Verify passphrase: Wiping device to initialize integrity checksum. You can interrupt this by pressing CTRL+c (rest of not wiped device will contain invalid checksum). Finished, time 33:38.376, 7634 MiB written, speed 3.8 MiB/s
作成したDevice mapperのインスタンスを有効化する
pi@raspberrypi:~ $ sudo cryptsetup open /dev/sda test Enter passphrase for /dev/sda:
作成したDevice mapperを確認する
pi@raspberrypi:~ $ sudo cryptsetup status test /dev/mapper/test is active. type: LUKS2 cipher: aes-xts-plain64 keysize: 768 bits key location: keyring integrity: hmac(sha256) integrity keysize: 256 bits device: /dev/sda1 sector size: 512 offset: 0 sectors size: 15634464 sectors mode: read/write
-
pi@raspberrypi:~ $ sudo mkfs.fat /dev/mapper/test mkfs.fat 4.1 (2017-01-24)
/mnt以下に作成したファイルシステムをマウントする
pi@raspberrypi:~ $ sudo mount -t vfat /dev/mapper/test /mnt/
測定スクリプト
#!/bin/bash -eu MNTPOINT=/mnt/ TARGET=FILE.TXT LOOP=10 function usage() { echo "Usage: $(basename $0) {0 | 1}" echo " 0: measure for write, randwrite" echo " 1: measure for read, randread" } function do_fio() { if [ $# != 2 ]; then echo "Internal Error: function needs 2 argument." exit 1 fi echo 3 > /proc/sys/vm/drop_caches fio --filename=${MNTPOINT}${TARGET} --direct=$2 --rw=$1 --bs=4K --ioengine=libaio --runtime=60 --time_based --name=$2 --size=1G --output-format=json >> result_$1.log } ## ## MAIN FUNCTION ## array_0=("write" "randwrite") array_1=("read" "randread") if [ $# != 1 ]; then usage exit 1 fi if [ $1 = '0' ]; then for pattern in ${array_0[@]} do echo "Start ${pattern}" touch result_${pattern}.log for i in `seq ${LOOP}` do do_fio ${pattern} 0 rm ${MNTPOINT}${TARGET} done done elif [ $1 = '1' ]; then for pattern in ${array_1[@]} do echo "Start ${pattern}" touch result_${pattern}.log for i in `seq ${LOOP}` do do_fio ${pattern} 1 done done else usage exit 1 fi