From 3362381e5a1471393d44fe500042b6c5679c7817 Mon Sep 17 00:00:00 2001 From: HaRoLd <303926+HarryR@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:23:26 +0000 Subject: [PATCH] docs: clarify the _stage1 args / LoadOptions model Document the argument model the boot-path audit clarified; no code change (stage0 already behaves this way): - _stage1.args / signed args_url set the booted EFI program's UEFI LoadOptions, the generic contract for any EFI stage1. They come only from metadata / the signed URL; stage0 never forwards its own firmware/shell invocation args to stage1. - For a Linux UKI stage1, the kernel cmdline is baked into the signed, measured UKI and is authoritative; under Secure Boot the stub ignores LoadOptions, so _stage1.args cannot alter the UKI cmdline (a replace would also escape PCR 14). Operator config for a UKI flows through _stage2, not the kernel cmdline. Touches config.rs module + args field docs, set_load_options in main.rs, and the README _stage1 reference. All args/args_url/set_load_options/ fetch_signed_args code is unchanged. Co-Authored-By: Claude Opus 4.8 --- README.md | 10 ++++++++++ crates/stage0/src/config.rs | 12 ++++++++++++ crates/stage0/src/main.rs | 8 ++++++++ 3 files changed, 30 insertions(+) diff --git a/README.md b/README.md index 28321a2..9a89e6d 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,16 @@ arch entry needs `url` **and exactly one** of `sha256` or `ed25519`. `args_url` content is verified against `ed25519` (the same release key as the payload) and used verbatim, trimmed, as the load-options string. +**Args model.** `args` / `args_url` set the booted EFI program's UEFI **LoadOptions** — +the generic way stage0 parameterizes whatever EFI image it chain-loads. They come **only** +from this metadata (or the signed URL); stage0 never forwards its own firmware/shell +invocation arguments to stage1. For a **Linux UKI** stage1, the kernel command line is +baked into the signed, measured UKI and is authoritative: under Secure Boot the systemd +stub **ignores** LoadOptions, so `args` cannot alter the UKI cmdline (and a replace would +also escape PCR 14). Production runs Secure Boot on; configure a UKI-based stage1 through +its `_stage2` document, not the kernel cmdline. A non-UKI EFI stage1 may read these +LoadOptions as its arguments. + ### Embedded metadata (self-contained `netboot.efi`) The `_stage1` document can be embedded in stage0's PE before Authenticode diff --git a/crates/stage0/src/config.rs b/crates/stage0/src/config.rs index 608810c..dc2360a 100644 --- a/crates/stage0/src/config.rs +++ b/crates/stage0/src/config.rs @@ -5,6 +5,15 @@ //! Mirrors `stage1`'s per-arch `{url, sha256}` structure (plus optional `args`) //! but under a distinct `_stage1` key, so a UEFI payload is never confused with //! a Linux `_stage2` binary in the same document. +//! +//! `args` (and the signed `args_url`) set the booted EFI program's UEFI *LoadOptions* -- +//! the generic way stage0 parameterizes whatever EFI image it chain-loads. They are +//! sourced ONLY from this metadata (or the signed URL); stage0 never forwards its own +//! firmware/shell invocation arguments to stage1. For a Linux UKI stage1 specifically, +//! the kernel command line is baked into the signed, measured UKI and is authoritative: +//! under Secure Boot the stub ignores LoadOptions, so `_stage1.args` cannot alter the UKI +//! cmdline (operator config for a UKI flows through `_stage2`, not the kernel cmdline). +//! A non-UKI EFI stage1 is free to read these LoadOptions as its arguments. use alloc::string::String; use alloc::vec::Vec; @@ -41,6 +50,9 @@ pub struct UserData { #[derive(Debug, Deserialize)] pub struct Stage1Config { + /// Inline UEFI LoadOptions for the booted stage1 EFI image (a non-UKI stage1 reads + /// these as its args). Overridden by the signed `args_url`. See the module docs for + /// how a Linux UKI treats these (baked cmdline wins; ignored under Secure Boot). #[serde(default)] pub args: Option>, // Exactly one of these is read per build (see `for_this_arch`); the other diff --git a/crates/stage0/src/main.rs b/crates/stage0/src/main.rs index 2cd98c6..cc90b22 100644 --- a/crates/stage0/src/main.rs +++ b/crates/stage0/src/main.rs @@ -264,6 +264,14 @@ fn fetch_signed_args( /// Set the loaded image's load options from the final `opts` string (UCS-2). /// Returns the backing [`CString16`], which the caller must keep alive until /// `start_image`. +/// +/// `opts` is sourced only from the metadata `_stage1.args` or the signed `args_url` +/// (see `run`); stage0 never reads or forwards its own firmware/shell invocation +/// arguments to the child. For a Linux UKI child these LoadOptions would be the kernel +/// command line, but the UKI bakes + measures its own `.cmdline` and the stub ignores +/// LoadOptions under Secure Boot -- so on a UKI in production this has no effect, by +/// design (operator config for a UKI flows through `_stage2`). A non-UKI EFI stage1 +/// reads these as its arguments. fn set_load_options(image: Handle, opts: Option<&str>) -> Option { let opts = opts?; if opts.is_empty() {