patches and low-level development discussion
 help / color / mirror / code / Atom feed
From: colbyt <colby@colbyt.com>
To: devel@spectrum-os.org
Cc: colbyt <colby@colbyt.com>
Subject: [PATCH 2/5] vm: add shared initramfs for verified roots
Date: Thu, 25 Jun 2026 13:20:10 -0700	[thread overview]
Message-ID: <20260625202013.1417254-3-colby@colbyt.com> (raw)
In-Reply-To: <20260625202013.1417254-1-colby@colbyt.com>

Add a shared initramfs for the app VM and net VM. It loads dm-mod and
dm-verity, finds the root and verity partitions from the supplied root
hash, opens root-verity, and switches into it as the read-only root.

Doing this in the initramfs lets the VM kernels keep dm-verity modular
instead of forcing it built in.

Signed-off-by: colbyt <colby@colbyt.com>
---
 img/app/Makefile           |   5 ++
 img/app/default.nix        |   7 ++-
 pkgs/default.nix           |   1 +
 vm/initramfs/Makefile      |  37 ++++++++++++
 vm/initramfs/default.nix   | 113 +++++++++++++++++++++++++++++++++++++
 vm/initramfs/etc/fstab     |   5 ++
 vm/initramfs/etc/getuuids  |  18 ++++++
 vm/initramfs/etc/init      |  58 +++++++++++++++++++
 vm/initramfs/etc/mdev.conf |   4 ++
 vm/initramfs/etc/probe     |  17 ++++++
 vm/sys/net/Makefile        |   5 ++
 vm/sys/net/default.nix     |   7 ++-
 12 files changed, 275 insertions(+), 2 deletions(-)
 create mode 100644 vm/initramfs/Makefile
 create mode 100644 vm/initramfs/default.nix
 create mode 100644 vm/initramfs/etc/fstab
 create mode 100755 vm/initramfs/etc/getuuids
 create mode 100755 vm/initramfs/etc/init
 create mode 100644 vm/initramfs/etc/mdev.conf
 create mode 100755 vm/initramfs/etc/probe

diff --git a/img/app/Makefile b/img/app/Makefile
index 6e47570..3f60ef8 100644
--- a/img/app/Makefile
+++ b/img/app/Makefile
@@ -14,6 +14,7 @@ imgdir = $(libdir)/spectrum/img
 VMM = cloud-hypervisor
 
 HOST_BUILD_FILES = \
+	$(imgdir)/appvm/initramfs \
 	$(imgdir)/appvm/blk/root.img \
 	$(imgdir)/appvm/blk/rootfs.verity.roothash \
 	$(imgdir)/appvm/vmlinux
@@ -21,6 +22,10 @@ HOST_BUILD_FILES = \
 all: $(HOST_BUILD_FILES)
 .PHONY: all
 
+$(imgdir)/appvm/initramfs: $(INITRAMFS)
+	mkdir -p $$(dirname $@)
+	cp $(INITRAMFS) $@
+
 $(imgdir)/appvm/vmlinux: $(KERNEL)
 	mkdir -p $$(dirname $@)
 	cp $(KERNEL) $@
diff --git a/img/app/default.nix b/img/app/default.nix
index ebd37e7..23487e3 100644
--- a/img/app/default.nix
+++ b/img/app/default.nix
@@ -2,7 +2,7 @@
 # SPDX-FileCopyrightText: 2021-2025 Alyssa Ross <hi@alyssa.is>
 
 import ../../lib/call-package.nix (
-{ spectrum-app-tools, spectrum-build-tools, src, terminfo
+{ callSpectrumPackage, spectrum-app-tools, spectrum-build-tools, src, terminfo
 , lib, appimageTools, buildFHSEnv, runCommand, stdenvNoCC, writeClosure
 , cryptsetup
 , erofs-utils, jq, s6-rc, util-linux, xorg
@@ -46,6 +46,10 @@ let
     ];
   });
 
+  vmInitramfs = callSpectrumPackage ../../vm/initramfs {
+    kernel = kernel.modules;
+  };
+
   appimageFhsenv = (buildFHSEnv (appimageTools.defaultFhsEnvArgs // {
     name = "vm-fhs-env";
     targetPkgs = pkgs: appimageTools.defaultFhsEnvArgs.targetPkgs pkgs ++ [
@@ -118,6 +122,7 @@ stdenvNoCC.mkDerivation {
   ];
 
   env = {
+    INITRAMFS = vmInitramfs;
     KERNEL = "${kernel}/${baseNameOf kernelTarget}";
     PACKAGES = runCommand "packages" {} ''
       printf "%s\n/\n" ${packagesSysroot} >$out
diff --git a/pkgs/default.nix b/pkgs/default.nix
index 4dbdfee..6d179b5 100644
--- a/pkgs/default.nix
+++ b/pkgs/default.nix
@@ -75,6 +75,7 @@ let
         ../tools/mount-flatpak/target
         ../tools/router/target
         ../tools/xdg-desktop-portal-spectrum-host/target
+        ../vm/initramfs/build
         ../vm/sys/net/build
       ]));
 
diff --git a/vm/initramfs/Makefile b/vm/initramfs/Makefile
new file mode 100644
index 0000000..03ae1b4
--- /dev/null
+++ b/vm/initramfs/Makefile
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Colby Russell <colby@colbyt.com>
+
+.POSIX:
+
+include ../../lib/common.mk
+
+dest = build/initramfs
+
+$(dest): build/local.cpio $(PACKAGES_CPIO)
+	cat build/local.cpio $(PACKAGES_CPIO) | gzip -9n > $@
+
+# etc/init isn't included in ETC_FILES, because it gets installed to
+# the root.
+ETC_FILES = etc/getuuids etc/probe etc/fstab etc/mdev.conf
+MOUNTPOINTS = dev mnt/root proc sys tmp
+
+build/local.cpio: $(ETC_FILES) etc/init build/mountpoints
+	printf "%s\n" $(ETC_FILES) | \
+	    awk '{while (length) { print; sub("/?[^/]*$$", "") }}' | \
+	    sort -u | \
+	    $(CPIO) -o $(CPIOFLAGS) > $@
+	cd etc && echo init | $(CPIO) -o $(CPIOFLAGS) -AF ../$@
+	cd build/mountpoints && printf "%s\n" $(MOUNTPOINTS) | \
+	    awk '{while (length) { print; sub("/?[^/]*$$", "") }}' | \
+	    sort -u | \
+	    $(CPIO) -o $(CPIOFLAGS) -AF ../../$@
+
+build/mountpoints:
+	rm -rf build/mountpoints
+	mkdir -p build/mountpoints
+	cd build/mountpoints && mkdir -p $(MOUNTPOINTS)
+	find build/mountpoints -mindepth 1 -exec touch -d @0 {} ';'
+
+clean:
+	rm -rf build
+.PHONY: clean
diff --git a/vm/initramfs/default.nix b/vm/initramfs/default.nix
new file mode 100644
index 0000000..59b01f0
--- /dev/null
+++ b/vm/initramfs/default.nix
@@ -0,0 +1,113 @@
+# SPDX-License-Identifier: MIT
+# SPDX-FileCopyrightText: 2026 Colby Russell <colby@colbyt.com>
+
+import ../../lib/call-package.nix (
+{ src, spectrum-build-tools, kernel ? null
+, lib, stdenvNoCC, makeModulesClosure, runCommand, writeClosure, pkgsStatic
+, busybox, cpio
+}:
+
+pkgsStatic.callPackage ({ execline, kmod, mdevd, cryptsetup, util-linuxMinimal }:
+
+let
+  inherit (lib) concatMapStringsSep filter foldl isString last split tail;
+
+  emptyFirmware = runCommand "empty-firmware" {} ''
+    mkdir -p $out/lib/firmware
+  '';
+
+  modules = if kernel == null then null else makeModulesClosure {
+    firmware = emptyFirmware;
+    inherit kernel;
+    rootModules = [ "dm-mod" "dm-verity" ];
+  };
+
+  packages = [
+    execline kmod mdevd
+
+    (cryptsetup.override {
+      programs = {
+        cryptsetup = false;
+        cryptsetup-reencrypt = false;
+        integritysetup = false;
+      };
+    })
+
+    (busybox.override {
+      enableStatic = true;
+      extraConfig = ''
+        CONFIG_DEPMOD n
+        CONFIG_FINDFS n
+        CONFIG_INSMOD n
+        CONFIG_LSMOD n
+        CONFIG_MODINFO n
+        CONFIG_MODPROBE n
+        CONFIG_RMMOD n
+      '';
+    })
+  ];
+
+  packagesSysroot = runCommand "vm-initramfs-packages-sysroot" {} ''
+    mkdir -p $out/bin
+    ln -s ${concatMapStringsSep " " (p: "${p}/bin/*") packages} $out/bin
+    ln -s /bin $out/sbin
+    ${lib.optionalString (modules != null) "cp -R ${modules}/lib $out"}
+
+    # TODO: this is a hack and we should just build the util-linux
+    # programs we want.
+    # https://lore.kernel.org/util-linux/87zgrl6ufb.fsf@alyssa.is/
+    cp ${util-linuxMinimal}/bin/{findfs,lsblk} $out/bin
+  '';
+
+  storeComponents = tail (filter isString (split "/" builtins.storeDir));
+
+  packagesCpio = runCommand "vm-initramfs-packages.cpio" {
+    nativeBuildInputs = [ cpio ];
+
+    storePaths = writeClosure [ packagesSysroot ];
+    storePrefixes = foldl
+      (acc: elem: acc ++ [ "${if acc == [] then "" else last acc}/${elem}" ])
+      []
+      storeComponents;
+
+    __structuredAttrs = true;
+    unsafeDiscardReferences = { out = true; };
+    dontFixup = true;
+  } ''
+    cd ${packagesSysroot}
+    (printf "%s\n" "''${storePrefixes[@]}" && find . $(< $storePaths)) |
+        cpio -o -H newc -R +0:+0 --reproducible > $out
+  '';
+in
+
+stdenvNoCC.mkDerivation {
+  name = "spectrum-vm-initramfs";
+
+  src = lib.fileset.toSource {
+    root = ../..;
+    fileset = lib.fileset.intersection src (lib.fileset.unions [
+      ./.
+      ../../lib/common.mk
+    ]);
+  };
+  sourceRoot = "source/vm/initramfs";
+
+  env = {
+    PACKAGES_CPIO = packagesCpio;
+  };
+
+  nativeBuildInputs = [ cpio spectrum-build-tools ];
+
+  makeFlags = [ "dest=$(out)" ];
+
+  dontInstall = true;
+
+  enableParallelBuilding = true;
+
+  __structuredAttrs = true;
+  unsafeDiscardReferences = { out = true; };
+  dontFixup = true;
+
+  passthru = { inherit packagesSysroot; };
+}
+) {}) (_: {})
diff --git a/vm/initramfs/etc/fstab b/vm/initramfs/etc/fstab
new file mode 100644
index 0000000..4387224
--- /dev/null
+++ b/vm/initramfs/etc/fstab
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2026 Colby Russell <colby@colbyt.com>
+devtmpfs	/dev	devtmpfs	nosuid			0	0
+proc		/proc	proc		nosuid,nodev,noexec	0	0
+sysfs		/sys	sysfs		nosuid,nodev,noexec	0	0
diff --git a/vm/initramfs/etc/getuuids b/vm/initramfs/etc/getuuids
new file mode 100755
index 0000000..62f8935
--- /dev/null
+++ b/vm/initramfs/etc/getuuids
@@ -0,0 +1,18 @@
+#!/bin/awk -f
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2021, 2023 Alyssa Ross <hi@alyssa.is>
+
+function to_uuid(hex) {
+    return substr(hex, 1, 8) "-" substr(hex, 9, 4) "-" substr(hex, 13, 4) "-" \
+        substr(hex, 17, 4) "-" substr(hex, 21)
+}
+
+BEGIN {
+    if (length(ENVIRON["roothash"]) != 64) {
+	system("echo 'roothash invalid or missing' >&2")
+	exit 1
+    }
+
+    print to_uuid(substr(ENVIRON["roothash"], 1, 32))
+    print to_uuid(substr(ENVIRON["roothash"], 33, 32))
+}
diff --git a/vm/initramfs/etc/init b/vm/initramfs/etc/init
new file mode 100755
index 0000000..4e2df2b
--- /dev/null
+++ b/vm/initramfs/etc/init
@@ -0,0 +1,58 @@
+#!/bin/execlineb -WS0
+# SPDX-FileCopyrightText: 2021-2022 Alyssa Ross <hi@alyssa.is>
+# SPDX-FileCopyrightText: 2026 Colby Russell <colby@colbyt.com>
+# SPDX-License-Identifier: EUPL-1.2+
+
+export PATH /bin
+
+if { mount -a }
+
+if { mkdir -p /sys/kernel/security }
+foreground { mount -t securityfs securityfs /sys/kernel/security }
+
+piperw 3 4
+if { fdmove 1 4 /etc/getuuids }
+fdclose 4
+# head -1 would be clearer, but it might use buffered I/O and consume
+# too much from the fifo.  Ideally we'd have line(1) from illumos.
+backtick ROOTFS_UUID { fdmove 0 3 dd count=1 bs=37 status=none }
+backtick VERITY_UUID { fdmove 0 3 dd count=1 bs=37 status=none }
+fdclose 3
+
+if { mkfifo /dev/rootfs.poll }
+
+background {
+  fdclose 3
+  mdevd -C -b134217728
+}
+importas -iu mdevd_pid !
+
+if { modprobe dm-mod }
+if { modprobe dm-verity }
+
+# Do a blocking read on the fifo to wait for mdevd to find the
+# partition.
+if {
+  redirfd -r 0 /dev/rootfs.poll
+  redirfd -w 1 /dev/null
+  head -c 1
+}
+background { kill $mdevd_pid }
+background { rm /dev/rootfs.poll }
+
+if {
+  importas -Si roothash
+  veritysetup open /dev/rootfs root-verity /dev/verity $roothash
+}
+
+background { rm /dev/rootfs /dev/verity }
+
+if { mount -o ro,nosuid,nodev /dev/mapper/root-verity /mnt/root }
+wait { $mdevd_pid }
+
+if { mount --move /proc /mnt/root/proc }
+if { mount --move /sys /mnt/root/sys }
+if { mount --move /dev /mnt/root/dev }
+
+switch_root /mnt/root
+/etc/init
diff --git a/vm/initramfs/etc/mdev.conf b/vm/initramfs/etc/mdev.conf
new file mode 100644
index 0000000..0f4655d
--- /dev/null
+++ b/vm/initramfs/etc/mdev.conf
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
+#
+$DEVTYPE=partition 0:0 660 +/etc/probe
diff --git a/vm/initramfs/etc/probe b/vm/initramfs/etc/probe
new file mode 100755
index 0000000..2ebeb8b
--- /dev/null
+++ b/vm/initramfs/etc/probe
@@ -0,0 +1,17 @@
+#!/bin/execlineb -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
+
+# Check whether we now have all the partitions we need to boot.
+
+importas -i rootfs_uuid ROOTFS_UUID
+importas -i verity_uuid VERITY_UUID
+
+backtick -E rootfs_dev { findfs PARTUUID=${rootfs_uuid} }
+backtick -E verity_dev { findfs PARTUUID=${verity_uuid} }
+
+if { rm -f /dev/rootfs /dev/verity }
+if { ln -s $rootfs_dev /dev/rootfs }
+if { ln -s $verity_dev /dev/verity }
+
+redirfd -w -nb 1 /dev/rootfs.poll echo
diff --git a/vm/sys/net/Makefile b/vm/sys/net/Makefile
index c6cf7f2..d01d766 100644
--- a/vm/sys/net/Makefile
+++ b/vm/sys/net/Makefile
@@ -13,6 +13,7 @@ vmdir = $(libdir)/spectrum/vm
 VMM = cloud-hypervisor
 
 HOST_BUILD_FILES = \
+	$(vmdir)/netvm/initramfs \
 	$(vmdir)/netvm/blk/root.img \
 	$(vmdir)/netvm/blk/rootfs.verity.roothash \
 	$(vmdir)/netvm/vmlinux
@@ -20,6 +21,10 @@ HOST_BUILD_FILES = \
 all: $(HOST_BUILD_FILES)
 .PHONY: all
 
+$(vmdir)/netvm/initramfs: $(INITRAMFS)
+	mkdir -p $$(dirname $@)
+	cp $(INITRAMFS) $@
+
 $(vmdir)/netvm/vmlinux: $(KERNEL)
 	mkdir -p $$(dirname $@)
 	cp $(KERNEL) $@
diff --git a/vm/sys/net/default.nix b/vm/sys/net/default.nix
index 8549bf3..7f077a9 100644
--- a/vm/sys/net/default.nix
+++ b/vm/sys/net/default.nix
@@ -2,7 +2,7 @@
 # SPDX-FileCopyrightText: 2021-2023 Alyssa Ross <hi@alyssa.is>
 
 import ../../../lib/call-package.nix (
-{ spectrum-build-tools, src, terminfo, pkgsMusl }:
+{ callSpectrumPackage, spectrum-build-tools, src, terminfo, pkgsMusl }:
 pkgsMusl.callPackage (
 
 { lib, stdenvNoCC, nixos, runCommand, writeClosure
@@ -52,6 +52,10 @@ let
     ];
   });
 
+  vmInitramfs = callSpectrumPackage ../../initramfs {
+    kernel = kernel.modules;
+  };
+
   packages = [
     dbus execline kmod mdevd s6 s6-linux-init s6-rc xdp-tools
 
@@ -124,6 +128,7 @@ stdenvNoCC.mkDerivation {
   ];
 
   env = {
+    INITRAMFS = vmInitramfs;
     KERNEL = "${kernel}/${baseNameOf kernelTarget}";
     PACKAGES = runCommand "packages" {} ''
       printf "%s\n/\n" ${packagesSysroot} >$out
-- 
2.54.0


  parent reply	other threads:[~2026-06-25 20:21 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-25 20:20 [PATCH 0/5] vm: boot sub-VMs from dm-verity roots and enable IPE colbyt
2026-06-25 20:20 ` [PATCH 1/5] vm: generate dm-verity metadata for sub-VM roots colbyt
2026-06-25 20:20 ` colbyt [this message]
2026-06-25 20:20 ` [PATCH 3/5] vm: boot sub-VMs through verified roots colbyt
2026-06-25 20:20 ` [PATCH 4/5] vm: enable IPE in sub-VM kernels colbyt
2026-06-25 20:20 ` [PATCH 5/5] host/rootfs: enable IPE in the host kernel colbyt
2026-07-02 13:44   ` Alyssa Ross
2026-06-26 15:14 ` [PATCH 0/5] vm: boot sub-VMs from dm-verity roots and enable IPE Alyssa Ross
2026-06-26 19:12   ` colby
2026-07-02 13:43     ` Alyssa Ross
2026-07-02 23:16       ` colby
2026-07-03 14:06         ` Alyssa Ross

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260625202013.1417254-3-colby@colbyt.com \
    --to=colby@colbyt.com \
    --cc=devel@spectrum-os.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://spectrum-os.org/git/crosvm
	https://spectrum-os.org/git/doc
	https://spectrum-os.org/git/mktuntap
	https://spectrum-os.org/git/nixpkgs
	https://spectrum-os.org/git/spectrum
	https://spectrum-os.org/git/ucspi-vsock
	https://spectrum-os.org/git/www

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).