aimdevel’s blog

勉強したことを書きます

dm-verityとOverlayFSを組み合わせてディレクトリの書き込み許可制御

初めに

前回はrootfsをdm-verityで検証してから起動するように設定した。

aimdevel.hatenablog.com

ただし、そのままではrootfsがすべてread onlyになってしまうので、必要なディレクトリを書き込み可能にしたい。
そこで今回はOverlayFSを使用して書き込み可能領域の作成を試みる。

OverlayFSとは

複数のディレクトリを1つのマウントポイントに階層構造に重ねてマージする仕組み。
最上層がread/write可能で、下層はread onlyとしてマウントされる。Linuxのコンテナでも使用されているらしい。
詳しくは以下のドキュメントなどを参照。

Overlay Filesystem — The Linux Kernel documentation

作るもの

前回作成したdm-verityでrootfsを検証する流れにOverlayFSを組み込む。
起動の流れは以下を目指す。

  1. initramfsのinitスクリプトを起動
  2. initramfsに含めたroot hashと別のパーティションに格納したhash treeを用いてrootfsの入っているパーティションを検証
  3. device-mapperでread onlyのデバイスとしてrootfsの入っているパーティションが認識されるので、適当な場所にマウント
  4. 書き込み用ディレクトリのあるパーティションを適当な場所にマウント
  5. OverlayFSで書き込み用ディレクトリをマウント
  6. switch_rootでrootfsを切り替え
  7. ラズパイOSのrootfsで起動

手順

前回作成したsdカードイメージを編集していく。

書き込み用パーティションを作成

前回と同様にsdカードイメージにパーティションを追加する。
今回は2GBの書き込み用パーティションを追加で作成する。サイズは適当。

ここでの2024-03-15-raspios-bookworm-arm64-lite.imgは前回作成したdm-verity対応がされたsdカードイメージである。

truncate -s 8G 2024-03-15-raspios-bookworm-arm64-lite.img
sudo fdisk 2024-03-15-raspios-bookworm-arm64-lite.img

書き込み用ディレクトリを作成

作成したパーティションをフォーマット、マウントして、書き込み用ディレクトリを作成する。
ディレクトリはデータ保存用にhome, var, etcと、作業用にoverlayfs-workを作成する。
コマンドは以下。losetup関連のコマンドはloop0が空いているものとして記載する。

sudo losetup -f 
sudo losetup -P /dev/loop0 2024-03-15-raspios-bookworm-arm64-lite.img
mkfs.ext4  /dev/loop0p5 
mkdir tmp
sudo mount /dev/loop0p5 tmp/
sudo mkdir tmp/{home,var,etc,overlayfs-work}
sudo mkdir tmp/overlayfs-work/{home,var,etc}
sudo umount tmp
sudo losetup -d /dev/loop0

initramfsのinitを修正

前回までと同様にinitを変更する。
以下の内容にする。前回のとの差分はOverlayFSの設定のみ。rootfsは変更していないので、VERITY_ROOTHASHの値は変更なし。

#!/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 rootfs.
mkdir -p /dev/root
mount -o noload /dev/mapper/verity-root /dev/root

# mount rw partition
mkdir -p /dev/rw-partition
mount /dev/mmcblk0p5 /dev/rw-partition

# mount overlayfs
mount -t overlay overlay -olowerdir=/dev/root/home,upperdir=/dev/rw-partition/home,workdir=/dev/rw-partition/overlayfs-work/home /dev/root/home
mount -t overlay overlay -olowerdir=/dev/root/var,upperdir=/dev/rw-partition/var,workdir=/dev/rw-partition/overlayfs-work/var /dev/root/var
mount -t overlay overlay -olowerdir=/dev/root/etc,upperdir=/dev/rw-partition/etc,workdir=/dev/rw-partition/overlayfs-work/etc /dev/root/etc

# switch root
exec switch_root -c /dev/console /dev/root /sbin/init

前回までと同様に上記のinitスクリプトを含めたFITImageを作成する。
詳しい作り方は過去の記事を参照。

その後作成したFITImageをsdカードイメージの第一パーティションに格納する。

sdカードイメージのフラッシュ

イメージが完成したので、sdカードにフラッシュする。
windowsの場合はRapsberry Pi Imagerなどで書き込める。

前回の記事で言っていたログインできない問題について

どうやらラズパイの初期ユーザpiは廃止されたようなので、書き込みの際にRaspberry Pi Imagerで適当なユーザを作るように設定が必要かもしれない。
しかし、ユーザを設定したかと思ったら、pi:raspberryでログインできたりして、ここに関しては動きがよくわからない。ラズパイ特有の話だと思うので深くは追いかけないことにする。
「Raspberry Pi OS」、最新リリースでデフォルトユーザー「pi」削除--セキュリティ強化 - ZDNET Japan


以後の作業はラズパイ実機で行う。

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

この設定をいちいち行うのも面倒なので、u-bootをビルドするときに設定を追加したほうがいい気がしてきた。

起動する

起動してファイルを書き込もうとすると、/var, /home, /etc以外のディレクトリがread onlyになっていることを確認できる。
また、mountコマンドを実行すると、以下のようにoverlayfsが使用されていることも見れる。

overlay on /home type overlay (rw,relatime,lowerdir=/dev/root/home,upperdir=/dev/rw-partition/home,workdir=/dev/rw-partition/overlayfs-work/home)
overlay on /var type overlay (rw,relatime,lowerdir=/dev/root/var,upperdir=/dev/rw-partition/var,workdir=/dev/rw-partition/overlayfs-work/var)
overlay on /etc type overlay (rw,relatime,lowerdir=/dev/root/etc,upperdir=/dev/rw-partition/etc,workdir=/dev/rw-partition/overlayfs-work/etc)

終わりに

dm-verityで検証したread onlyなrootfsの一部を書き込み可能にするために、OverlayFSを使用した。
特定のディレクトリだけ書き込み可能な設定を行えた。そのため、書き込みが必要なディレクトリさえ決まって入れば、必要最低限のディレクトリだけを書き込み可能にすることで、想定外のファイルを編集されることや問題のあるソフトウェアのインストールを防いだりできそうだ。