Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions crates/stage0/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Vec<String>>,
// Exactly one of these is read per build (see `for_this_arch`); the other
Expand Down
8 changes: 8 additions & 0 deletions crates/stage0/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CString16> {
let opts = opts?;
if opts.is_empty() {
Expand Down