diff --git a/flake.lock b/flake.lock index 7e49cc1..8be1779 100644 --- a/flake.lock +++ b/flake.lock @@ -106,11 +106,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1689951833, - "narHash": "sha256-wdpIgb5X0p85RRne74TeUOp9ti7a1k9KDSe4NzsaAGk=", + "lastModified": 1695859332, + "narHash": "sha256-w2a7NW3VtI5FgFPUKslYRGAj5Qb7y4i0I2QO0S/lBMQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ebf4e87429ce7faa51a86a36a7b2e615c8bcc735", + "rev": "248a83fffc10b627da67fa6b25d2c13fc7542628", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 73a9e69..fd32041 100644 --- a/flake.nix +++ b/flake.nix @@ -74,11 +74,12 @@ "x86_64-linux" # Not actively tested, but may work: - # "aarch64-linux" + "aarch64-linux" ]; perSystem = { config, system, pkgs, ... }: let + rustTarget = "${pkgs.hostPlatform.qemuArch}-unknown-uefi"; pkgs = import nixpkgs { system = system; overlays = [ @@ -89,7 +90,7 @@ inherit (pkgs) lib; uefi-rust-stable = pkgs.rust-bin.fromRustupToolchainFile ./rust/uefi/rust-toolchain.toml; - craneLib = crane.lib.x86_64-linux.overrideToolchain uefi-rust-stable; + craneLib = crane.lib.${system}.overrideToolchain uefi-rust-stable; # Build attributes for a Rust application. buildRustApp = lib.makeOverridable ( @@ -145,7 +146,7 @@ stubCrane = buildRustApp { pname = "lanzaboote-stub"; src = craneLib.cleanCargoSource ./rust/uefi; - target = "x86_64-unknown-uefi"; + target = rustTarget; doCheck = false; }; @@ -234,6 +235,7 @@ let systemdUkify = pkgs.systemdMinimal.override { withEfi = true; + withBootloader = true; withUkify = true; }; in diff --git a/nix/modules/lanzaboote.nix b/nix/modules/lanzaboote.nix index 92939db..287f3b1 100644 --- a/nix/modules/lanzaboote.nix +++ b/nix/modules/lanzaboote.nix @@ -103,7 +103,10 @@ in ${sbctlWithPki}/bin/sbctl enroll-keys --yes-this-might-brick-my-machine ''} + # Use the system from the kernel's hostPlatform because this should + # always, even in the cross compilation case, be the right system. ${cfg.package}/bin/lzbt install \ + --system ${config.boot.kernelPackages.stdenv.hostPlatform.system} \ --systemd ${config.systemd.package} \ --systemd-boot-loader-config ${loaderConfigFile} \ --public-key ${cfg.publicKeyFile} \ diff --git a/rust/tool/shared/src/architecture.rs b/rust/tool/shared/src/architecture.rs new file mode 100644 index 0000000..94b1761 --- /dev/null +++ b/rust/tool/shared/src/architecture.rs @@ -0,0 +1,35 @@ +use std::path::PathBuf; + +use anyhow::{bail, Result}; + +/// Supported system +#[non_exhaustive] +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Architecture { + X86, + AArch64, +} + +impl Architecture { + pub fn efi_representation(&self) -> &str { + match self { + Self::X86 => "x64", + Self::AArch64 => "aa64", + } + } + + pub fn efi_fallback_filename(&self) -> PathBuf { + format!("BOOT{}.EFI", self.efi_representation().to_ascii_uppercase()).into() + } +} + +impl Architecture { + /// Converts from a NixOS system double to a supported system + pub fn from_nixos_system(system_double: &str) -> Result { + Ok(match system_double { + "x86_64-linux" => Self::X86, + "aarch64-linux" => Self::AArch64, + _ => bail!(format!("Unsupported NixOS system: {}.", system_double)), + }) + } +} diff --git a/rust/tool/shared/src/esp.rs b/rust/tool/shared/src/esp.rs index 27cd071..73a1fa6 100644 --- a/rust/tool/shared/src/esp.rs +++ b/rust/tool/shared/src/esp.rs @@ -3,14 +3,16 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; +use indoc::indoc; +use crate::architecture::Architecture; use crate::generation::Generation; /// Generic ESP paths which can be specific to a bootloader pub trait EspPaths { /// Build an ESP path structure out of the ESP root directory - fn new(esp: impl AsRef) -> Self; + fn new(esp: impl AsRef, arch: Architecture) -> Self; /// Return the used file paths to store as garbage collection roots. fn iter(&self) -> std::array::IntoIter<&PathBuf, N>; @@ -33,8 +35,17 @@ impl EspGenerationPaths { pub fn new>( esp_paths: &P, generation: &Generation, + system: Architecture, ) -> Result { let bootspec = &generation.spec.bootspec.bootspec; + let bootspec_system: Architecture = Architecture::from_nixos_system(&bootspec.system)?; + + if system != bootspec_system { + bail!(indoc! {r#" + The CPU architecture declared in your module differs from the one declared in the + bootspec of the current generation. + "#}) + } Ok(Self { kernel: esp_paths diff --git a/rust/tool/shared/src/lib.rs b/rust/tool/shared/src/lib.rs index eb3c899..35c1465 100644 --- a/rust/tool/shared/src/lib.rs +++ b/rust/tool/shared/src/lib.rs @@ -1,3 +1,4 @@ +pub mod architecture; pub mod esp; pub mod gc; pub mod generation; diff --git a/rust/tool/systemd/src/architecture.rs b/rust/tool/systemd/src/architecture.rs new file mode 100644 index 0000000..173f148 --- /dev/null +++ b/rust/tool/systemd/src/architecture.rs @@ -0,0 +1,19 @@ +use std::path::PathBuf; + +use lanzaboote_tool::architecture::Architecture; + +/// Systemd-specific architecture helpers +pub trait SystemdArchitectureExt { + fn systemd_stub_filename(&self) -> PathBuf; + fn systemd_filename(&self) -> PathBuf; +} + +impl SystemdArchitectureExt for Architecture { + fn systemd_stub_filename(&self) -> PathBuf { + format!("linux{}.efi.stub", self.efi_representation()).into() + } + + fn systemd_filename(&self) -> PathBuf { + format!("systemd-boot{}.efi", self.efi_representation()).into() + } +} diff --git a/rust/tool/systemd/src/cli.rs b/rust/tool/systemd/src/cli.rs index df86121..90d91c0 100644 --- a/rust/tool/systemd/src/cli.rs +++ b/rust/tool/systemd/src/cli.rs @@ -4,6 +4,7 @@ use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; use crate::install; +use lanzaboote_tool::architecture::Architecture; use lanzaboote_tool::signature::KeyPair; /// The default log level. @@ -30,6 +31,10 @@ enum Commands { #[derive(Parser)] struct InstallCommand { + /// System for lanzaboote binaries, e.g. defines the EFI fallback path + #[arg(long)] + system: String, + /// Systemd path #[arg(long)] systemd: PathBuf, @@ -90,6 +95,7 @@ fn install(args: InstallCommand) -> Result<()> { install::Installer::new( PathBuf::from(lanzaboote_stub), + Architecture::from_nixos_system(&args.system)?, args.systemd, args.systemd_boot_loader_config, key_pair, diff --git a/rust/tool/systemd/src/esp.rs b/rust/tool/systemd/src/esp.rs index e3afd6e..7227367 100644 --- a/rust/tool/systemd/src/esp.rs +++ b/rust/tool/systemd/src/esp.rs @@ -1,5 +1,7 @@ use std::path::{Path, PathBuf}; +use crate::architecture::SystemdArchitectureExt; +use lanzaboote_tool::architecture::Architecture; use lanzaboote_tool::esp::EspPaths; /// Paths to the boot files that are not specific to a generation. @@ -18,7 +20,7 @@ pub struct SystemdEspPaths { } impl EspPaths<10> for SystemdEspPaths { - fn new(esp: impl AsRef) -> Self { + fn new(esp: impl AsRef, architecture: Architecture) -> Self { let esp = esp.as_ref(); let efi = esp.join("EFI"); let efi_nixos = efi.join("nixos"); @@ -34,9 +36,9 @@ impl EspPaths<10> for SystemdEspPaths { nixos: efi_nixos, linux: efi_linux, efi_fallback_dir: efi_efi_fallback_dir.clone(), - efi_fallback: efi_efi_fallback_dir.join("BOOTX64.EFI"), + efi_fallback: efi_efi_fallback_dir.join(architecture.efi_fallback_filename()), systemd: efi_systemd.clone(), - systemd_boot: efi_systemd.join("systemd-bootx64.efi"), + systemd_boot: efi_systemd.join(architecture.systemd_filename()), loader, systemd_boot_loader_config, } diff --git a/rust/tool/systemd/src/install.rs b/rust/tool/systemd/src/install.rs index 789cb3f..bdcb676 100644 --- a/rust/tool/systemd/src/install.rs +++ b/rust/tool/systemd/src/install.rs @@ -10,8 +10,10 @@ use anyhow::{anyhow, Context, Result}; use nix::unistd::syncfs; use tempfile::TempDir; +use crate::architecture::SystemdArchitectureExt; use crate::esp::SystemdEspPaths; use crate::version::SystemdVersion; +use lanzaboote_tool::architecture::Architecture; use lanzaboote_tool::esp::{EspGenerationPaths, EspPaths}; use lanzaboote_tool::gc::Roots; use lanzaboote_tool::generation::{Generation, GenerationLink}; @@ -30,11 +32,14 @@ pub struct Installer { configuration_limit: usize, esp_paths: SystemdEspPaths, generation_links: Vec, + arch: Architecture, } impl Installer { + #[allow(clippy::too_many_arguments)] pub fn new( lanzaboote_stub: PathBuf, + arch: Architecture, systemd: PathBuf, systemd_boot_loader_config: PathBuf, key_pair: KeyPair, @@ -43,7 +48,7 @@ impl Installer { generation_links: Vec, ) -> Self { let mut gc_roots = Roots::new(); - let esp_paths = SystemdEspPaths::new(esp); + let esp_paths = SystemdEspPaths::new(esp, arch); gc_roots.extend(esp_paths.iter()); Self { @@ -56,6 +61,7 @@ impl Installer { configuration_limit, esp_paths, generation_links, + arch, } } @@ -238,7 +244,7 @@ impl Installer { let bootspec = &generation.spec.bootspec.bootspec; - let esp_gen_paths = EspGenerationPaths::new(&self.esp_paths, generation)?; + let esp_gen_paths = EspGenerationPaths::new(&self.esp_paths, generation, self.arch)?; self.gc_roots.extend(esp_gen_paths.to_iter()); let initrd_content = fs::read( @@ -284,7 +290,7 @@ impl Installer { let bootspec = &generation.spec.bootspec.bootspec; - let esp_gen_paths = EspGenerationPaths::new(&self.esp_paths, generation)?; + let esp_gen_paths = EspGenerationPaths::new(&self.esp_paths, generation, self.arch)?; let kernel_cmdline = assemble_kernel_cmdline(&bootspec.init, bootspec.kernel_params.clone()); @@ -335,7 +341,8 @@ impl Installer { fn install_systemd_boot(&self) -> Result<()> { let systemd_boot = self .systemd - .join("lib/systemd/boot/efi/systemd-bootx64.efi"); + .join("lib/systemd/boot/efi") + .join(self.arch.systemd_filename()); let paths = [ (&systemd_boot, &self.esp_paths.efi_fallback), diff --git a/rust/tool/systemd/src/lib.rs b/rust/tool/systemd/src/lib.rs new file mode 100644 index 0000000..0f1102b --- /dev/null +++ b/rust/tool/systemd/src/lib.rs @@ -0,0 +1 @@ +pub mod architecture; diff --git a/rust/tool/systemd/src/main.rs b/rust/tool/systemd/src/main.rs index b453557..51633de 100644 --- a/rust/tool/systemd/src/main.rs +++ b/rust/tool/systemd/src/main.rs @@ -1,3 +1,4 @@ +mod architecture; mod cli; mod esp; mod install; diff --git a/rust/tool/systemd/tests/common/mod.rs b/rust/tool/systemd/tests/common/mod.rs index f0a97ec..ac17003 100644 --- a/rust/tool/systemd/tests/common/mod.rs +++ b/rust/tool/systemd/tests/common/mod.rs @@ -17,6 +17,23 @@ use rand::{thread_rng, Rng}; use serde_json::json; use sha2::{Digest, Sha256}; +use lanzaboote_tool::architecture::Architecture; +use lzbt_systemd::architecture::SystemdArchitectureExt; + +/// Returns the host platform system +/// in the system double format for +/// our usual targets. +#[cfg(target_arch = "aarch64")] +pub static SYSTEM: &str = "aarch64-linux"; + +// We do not actually care much about 32 bit. However we can use this to easily test that lzbt +// works with another architecture. +#[cfg(target_arch = "x86")] +pub static SYSTEM: &str = "i686-linux"; + +#[cfg(target_arch = "x86_64")] +pub static SYSTEM: &str = "x86_64-linux"; + /// Create a mock generation link. /// /// Works like `setup_generation_link_from_toplevel` but already sets up toplevel. @@ -55,7 +72,7 @@ pub fn setup_generation_link_from_toplevel( ], "label": "LanzaOS", "toplevel": toplevel, - "system": "x86_64-linux", + "system": SYSTEM, }, "org.nixos-community.lanzaboote": { "osRelease": toplevel.join("os-release") } }); @@ -78,13 +95,18 @@ pub fn setup_generation_link_from_toplevel( /// Accepts the temporary directory as a parameter so that the invoking function retains control of /// it (and when it goes out of scope). pub fn setup_toplevel(tmpdir: &Path) -> Result { + let system = Architecture::from_nixos_system(SYSTEM)?; // Generate a random toplevel name so that multiple toplevel paths can live alongside each // other in the same directory. let toplevel = tmpdir.join(format!("toplevel-{}", random_string(8))); fs::create_dir(&toplevel)?; let test_systemd = systemd_location_from_env()?; - let test_systemd_stub = format!("{test_systemd}/lib/systemd/boot/efi/linuxx64.efi.stub"); + let systemd_stub_filename = system.systemd_stub_filename(); + let test_systemd_stub = format!( + "{test_systemd}/lib/systemd/boot/efi/{systemd_stub_filename}", + systemd_stub_filename = systemd_stub_filename.display() + ); let initrd_path = toplevel.join("initrd"); let kernel_path = toplevel.join("kernel"); @@ -119,8 +141,13 @@ pub fn lanzaboote_install( ) -> Result { // To simplify the test setup, we use the systemd stub here instead of the lanzaboote stub. See // the comment in setup_toplevel for details. + let system = Architecture::from_nixos_system(SYSTEM)?; let test_systemd = systemd_location_from_env()?; - let test_systemd_stub = format!("{test_systemd}/lib/systemd/boot/efi/linuxx64.efi.stub"); + let systemd_stub_filename = system.systemd_stub_filename(); + let test_systemd_stub = format!( + "{test_systemd}/lib/systemd/boot/efi/{systemd_stub_filename}", + systemd_stub_filename = systemd_stub_filename.display() + ); let test_loader_config_path = tempfile::NamedTempFile::new()?; let test_loader_config = r"timeout 0\nconsole-mode 1\n"; @@ -131,6 +158,8 @@ pub fn lanzaboote_install( .env("LANZABOOTE_STUB", test_systemd_stub) .arg("-vv") .arg("install") + .arg("--system") + .arg(SYSTEM) .arg("--systemd") .arg(test_systemd) .arg("--systemd-boot-loader-config") diff --git a/rust/tool/systemd/tests/systemd_boot.rs b/rust/tool/systemd/tests/systemd_boot.rs index c6a07ed..d295f79 100644 --- a/rust/tool/systemd/tests/systemd_boot.rs +++ b/rust/tool/systemd/tests/systemd_boot.rs @@ -2,11 +2,13 @@ use std::fs; use std::path::PathBuf; use anyhow::Result; +use lanzaboote_tool::architecture::Architecture; +use lzbt_systemd::architecture::SystemdArchitectureExt; use tempfile::tempdir; mod common; -use common::{hash_file, mtime, remove_signature, verify_signature}; +use common::{hash_file, mtime, remove_signature, verify_signature, SYSTEM}; #[test] fn keep_systemd_boot_binaries() -> Result<()> { @@ -113,9 +115,15 @@ fn overwrite_unsigned_systemd_boot_binaries() -> Result<()> { } fn systemd_boot_path(esp: &tempfile::TempDir) -> PathBuf { - esp.path().join("EFI/systemd/systemd-bootx64.efi") + let arch = Architecture::from_nixos_system(SYSTEM).unwrap(); + esp.path() + .join("EFI/systemd/") + .join(arch.systemd_filename()) } fn systemd_boot_fallback_path(esp: &tempfile::TempDir) -> PathBuf { - esp.path().join("EFI/BOOT/BOOTX64.EFI") + let arch = Architecture::from_nixos_system(SYSTEM).unwrap(); + esp.path() + .join("EFI/BOOT/") + .join(arch.efi_fallback_filename()) } diff --git a/rust/uefi/rust-toolchain.toml b/rust/uefi/rust-toolchain.toml index 530928a..362afcd 100644 --- a/rust/uefi/rust-toolchain.toml +++ b/rust/uefi/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] channel = "1.70.0" components = [ "rust-src" ] -targets = [ "x86_64-unknown-uefi" ] +targets = [ "x86_64-unknown-uefi", "aarch64-unknown-uefi" ]