初めに
前回はBuildrootでinitramfsを作成し起動する方法を試した。
今回は、本番用rootfsをinitramfs + dm-verityで検証することで改ざんを検知できるようにしていく。
dm-verityとは
dm-verityとは、linux kernelに備えられたブロックデバイスを検証する仕組みのこと。
詳しくは以下の公式ドキュメントなどを参照してください。
https://docs.kernel.org/6.1/admin-guide/device-mapper/verity.html
作るもの
以下の流れでrootfsを検証し起動するように作成する。
1. initramfsのinitスクリプトを起動
2. initramfsに含めたroot hashと別のパーティションに格納したhash treeを用いてrootfsの入っているパーティションを検証
3. device-mapperでread onlyのデバイスとしてrootfsの入っているパーティションが認識されるので、適当な場所にマウント
4. switch_rootでrootfsを切り替え
実施環境
- ビルド環境
windows11のwslでubuntu24.04を使用してビルドなどを行い、sdカードの操作はwindows側で行った。 - 実機
ラズパイ4B - 対象バージョン
ラズパイOSの公式イメージ「2024-03-15-raspios-bookworm-arm64-lite.img.xz」と同等。
手順
まずは開発環境で作業を行う。
sdカードイメージのマウント
sdカードイメージを編集するのでラズパイOSのimgファイルの各パーティションをマウントしておく。
せっかくなのでマウントには自作ツールを使用する。
使い方は以下を参照。本記事ではこのツールがホームディレクトリに配置してある前提で話を進める。
losetupコマンドなどを使って手動でマウントを行っても問題ない。
https://aimdevel.hatenablog.com/entry/2023/08/05/232643
mkdir ~/dm-verity-work cd ~/dm-verity-work/ mkdir boot rootfs wget https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz xz -d 2024-03-15-raspios-bookworm-arm64-lite.img.xz sudo ~/part-mount -p1 2024-03-15-raspios-bookworm-arm64-lite.img boot sudo ~/part-mount -p2 2024-03-15-raspios-bookworm-arm64-lite.img rootfs
rootfsの準備
ラズパイOSのrootfsをそのまま流用する。
まずはどこにrootfsが入っているパーティションがあるか確認する。
df -h | grep dm-verity-work/rootfs
上記のコマンドの出力が以下のようになり、/dev/loop1
がrootfsのパーティションに対応したループデバイスであることがわかる。このデバイスをダンプする。
/dev/loop1 2.0G 1.6G 313M 84% /home/ubuntu/dm-verity-work/rootfs
/dev/loop1をダンプする場合は以下のように実行する。
sudo e2image -raf -p /dev/loop1 rootfs.img
sdカードイメージを使い終わったら一度アンマウントしておく。
sudo umount boot sudo umount rootfs
hash treeの作成
以下のサイトを参考に作業し、検証が成功することを確認する。
https://qiita.com/propella/items/f7cb0534a0a19f51b09b
truncate -s 100M hash.img veritysetup -v --debug format rootfs.img hash.img
以下のような出力が得られる。Root hashの値を使用するのでメモっておくこと。
VERITY header information for hash.img UUID: bca5d298-31dd-429d-bc98-0f545b033c61 Hash type: 1 Data blocks: 543744 Data block size: 4096 Hash blocks: 4283 Hash block size: 4096 Hash algorithm: sha256 Salt: 672d635155bc264a61c92640f2351decccbb58d32dc8793ce6c3fec139ef1209 Root hash: 3d203e1917909149841a8465e71a50f23e27d8b5e191ebea04b90ba442fe6891 <== ここをメモっておく。 Hash device size: 17547264 [bytes] # Releasing crypt device hash.img context. # Releasing device-mapper backend. # Closing read write fd for hash.img. Command successful.
検証を試す。<your Root hash>
には先ほどメモしたRoot hashの値を使用する。
mkdir verity-test sudo veritysetup open rootfs.img verity-test hash.img <your Root hash> sudo mount -o noload /dev/mapper/verity-test verity-test
成功すると/dev/mapper/verity-test
が、read onlyで~/dm-verity-work/verity-test
にマウントされる。
確認したらマウントやdevice-mapperを掃除しておく。
sudo umount verity-test sudo veritysetup close verity-test
以上でdm-verityの試用は終わり。
次はsdカードイメージを作成していく。
rootfs.imgとhash.imgの格納
sdカードイメージにhash.img
を格納する。
新規にパーティションを作成してそこに書き込む。
truncate -s 4G 2024-03-15-raspios-bookworm-arm64-lite.img sudo fdisk 2024-03-15-raspios-bookworm-arm64-lite.img
fdiskで以下のように操作して新しいパーティションを作成する。セクタの値などはラズパイOSのバージョンによって異なるかもしないので、適宜変更する。
Welcome to fdisk (util-linux 2.39.3). Changes will remain in memory only, until you decide to write them. Be careful before using the write command. Command (m for help): p Disk 2024-03-15-raspios-bookworm-arm64-lite.img: 4 GiB, 4294967296 bytes, 8388608 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0xfb33757d Device Boot Start End Sectors Size Id Ty 2024-03-15-raspios-bookworm-arm64-lite.img1 8192 1056767 1048576 512M c W9 2024-03-15-raspios-bookworm-arm64-lite.img2 1056768 5406719 4349952 2.1G 83 Li Command (m for help): n Partition type p primary (2 primary, 0 extended, 2 free) e extended (container for logical partitions) Select (default p): p Partition number (3,4, default 3): First sector (2048-8388607, default 2048): 5439488 Last sector, +/-sectors or +/-size{K,M,G,T,P} (5439488-8388607, default 8388607): +200M Created a new partition 3 of type 'Linux' and of size 200 MiB. Command (m for help): w The partition table has been altered. Syncing disks.
次に、rootfs.img
とhash.img
をそれぞれ書き込む。
一度各パーティションをloopデバイスとして見える状態にしておく。
以下のコマンドで空いているloopデバイスを確認し、
sudo losetup -f
以下のコマンドで設定、書き込みを行う。自分の環境では/dev/loop0
が空いていた。第二パーティションにrootfs.img
を、第三パーティションにhash.img
を書き込む。
sudo losetup -P /dev/loop0 2024-03-15-raspios-bookworm-arm64-lite.img sudo dd if=rootfs.img of=/dev/loop0p2 sudo dd if=hash.img of=/dev/loop0p3 sudo losetup -d /dev/loop0
Linux kernelのビルド
ラズパイ用のdefconfigファイルではdm-verityが無効なので、これを有効にしたLinux kernelをビルドする。
過去のビルド結果が残っている場合はそれを使いまわしてもよい。
まずはソースコードを取得する。
wget https://github.com/raspberrypi/linux/archive/refs/tags/stable_20240423.tar.gz tar xf stable_20240423.tar.gz cd linux-stable_20240423
以下のようにしてarch/arm64/configs/bcm2711_defconfig
に追記する。
echo "CONFIG_DM_VERITY=y" >> arch/arm64/configs/bcm2711_defconfig
ビルドする。
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig make -j`nproc` ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
initramfsの作成
ベースとなるrootfsは、前回作成したveritysetupがインストールされたものを使用する。
今回は前回のinitramfsに、/init
ファイルの修正とkernelモジュールのインストールを行う。
/init
を以下の内容にする。変数VERITY_ROOTHASH
に各自のroot hashを指定するようにしている。
#!/bin/sh VERITY_ROOTHASH=3d203e1917909149841a8465e71a50f23e27d8b5e191ebea04b90ba442fe6891 mount -v --bind /dev /dev mount -v --bind /dev/pts /dev/pts mount -vt proc proc /proc mount -vt sysfs sysfs /sys mount -vt tmpfs tmpfs /run mdev -s echo "start switch_root!" # parse cmdline cmdline=$(cat /proc/cmdline) for param in $cmdline; do echo $param case $param in root=*) ROOT=`echo $param | sed 's/.*=//'` ;; esac done # wait rootfs for i in {1..30} do /sbin/mdev -s TMP=`ls -l /dev/ | grep $ROOT` if [ -n "${TMP}" ];then break fi echo "waiting rootfs..." sleep 1 done # veritysetup veritysetup open $ROOT verity-root /dev/mmcblk0p3 $VERITY_ROOTHASH # mount mrootfs. mkdir -p /dev/root mount -o noload /dev/mapper/verity-root /dev/root # switch root exec switch_root -c /dev/console /dev/root /sbin/init
さらに以下のコマンドでkernel moduleをインストールする。インストール先は前回の記事で使用したrootfsとしている。
cd ~/dm-verity-work/linux-stable_20240423 make modules_install INSTALL_MOD_PATH=~/initramfs-buildroot/buildroot-2024.02.3/output/target/
FIT imageの作成
前回までと同様に上記のinitスクリプトを含んだFIT imageを作成する。ここは前回から変更なしなので、コマンドは割愛。
ファイルの格納
必要なファイルをsdカードイメージに格納する。ここでもimgファイルをマウントして作業する。
今回はsdカードイメージに直接手を加えたので、bootパーティション(第一パーティション)に以下の作業をすべて行う。
- FITImage格納
作成したFITImage
を格納する。 - u-boot.binの格納
以前の記事で作成したu-boot.bin
を格納する。
config.txt編集
config.txtの以下の設定を行う。enable_uart=1 arm_64bit=1 kernel=u-boot.bin
sdカードイメージを圧縮(任意)
sdカードイメージを圧縮してサイズを小さくしておくと容量を食わなくてよい。
多分動作確認後にやることだとは思うが。
gzip 2024-03-15-raspios-bookworm-arm64-lite.img
sdカードイメージのフラッシュ
sdカードイメージが完成したので、sdカードにフラッシュする。
windowsの場合はRapsberry Pi Imagerなどで書き込める。
以降の作業はラズパイ実機で行う。
作成したsdカードを使用して起動する。
u-bootの設定を変更する。
u-bootコンソールで以下の設定を行う。
setenv bootargs "coherent_pool=1M 8250.nr_uarts=1 root=/dev/mmcblk0p2 rw rootwait" setenv bootcmd "fatload mmc 0:1 0x1000000 FITImage;bootm 0x1000000;" saveenv
起動する
以下のようなログが出てdm-verityで検証してマウントされていることを確認できる。
[ 4.450015] device-mapper: ioctl: 4.48.0-ioctl (2023-03-01) initialised: dm-devel@redhat.com [ 4.480041] device-mapper: verity: sha256 using implementation "sha256-generic" [ 4.504788] EXT4-fs: Mount option(s) incompatible with ext2 [ 4.520125] EXT4-fs (dm-0): mounted filesystem 93c89e92-8f2e-4522-ad32-68faed883d2f ro without journal. Quota mode: none
そのままラズパイOSが起動するが、うまくログインできない。ラズパイOSのデフォルトユーザってpiでパスワードraspberryじゃなかったっけ?rootfsのro化の影響?
まあ、とりあえず起動までは試せたので今回はここまでにしておく。
終わりに
Linuxのrootfsをdm-verityで検証できるようにした。
ただ、このままだとログインできないし、ファイルも書き込めないので、次は書き込み可能領域を作成して再挑戦してみようかと思う。