Documentation
¶
Overview ¶
Package systemdboot is a pure-Go reader for systemd-boot configurations following the Boot Loader Specification: https://uapi-group.org/specifications/specs/boot_loader_specification/
The package solves kernel discovery for Linux installs that ship the kernel + initrd at content-addressed paths in /EFI/<distro>/<hash>-* (NixOS, Clear Linux, Fedora CoreOS w/ systemd-boot, custom Arch/Gentoo setups). The conventional /boot/{vmlinuz,Image}-* glob doesn't find them; this package reads /loader/loader.conf + /loader/entries/*.conf and returns the resolved linux/initrd paths.
The core BLS reader does no I/O of its own — callers pass an FS abstraction that any of go-filesystems' concrete readers satisfies via Go's structural typing.
On top of the reader, this package is a production consumer of the pure-Go storage / firmware / boot stack:
- OpenImage (image.go) GPT-locates the EFI System Partition with go-volumes/gpt, mounts its FAT32 filesystem with go-filesystems/detect, adapts it to FS, and returns resolved BLS entries.
- ReadBootState and the BootNext/BootOrder helpers (uefi.go) read and manage the UEFI boot variables via go-filesystems/uefi.
- MeasureBoot (measure.go) measures the selected kernel/initrd/cmdline into the conventional TPM PCRs via go-tpm2, behind an injected Extender so the package stays TPM-free when no TPM is present.
Index ¶
- Constants
- Variables
- func MakeSystemdBootFirst(store uefi.VariableStore) (uint16, error)
- func SetBootNext(store uefi.VariableStore, n uint16) error
- func SetBootOrder(store uefi.VariableStore, order []uint16) error
- type Boot
- type BootEntry
- type BootState
- type Extender
- type FS
- type Image
- type LoaderConf
- type MeasureResult
- type Measurement
Constants ¶
const ( // PCRKernel is the PCR into which the kernel image is measured (the // boot-loader / loaded-image PCR). PCRKernel = 4 // PCRCmdline is the PCR into which the kernel command line is measured. PCRCmdline = 8 // PCRInitrd is the PCR into which the initrd is measured. PCRInitrd = 9 // PCRUKI is the PCR into which unified-kernel-image (sd-stub) sections // are measured. PCRUKI = 11 )
Conventional PCR indices used by the systemd measured-boot chain. They are literals here because the go-tpm2 libraries deliberately do not encode the systemd registry — the mapping is policy, supplied by the consumer.
Variables ¶
var ErrNotFound = fmt.Errorf("systemd-boot: not found")
ErrNotFound is returned when the requested configuration file doesn't exist on the FS.
Functions ¶
func MakeSystemdBootFirst ¶
func MakeSystemdBootFirst(store uefi.VariableStore) (uint16, error)
MakeSystemdBootFirst rewrites BootOrder so the first Boot#### that points at systemd-boot is evaluated first, preserving the relative order of the rest. Returns the chosen systemd-boot slot. Returns an error if no Boot#### points at systemd-boot.
func SetBootNext ¶
func SetBootNext(store uefi.VariableStore, n uint16) error
SetBootNext sets the one-shot UEFI BootNext variable to n (the next boot, and only the next boot, will use Boot####=n). Thin pass-through to go-filesystems/uefi so callers don't import it directly for the common case.
func SetBootOrder ¶
func SetBootOrder(store uefi.VariableStore, order []uint16) error
SetBootOrder replaces the UEFI BootOrder with the given sequence.
Types ¶
type Boot ¶
type Boot struct {
// EntryID is the filename of the .conf entry without the
// ".conf" suffix, e.g. "nixos-generation-42".
EntryID string
// Title is the human-readable name for the entry, e.g. "NixOS".
Title string
// Version sorts entries within the same sort-key group, e.g.
// "Generation 42 NixOS 23.11.20240115.abc".
Version string
// SortKey groups entries across distros (systemd-boot sorts
// entries with the same sort-key together, then by version).
// Empty if not set in the entry file.
SortKey string
// LinuxPath is the linux= field — the kernel binary's path
// relative to the ESP root, e.g. "/EFI/nixos/<hash>-linux.efi".
LinuxPath string
// InitrdPath is the initrd= field, similar.
InitrdPath string
// Options is the kernel command line, concatenated from any
// options= lines in the entry file (BLS allows multiple).
Options string
// Raw retains the unparsed key/value pairs the parser saw — useful
// for less-common keys (machine-id, devicetree, efi, architecture).
Raw map[string]string
}
Boot is a resolved boot entry — the BLS .conf file's content with the most useful fields lifted out.
type BootEntry ¶
type BootEntry struct {
// Number is the Boot#### index (e.g. 0x0001 for Boot0001).
Number uint16
// Description is the human-readable label (UEFI "Description" field).
Description string
// DevicePathText is the device path rendered to the standard UEFI text
// representation, e.g.
// "HD(1,GPT,<guid>,…)/File(\EFI\systemd\systemd-bootx64.efi)".
DevicePathText string
// Active reflects the LOAD_OPTION_ACTIVE attribute bit.
Active bool
// InBootOrder is true if this entry's number appears in BootOrder.
InBootOrder bool
// IsSystemdBoot is true if the device path's File() segment matches one
// of systemd-boot's canonical EFI paths.
IsSystemdBoot bool
}
BootEntry is a UEFI Boot#### LoadOption lifted into a friendly shape, with the device path already rendered to text and the systemd-boot match pre-computed.
type BootState ¶
type BootState struct {
Order []uint16
Entries map[uint16]*BootEntry
BootNext uint16
BootNextSet bool
SystemdBootSlots []uint16
}
BootState is the firmware boot-variable picture: the ordered BootOrder, the resolved Boot#### entries (keyed by number), BootNext if set, and the numbers (in BootOrder sequence) that point at systemd-boot.
func ReadBootState ¶
func ReadBootState(store uefi.VariableStore) (*BootState, error)
ReadBootState reads the full UEFI boot-variable state from store (a varstore opened/minted via go-filesystems/uefi). It lists every Boot#### LoadOption, renders device paths to text, cross-references BootOrder and BootNext, and flags entries that point at systemd-boot.
type Extender ¶
Extender is the minimal TPM surface the measured-boot helper needs: extend a digest into a PCR bank. *github.com/go-tpm2/tpm2.TPM satisfies it (PCRExtend(pcr int, hash uint16, digest []byte) error), so a caller passes a real TPM to measure into hardware, or a fake for dry-runs / tests. Keeping the dependency behind this interface is what lets the package build and run with no TPM present.
type FS ¶
FS is the minimal filesystem surface the parser needs. Any type matching this signature (in particular the concrete drivers from go-filesystems) satisfies it without explicit declaration.
func NewFS ¶
func NewFS(fs filesystem.Filesystem) FS
NewFS adapts a go-filesystems driver (anything implementing filesystem.Filesystem — fat32, ext4, …) to the local FS interface so it can be passed to Default / All / LoadLoaderConf. Callers who already hold a mounted ESP filesystem use this instead of OpenImage.
type Image ¶
type Image struct {
// FS is the ESP's FAT32 filesystem adapted to the local FS interface.
// Pass it (with EspRoot) to Default / All for resolved entries.
FS FS
// EspRoot is the path within FS at which the loader/ directory lives.
// For a freshly mounted ESP this is "/" (the ESP root is the FS root).
EspRoot string
// Partition is the GPT EFI System Partition entry that was located.
Partition gpt.Partition
// LoaderConf is the parsed /loader/loader.conf (nil if absent).
LoaderConf *LoaderConf
// contains filtered or unexported fields
}
Image is a mounted disk image: an ESP filesystem adapted to the FS interface, the disk geometry that located it, and the parsed loader.conf. Close releases the underlying filesystem and any staged temp file.
func OpenImage ¶
OpenImage opens a whole disk image at path, locates its EFI System Partition via GPT, mounts the partition's FAT32 filesystem, and returns an Image whose FS yields resolved BLS boot entries. The caller MUST Close the returned Image.
loader.conf is parsed eagerly and exposed as Image.LoaderConf; its absence is not an error (an ESP may carry entries without a loader.conf), in which case LoaderConf is nil.
func (*Image) Default ¶
Default returns the active BLS entry (loader.conf's default, or the BLS-highest entry when no default is set).
func (*Image) ReadESPFile ¶
ReadESPFile reads a file from the mounted ESP, resolving an ESP-relative path (as found in a BLS linux=/initrd= field) against the ESP root. The returned slice is bounded by limit so an untrusted/corrupt FAT directory entry cannot make a caller process an unexpectedly huge file.
limit caps the returned bytes; pass a value comfortably above the largest kernel/initrd you expect (e.g. 512 MiB). A file larger than limit is an error rather than a silent truncation. A non-positive limit reads with no cap (use only for trusted images).
type LoaderConf ¶
type LoaderConf struct {
Default string // possibly with a "*" suffix wildcard
Timeout string // string form, e.g. "5" or "menu-force"
Raw map[string]string
}
LoaderConf is the parsed /loader/loader.conf — currently only `default` and `timeout` are surfaced (the rest are UI knobs we don't care about as a non-interactive reader).
func LoadLoaderConf ¶
func LoadLoaderConf(fs FS, espRoot string) (*LoaderConf, error)
LoadLoaderConf reads <espRoot>/loader/loader.conf and parses it. Returns ErrNotFound if the file is absent.
type MeasureResult ¶
type MeasureResult struct {
Measurements []Measurement
EventLog []byte
}
MeasureResult is the outcome of measuring a Boot entry: the ordered list of measurements performed and the serialised TCG event log describing them.
func MeasureBoot ¶
MeasureBoot measures the selected Boot entry into the conventional PCRs via ext and returns the measurements plus a TCG event log. The kernel and initrd are read from the mounted ESP (im); the command line is taken from the entry's Options. fileLimit bounds each file read (see Image.ReadESPFile).
ext may be nil for a pure event-log dry run (digests are still computed and logged, but nothing is extended into a TPM) — this is the TPM-free path. To measure into hardware, pass a *tpm2.TPM built over a crb/tis transport.
The digest of each measured object is SHA-256 over its bytes, which is the digest the event log records and what a SHA-256 PCR bank is extended with.
