WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Open
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
18 changes: 16 additions & 2 deletions nix/modules/lanzaboote.nix
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ in
'';
};

bootCounting = {
initialTries = lib.mkOption {
type = lib.types.ints.u32;
default = 0;
description = ''
The number of boot counting tries to set for new boot entries.
Setting this to zero, disables boot counting.
See https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/
'';
};
};

installCommand = lib.mkOption {
type = lib.types.str;
readOnly = true;
Expand All @@ -157,14 +169,16 @@ in
--systemd ${config.systemd.package} \
--systemd-boot-loader-config ${loaderConfigFile} \
--configuration-limit ${toString configurationLimit} \
--allow-unsigned ${lib.boolToString cfg.allowUnsigned}'';
--allow-unsigned ${lib.boolToString cfg.allowUnsigned} \
--bootcounting-initial-tries ${toString cfg.bootCounting.initialTries}'';
defaultText = lib.literalExpression ''
''${lib.getExe cfg.package} install \
--system ''${config.boot.kernelPackages.stdenv.hostPlatform.system} \
--systemd ''${config.systemd.package} \
--systemd-boot-loader-config ''${loaderConfigFile} \
--configuration-limit ''${toString configurationLimit} \
--allow-unsigned ''${lib.boolToString cfg.allowUnsigned}'';
--allow-unsigned ''${lib.boolToString cfg.allowUnsigned} \
--bootcounting-initial-tries ''${toString cfg.bootCounting.initialTries}'';
};

extraEfiSysMountPoints = lib.mkOption {
Expand Down
1 change: 1 addition & 0 deletions nix/tests/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ in
export-efivars-tpm = runTest ./lanzaboote/export-efivars-tpm.nix;
extra-efi-partitions = runTest ./lanzaboote/extra-efi-partitions.nix;
auto-generate-enroll = runTest ./lanzaboote/auto-generate-enroll.nix;
boot-counting = runTest ./lanzaboote/boot-counting.nix;

systemd-pcrlock = runTest ./lanzaboote/systemd-pcrlock.nix;
systemd-measured-uki = runTest ./lanzaboote/systemd-measured-uki.nix;
Expand Down
130 changes: 130 additions & 0 deletions nix/tests/lanzaboote/boot-counting.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
let
sortKey = "mySpecialSortKey";
sortKeySpectator = "spectatorSortKey";
sortKeyBad = "allthewayontop";
in
{
name = "lanzaboote boot counting";

nodes = {
machine = {
imports = [ ./common/lanzaboote.nix ];

boot.lanzaboote = {
inherit sortKey;
bootCounting.initialTries = 2;
};

# Boot is successful if multi-user is reached
systemd.targets.boot-complete.after = [ "multi-user.target" ];

specialisation = {
# This specialisation is ordered first, but it will fail to boot successfully
bad.configuration =
{ lib, ... }:
{
boot.lanzaboote.sortKey = lib.mkForce sortKeyBad;

systemd.services."failing" = {
script = "exit 1";
requiredBy = [ "boot-complete.target" ];
before = [ "boot-complete.target" ];
serviceConfig.Type = "oneshot";
};
};

# This specialisation should be ordered at the bottom, and we should never boot it
spectator.configuration =
{ lib, ... }:
{
boot.lanzaboote.sortKey = lib.mkForce sortKeySpectator;
};
# Specialisation with boot counting turned off, this should not matter
spectator2.configuration =
{ lib, ... }:
{
boot.lanzaboote = {
sortKey = lib.mkForce sortKeySpectator;
bootCounting.initialTries = lib.mkForce 0;
};
};
};
};
};

testScript =
{ nodes, ... }:
let
orig = nodes.machine.system.build.toplevel;
bad = nodes.machine.specialisation.bad.configuration.system.build.toplevel;
in
(import ./common/image-helper.nix { inherit (nodes) machine; })
+
# python
''
orig = "${orig}"
bad = "${bad}"

def check_current_system(system_path:str):
current_sys = machine.succeed('readlink -f /run/current-system').strip()
print(f'current system: {current_sys}')
machine.succeed(f'test "{current_sys}" = "{system_path}"')

def check_boot_entry(
generation_counter:int,
specialisation:str|None,
boot_counter:int=0,
bad_counter:int=0
):
regex = rf"^/boot/EFI/Linux/nixos-generation-{generation_counter}"

if specialisation:
regex += f"-specialisation-{specialisation}"

regex += r"-[0-9a-z]{52}"

if boot_counter != 0 or bad_counter != 0:
regex += rf"\+{boot_counter}"
if bad_counter != 0:
regex += rf"-{bad_counter}"

regex += r"\.efi$"

find_command = rf"find /boot/EFI/Linux/ -regextype posix-extended -regex '{regex}' -type f | grep -q '.'"

machine.succeed(find_command)

machine.start()
machine.wait_for_unit("multi-user.target")
# Ensure we booted using an entry with counters enabled
machine.succeed(
"test -e /sys/firmware/efi/efivars/LoaderBootCountPath-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
)
print(machine.succeed("bootctl list"))
check_current_system(bad)
check_boot_entry(generation_counter=1, specialisation=None, boot_counter=2)
check_boot_entry(generation_counter=1, specialisation="spectator", boot_counter=2)
check_boot_entry(generation_counter=1, specialisation="bad", boot_counter=1, bad_counter=1)
machine.shutdown()

machine.start()
machine.wait_for_unit("multi-user.target")
print(machine.succeed("bootctl list"))
check_current_system(bad)
check_boot_entry(generation_counter=1, specialisation=None, boot_counter=2)
check_boot_entry(generation_counter=1, specialisation="spectator", boot_counter=2)
check_boot_entry(generation_counter=1, specialisation="bad", boot_counter=0, bad_counter=2)
machine.shutdown()

# Should boot back into original configuration
machine.start()
check_current_system(orig)
machine.wait_for_unit("multi-user.target")
machine.wait_for_unit("systemd-bless-boot.service")
print(machine.succeed("bootctl list"))
check_boot_entry(generation_counter=1, specialisation=None)
check_boot_entry(generation_counter=1, specialisation="spectator", boot_counter=2)
check_boot_entry(generation_counter=1, specialisation="bad", boot_counter=0, bad_counter=2)
machine.shutdown()
'';
}
33 changes: 33 additions & 0 deletions rust/tool/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/tool/systemd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ serde_json = "1.0.145"
sha2 = "0.10.9"
tempfile = "3.23.0"
nix = { version = "0.30.1", default-features = false, features = [ "fs" ] }
regex = "1.12.2"

[dev-dependencies]
assert_cmd = "2.1.1"
Expand Down
5 changes: 5 additions & 0 deletions rust/tool/systemd/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ struct InstallCommand {
#[arg(long, default_value_t = 1)]
configuration_limit: usize,

/// Initial number of boot counting tries, set to zero to disable boot counting
#[arg(long, default_value_t = 0)]
bootcounting_initial_tries: u32,

/// EFI system partition mountpoint (e.g. efiSysMountPoint)
esp: PathBuf,

Expand Down Expand Up @@ -106,6 +110,7 @@ fn install(args: InstallCommand) -> Result<()> {
args.systemd,
args.systemd_boot_loader_config,
args.configuration_limit,
args.bootcounting_initial_tries,
args.esp,
args.generations,
);
Expand Down
Loading