概要
Raspberry Pi 4 Model B で fs-verityとdm-verityの使用方法について確認した。
- fs-verityはシーケンシャルリードが50%、ランダムリードが15%の性能低下していることが確認できた。
- dm-verityはシーケンシャルリードが30%、ランダムリードが20%の性能低下していることが確認できた。
はじめに
Raspberry Pi 4 Model B (Raspberry Pi 4) は安価に手に入るシングルボードコンピュータとして広く利用されている。
Raspberry Pi は、DebianベースOSのRaspberry Pi OSが搭載されている。
そのため、組込みLinuxの動作確認用のボードとして手軽に使うことができる。
一方で、fs-verityはファイルの改ざんを検出できるLinux Kernel 5.4の新機能である。
残念ながらfs-verityは、Raspberry Pi OSのデフォルトの設定では無効になっている。
そこで、本記事ではRaspberry Pi 用カーネルの自前ビルドしたLinux Kernel 5.10でfs-verityのオーバーヘッドを測定した。
fs-verity とは
fs-verityとは、ファイルシステムと連携して<b<ファイルの実データの改ざんを検知することができるLinux Kernelの機能の一つである。
Linux Kernel 5.10では、ext4ファイルシステムとf2fsファイルシステムで上記がサポートされている。
fs-verityでは、ファイルの実データを一定サイズ (下記の例では4096
バイト)毎に区切り、それぞれのハッシュ値を計算する。
計算したハッシュ値は、一定サイズになるまで同様にハッシュ値を計算していく。
このように算出されたハッシュツリーをあらかじめファイルの一部として保存し、実データとの比較によりデータの改ざんを検知することができる。
ただし、fs-verityを有効にしたファイルはRead-Onlyとなるので注意が必要である。
fs-verityの実装は、ファイルシステムのreadpages
操作をフックするようになっている。
fs-verityを有効にしたreadpages
操作では、実データに加えてハッシュツリーもページキャッシュに読み込まれる仕様となっている。
そのため、fs-verityではDirect I/Oがサポートされていない。
fs-verityの使い方については、カーネルドキュメントやfs-verityのユーティリティ(fsverity-utils)のREADMEに記載されている。
ただし、上記のやり方のみだと、実データの保証に用いたハッシュツリーの改ざんを防ぐことができない。
そこで、fs-verityでは署名ファイル検証をサポートしている。
署名ファイル検証では、あらかじめ作成した証明書と秘密鍵からシグネチャを生成し、ファイルに付与する。
ファイルの読み込み時にこれらを比較することで、ハッシュツリーが書き換えられていないことを検証することができる
署名ファイル検証で使用する証明書は、カーネルのkeyring(.fs-verity)を利用して実現する。
fs-verityでは PKCS#7のDER形式のX.509証明書を利用する。
dm-verity とは
fs-verity と似た機能として dm-verity がある。
dm-verityとは、device-mapperと連携してブロックデバイスの改ざんを検知することができるLinux Kernelの機能の一つである。
dm-verityの使い方については、カーネルドキュメントやdm-verityのユーティリティ(cryptsetup)のREADMEに記載されている。
また、dm-verityでも同様にハッシュツリーの署名検証をサポートしている。
ファイルの読み込み時にこれらを比較することで、ハッシュツリーが書き換えられていないことを検証することができる
署名ファイル検証で使用する証明書は、カーネルのkeyring(.fs-verity)を利用して実現する。
fs-verityでは PKCS#7のDER形式のX.509証明書を利用する。
実行環境
計測対象のRaspberry Pi 4 Model Bの詳細は下記のとおりである。
名前 | 詳細 |
---|---|
Hardware | Raspberry Pi 4 Model B Rev 1.2 (4GB) |
OS | Raspbian GNU/Linux 10 (buster) |
Kernel | Linux version 5.10.3-v7l+ |
SDカード | HDMCSDH16GCL10UIJP-WOA |
USBメモリ | SILICON POWER SP032GBUF3B02V1K |
IPアドレス (eth0) | 172.16.1.3 |
ユーザ名 | pi |
ホスト名 | raspberry |
本記事では、SDカードにブートに必要なイメージを格納し、USBメモリに作成したext4ファイルシステムで計測する。
カーネルは前回の記事で作成したものを利用する。
実験
ここまでの作業により、自前ビルドしたカーネルが起動することを確認できる。
そこで、作成したカーネルとルートファイルシステムをSDカードに書き込み、fs-verityを使用してみる。
fsverity-utilsのREADMEに記載されている使用方法に沿ってfs-verityによる改ざんチェックを実験する。
fs-verityの準備
USBメモリ上にext4ファイルシステムを作成し、テスト用のファイル
file
を準備するpi@raspberrypi:~ $ sudo mkfs.ext4 -O verity /dev/sda1 pi@raspberrypi:~ $ mount /dev/sda1 /mnt pi@raspberrypi:~ $ head -c 1000000 /dev/urandom > /mnt/file
OpenSSLを用いて証明書と秘密鍵を生成する
pi@raspberrypi:~ $ openssl req -newkey rsa:4096 -nodes -keyout key.pem -x509 -out cert.pem
証明書をPEM形式からDER形式に変換する
pi@raspberrypi:~ $ openssl x509 -in cert.pem -out cert.der -outform der
fs-verityのキーリングに証明書を追加する
pi@raspberrypi:~ $ sudo keyctl padd asymmetric '' %keyring:.fs-verity < cert.der
fs-verityのキーリングの修正を禁止する
pi@raspberrypi:~ $ sudo keyctl restrict_keyring %keyring:.fs-verity
fs-verity検証時に署名を要求する
pi@raspberrypi:~ $ sudo sysctl fs.verity.require_signatures=1
ハッシュ値を確認する
pi@raspberrypi:~ $ sha256sum /mnt/file 1bfbef29d8891d710007696a02d0ad56297ca46a94eda673c520f4e1fd4daf6d /mnt/file
対象のファイルに署名する
pi@raspberrypi:~ $ fsverity sign /mnt/file file.sig --key=key.pem --cert=cert.pem Signed file '/mnt/file' (sha256:fe35b9281d3711296bcd49f65a6b0c4c26a33b0e2967c40c365ac94d0a4186af)
fs-verityを有効化する
pi@raspberrypi:~ $ sudo fsverity enable /mnt/file --signature=file.sig pi@raspberrypi:~ $ ls -l /mnt/file -rw-r--r-- 1 pi pi 1000000 12月 29 13:49 file
dm-verityの準備
dm-verityでは、改ざんチェックの対象となるブロックデバイス(データデバイス)とチェックのためのブロックデバイス(ハッシュデバイス)の2つを用意する必要がある。
そこで、測定対象デバイス(USBメモリ) 上でパーティション作成した。
このとき、データデバイス/dev/sda1
を10GB、ハッシュデバイス/dev/sda2
を残りの領域に割り当てた。
下記の手順にて測定環境を構築した。
USBメモリ上にext4ファイルシステムを作成し、テスト用にデータを埋めておく
pi@raspberrypi:~ $ sudo mkfs.ext4 /dev/sda1 pi@raspberrypi:~ $ mount /dev/sda1 /mnt pi@raspberrypi:~ $ sudo dd if=/dev/urandom of=/mnt/file bs=1M count=1K
ハッシュデバイスの構築準備
pi@raspberrypi:~ $ sudo veritysetup format /dev/sda1 /dev/sda2 VERITY header information for /dev/sda2 UUID: 551c1f12-7d4d-4cf6-94a5-c03f6e66271f Hash type: 1 Data blocks: 2621440 Data block size: 4096 Hash block size: 4096 Hash algorithm: sha256 Salt: 010b3c6dbfe22ebf306c1eedaa96de0a05cde16f273a8f5212a3fadd6c57ff43 Root hash: 06dbc0a80648219b5ae5a9ab229ecea616541c9a9db548bfa9282b076bdf6598
device-mapper (
/dev/mapper/test
)を作成pi@raspberrypi:~ $ sudo veritysetup create test /dev/sda1 /dev/sda2 06dbc0a80648219b5ae5a9ab229ecea616541c9a9db548bfa9282b076bdf6598 Signed file '/mnt/file' (sha256:fe35b9281d3711296bcd49f65a6b0c4c26a33b0e2967c40c365ac94d0a4186af) pi@raspberrypi:~ $ ls -la /dev/mapper/test lrwxrwxrwx 1 root root 7 12月 30 04:12 /dev/mapper/test -> ../dm-0
性能計測
ここでは、fs-verityを有効化していない場合と有効化した場合でread性能にどれだけ差がでるかを確認する。
ここで、測定時の環境を再掲する。
名前 | 詳細 |
---|---|
Hardware | Raspberry Pi 4 Model B Rev 1.2 (4GB) |
OS | Raspbian GNU/Linux 10 (buster) |
Kernel | Linux version 5.10.3-v7l+ |
OSインストール先 | SDカード |
測定対象デバイス | USBメモリ |
fioで使用したパラメータは下記のとおりである。
- direct I/Oは使用しない
- シーケンシャルリードとランダムリードの2つを計測する
- ブロックサイズは4KB
- 非同期IO
- ファイルサイズ1MBと10GBの2つを計測する
毎回の計測時に下記のような作業を1回実施し、その結果を測定値とした。
- 測定対象デバイスにext4ファイルシステムを作成する
- 1MBまたは10GBのファイルを作成する
- fioでread性能を計測する
- fioでrandread性能を計測する
- fs-verityを有効化する
- fioでread性能を計測する
- fioでrandread性能を計測する
また、fioの計測前に# echo 3 > /proc/sys/vm/drop_caches
を実行してキャッシュを落としておく。
fs-verityの計測
まずは、1MBのファイルに対して計測してみた。
計測に使用したコマンドを下記に示す。
pi@raspberrypi:~/fsverity $ sudo fio --filename=/mnt/file --direct=0 --rw=read --bs=4K --ioengine=libaio --runtime=300 --time_based --name=read1 --readonly --size=1M
pi@raspberrypi:~/fsverity $ sudo fio --filename=/mnt/file --direct=0 --rw=randread --bs=4K --ioengine=libaio --runtime=300 --time_based --name=read1 --readonly --size=1M
ここでは、fioコマンドの結果によって得られたread/randread性能のみ抽出する。 結果は下記の通りである。
fs-verityなし | fs-verityあり | 性能変化率 | |
---|---|---|---|
read性能 | 82.2MiB/s | 43.9MiB/s | -47% |
randread性能 | 6906KiB/s | 5805KiB/s | -16% |
幣著が実験した環境では、シーケンシャルリードの性能が47%程度、ランダムリードが16%程度落ちていることが分かった。
また、10GBのファイルの読み込み性能も同様に計測してみた。
計測に使用したコマンドを下記に示す。
pi@raspberrypi:~/fsverity $ sudo fio --filename=/mnt/file --direct=0 --rw=read --bs=4K --ioengine=libaio --runtime=300 --time_based --name=read1 --readonly --size=10G
pi@raspberrypi:~/fsverity $ sudo fio --filename=/mnt/file --direct=0 --rw=randread --bs=4K --ioengine=libaio --runtime=300 --time_based --name=read1 --readonly --size=10G
ここでは、fioコマンドの結果によって得られたread/randread性能のみ抽出する。 その結果が下記の通りとなった。
fs-verityなし | fs-verityあり | 性能変化率 | |
---|---|---|---|
read性能 | 70.5MiB/s | 27.9MiB/s | -60% |
randread性能 | 3792KiB/s | 3340KiB/s | -12% |
幣著が実験した環境では、シーケンシャルリードの性能が60%程度、ランダムリードが12%程度落ちていることが分かった。
dm-verityの計測
これらを利用して、fs-verityと同様の環境で、dm-verityの性能測定をしてみた。
- direct I/Oは使用しない
- シーケンシャルリードとランダムリードの2つを計測する
- ブロックサイズは4KB
- 非同期IO
- 測定対象デバイスファイルは
/dev/sda1
(dm-verityなし)と/dev/mapper/test
(dm-verityあり)の2つを計測する - 測定対象デバイスファイルに構築されたext4ファイルシステム上の1MBファイルを計測する
dm-verityなし | dm-verityあり | 性能変化率 | |
---|---|---|---|
read性能 | 64.9MiB/s | 43.8MiB/s | -33% |
randread性能 | 6582KiB/s | 5420KiB/s | -18% |
幣著が実験した環境では、シーケンシャルリードの性能が33%程度、ランダムリードが18%程度落ちていることが分かった。
おわりに
本記事ではRaspberry Pi 用カーネルのLinux Kernel 5.10を自前でビルドして、fs-verityを有効化した。
またfioを用いて、fs-verityの有無によるread性能を計測することで、fs-verityのオーバーヘッドを測定した。
その結果、fs-verityを有効化したことでシーケンシャルリードが50%、ランダムリードが15%ほど性能低下していることが確認できた。
シーケンシャルリードの性能が大きく変化していることに関しては調査中であるが、readaheadとの相性が悪いのではないかと考えられる。
付録: fs-verity有効化でファイルを更新してみる
ファイルシステムから該当ファイルの実データを更新する: 実データの更新不可
pi@raspberrypi:~ $ pi@raspberrypi:/mnt $ echo "FIX" >> file -bash: file: 許可されていない操作です
ファイルシステムから該当ファイルのメタデータを更新する: メタデータの更新可能
pi@raspberrypi:~ $ sudo chmod 777 /mnt/file pi@raspberrypi:~ $ ls -l /mnt/file -rwxrwxrwx 1 pi pi 1048576 12月 30 02:31 /mnt/file
ブロックデバイスファイルから該当データを更新する: 読み込み時にエラー
pi@raspberrypi:~ $ umount /mnt # fileの実データが格納されているオフセットを直接書き換え pi@raspberrypi:~ $ sudo dd if=/dev/zero of=/dev/sda1 seek=268435456 bs=1 count=2 pi@raspberrypi:~ $ sudo dd if=/dev/zero of=/dev/sda1 seek=270532608 bs=1 count=2 pi@raspberrypi:~ $ mount /dev/sda1 /mnt pi@raspberrypi:~ $ cat /mnt/file cat: /mnt/file: 入力/出力エラーです
付録: dm-verityで署名機能を利用してみる
dm-verityの構築手順は下記を参考に、ルートハッシュの署名機能を有効にしてみた。
署名用の鍵と証明書を作成する
leava@server:~/linux $openssl req -x509 -newkey rsa:1024 -keyout ca_key.pem -out ca.pem -nodes -days 365 -set_serial 01 -subj /CN=example.com
コンフィグを修正する (
CONFIG_SYSTEM_TRUSTED_KEYS
のパスを指定)leava@server:~/linux $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig -*- Cryptographic API ---> Certificates for signature checking ---> (ca.pem) Additional X.509 keys for default system keyring
カーネルのビルド
leava@server:~/linux $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) zImage modules dtbs
作成した署名用の鍵をRaspberry Pi 4Bにコピーしておく
leava@server:~/linux $ scp ca.pem pi@raspberry:~/ leava@server:~/linux $ scp ca_key.pem pi@raspberry:~/
生成したカーネルイメージをSDカードに焼き、Raspberry Pi 4Bを起動する。
署名なしでルートハッシュの生成する
pi@raspberrypi:~ $ veritysetup format /dev/sda1 /dev/sda2
#!/bin/bash NAME=test DEV=/dev/sda1 DEV_HASH=/dev/sda2 ROOT_HASH=778fccab393842688c9af89cfd0c5cde69377cbe21ed439109ec856f2aa8a423 ★ここは修正する SIGN=sign.txt SIGN_NAME=verity:$NAME # get unsigned device-mapper table using unpatched veritysetup veritysetup open $DEV $NAME $DEV_HASH $ROOT_HASH TABLE=$(dmsetup table $NAME) veritysetup close $NAME # sign root hash directly by CA cert echo -n $ROOT_HASH | openssl smime -sign -nocerts -noattr -binary -inkey ca_key.pem -signer ca.pem -outform der -out $SIGN # load signature to keyring keyctl padd user $SIGN_NAME @u <$SIGN # add device-mapper table, now with sighed root hash optional argument dmsetup create -r $NAME --table "$TABLE 2 root_hash_sig_key_desc $SIGN_NAME" dmsetup table $NAME # cleanup # dmsetup remove $NAME # keyctl clear @u
変更履歴
- 2021/3/1: 記事公開
参考
- dm-verity — The Linux Kernel documentation
- dm-verityの公式ドキュメント
- fs-verity: read-only file-based authenticity protection — The Linux Kernel documentation
- fs-verityの公式ドキュメント
- 図解 X.509 証明書 - Qiita
- デジタル署名の基礎知識
- RSA鍵、証明書のファイルフォーマットについて - Qiita
- RSA鍵のファイルフォーマット
- [RFC PATCH v7 0/1] Add dm verity root hash pkcs7 sig validation.
- dm-verityの署名検証の導入
- [dm-devel] [RFC PATCH v6 0/1] Add dm verity root hash pkcs7 sig validation.
- dm-verityの署名機能の導入