From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from atuin.qyliss.net (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id 485E7A583; Thu, 25 Jun 2026 20:21:21 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 993) id 25E2CA4BB; Thu, 25 Jun 2026 20:21:17 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-0.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DMARC_PASS,RCVD_IN_DNSWL_LOW,SPF_HELO_PASS autolearn=unavailable autolearn_force=no version=4.0.1 Received: from mail-244107.protonmail.ch (mail-244107.protonmail.ch [109.224.244.107]) by atuin.qyliss.net (Postfix) with ESMTPS id 2857EA496 for ; Thu, 25 Jun 2026 20:21:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=colbyt.com; s=protonmail; t=1782418873; x=1782678073; bh=imuwEeLwzqKnEVwb6z7EBMTkPqQ9l28IbYJ+qNgiInQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:From:To: Cc:Date:Subject:Reply-To:Feedback-ID:Message-ID:BIMI-Selector; b=nEXHnMWZGioP/Xmb1SiRkKmaDBZKxWeQi39pbsYh/DVPLysBZrJgQ1jTD81JYpbVb UgGJiaC5TwGNhEqlgaNKiYA6dYLxJwBFBCeM695rvBCcSQ1cVWbhRsld4qdCES3Nn8 fyylXAGKhzev6IFnmQ2LPn1jtRpPhJNErIei40EZHAppFcf5kRe8Xrnt7xet/Tiw/I PR8qbGZJZTV/+bLYN14nPkSi0wz2oT8WWkKA92BGup069pf1OwITZp9xRpmTM7w2qF /x53Oj1oGDXUvrceFO/yMdlPtdZqjMlRrpuLh1eR3uP9gLCcdj16+ZWMo7M88MwRYh WMEpazfga3tnA== X-Pm-Submission-Id: 4gmVZ01zCJz1DFGF From: colbyt To: devel@spectrum-os.org Subject: [PATCH 2/5] vm: add shared initramfs for verified roots Date: Thu, 25 Jun 2026 13:20:10 -0700 Message-ID: <20260625202013.1417254-3-colby@colbyt.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260625202013.1417254-1-colby@colbyt.com> References: <20260625202013.1417254-1-colby@colbyt.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Message-ID-Hash: VBIEIQ2HAI5UGXUX6HVXNPR72TXKN7NF X-Message-ID-Hash: VBIEIQ2HAI5UGXUX6HVXNPR72TXKN7NF X-MailFrom: colby@colbyt.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-devel.spectrum-os.org-0; header-match-devel.spectrum-os.org-1; header-match-devel.spectrum-os.org-2; header-match-devel.spectrum-os.org-3; header-match-devel.spectrum-os.org-4; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: colbyt X-Mailman-Version: 3.3.10 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 --- 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 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 + +.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 + +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 +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 + +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 +# SPDX-FileCopyrightText: 2026 Colby Russell +# 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 +# +$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 + +# 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 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