From 7c2ac910b2767e8d81b6081a4aea1da2da872f88 Mon Sep 17 00:00:00 2001 From: Michael Ferrari Date: Sat, 28 Sep 2024 22:12:42 +0200 Subject: [PATCH 1/2] Extract `ukify` call to separate function --- mkosi/__init__.py | 106 +++++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 40 deletions(-) diff --git a/mkosi/__init__.py b/mkosi/__init__.py index ba5d31d2e..edcde2320 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -1442,50 +1442,37 @@ def want_signed_pcrs(config: Config) -> bool: ) -def build_uki( +def run_ukify( context: Context, + arch: str, stub: Path, - kver: str, - kimg: Path, - microcodes: list[Path], - initrds: list[Path], - cmdline: Sequence[str], output: Path, + arguments: list[PathString], + options: list[PathString], ) -> None: - # Older versions of systemd-stub expect the cmdline section to be null terminated. We can't - # embed NUL terminators in argv so let's communicate the cmdline via a file instead. - (context.workspace / "cmdline").write_text(f"{' '.join(cmdline).strip()}\x00") - - if not (arch := context.config.architecture.to_efi()): - die(f"Architecture {context.config.architecture} does not support UEFI") - - if not (ukify := context.config.find_binary("ukify", "/usr/lib/systemd/ukify")): + ukify = context.config.find_binary("ukify", "/usr/lib/systemd/ukify") + if not ukify: die("Could not find ukify") - cmd: list[PathString] = [ + cmd = [ python_binary(context.config, binary=ukify), ukify, - *(["--cmdline", f"@{context.workspace / 'cmdline'}"] if cmdline else []), - "--os-release", f"@{context.root / 'usr/lib/os-release'}", + "build", + *arguments, + "--efi-arch", arch, "--stub", stub, "--output", workdir(output), - "--efi-arch", arch, - "--uname", kver, ] # fmt: skip - options: list[PathString] = [ - "--bind", output.parent, workdir(output.parent), - "--ro-bind", context.workspace / "cmdline", context.workspace / "cmdline", - "--ro-bind", context.root / "usr/lib/os-release", context.root / "usr/lib/os-release", + options += [ "--ro-bind", stub, stub, + "--bind", output.parent, workdir(output.parent), ] # fmt: skip if context.config.secure_boot: assert context.config.secure_boot_key assert context.config.secure_boot_certificate - cmd += ["--sign-kernel"] - if context.config.secure_boot_sign_tool != SecureBootSignTool.pesign: cmd += [ "--signtool", "sbsign", @@ -1511,8 +1498,57 @@ def build_uki( ] # fmt: skip options += ["--ro-bind", context.workspace / "pesign", context.workspace / "pesign"] + run( + cmd, + sandbox=context.sandbox( + binary=ukify, + options=options, + devices=context.config.secure_boot_key_source.type != KeySourceType.file, + ), + ) + + +def build_uki( + context: Context, + stub: Path, + kver: str, + kimg: Path, + microcodes: list[Path], + initrds: list[Path], + cmdline: Sequence[str], + output: Path, +) -> None: + # Older versions of systemd-stub expect the cmdline section to be null terminated. We can't + # embed NUL terminators in argv so let's communicate the cmdline via a file instead. + (context.workspace / "cmdline").write_text(f"{' '.join(cmdline).strip()}\x00") + + if not (arch := context.config.architecture.to_efi()): + die(f"Architecture {context.config.architecture} does not support UEFI") + + if not (ukify := context.config.find_binary("ukify", "/usr/lib/systemd/ukify")): + die("Could not find ukify") + + arguments: list[PathString] = [ + *(["--cmdline", f"@{context.workspace / 'cmdline'}"] if cmdline else []), + "--os-release", f"@{context.root / 'usr/lib/os-release'}", + "--uname", kver, + "--linux", kimg, + ] # fmt: skip + + options: list[PathString] = [ + "--ro-bind", context.workspace / "cmdline", context.workspace / "cmdline", + "--ro-bind", context.root / "usr/lib/os-release", context.root / "usr/lib/os-release", + "--ro-bind", kimg, kimg, + ] # fmt: skip + + if context.config.secure_boot: + assert context.config.secure_boot_key + assert context.config.secure_boot_certificate + + arguments += ["--sign-kernel"] + if want_signed_pcrs(context.config): - cmd += [ + arguments += [ "--pcr-private-key", context.config.secure_boot_key, # SHA1 might be disabled in OpenSSL depending on the distro so we opt to not sign # for SHA1 to avoid having to manage a bunch of configuration to re-enable SHA1. @@ -1521,7 +1557,7 @@ def build_uki( if context.config.secure_boot_key.exists(): options += ["--bind", context.config.secure_boot_key, context.config.secure_boot_key] if context.config.secure_boot_key_source.type == KeySourceType.engine: - cmd += [ + arguments += [ "--signing-engine", context.config.secure_boot_key_source.source, "--pcr-public-key", context.config.secure_boot_certificate, ] # fmt: skip @@ -1530,9 +1566,6 @@ def build_uki( "--bind-try", "/run/pcscd", "/run/pcscd", ] # fmt: skip - cmd += ["build", "--linux", kimg] - options += ["--ro-bind", kimg, kimg] - if microcodes: # new .ucode section support? if ( @@ -1546,24 +1579,17 @@ def build_uki( and version >= "256" ): for microcode in microcodes: - cmd += ["--microcode", microcode] + arguments += ["--microcode", microcode] options += ["--ro-bind", microcode, microcode] else: initrds = microcodes + initrds for initrd in initrds: - cmd += ["--initrd", initrd] + arguments += ["--initrd", initrd] options += ["--ro-bind", initrd, initrd] with complete_step(f"Generating unified kernel image for kernel version {kver}"): - run( - cmd, - sandbox=context.sandbox( - binary=ukify, - options=options, - devices=context.config.secure_boot_key_source.type != KeySourceType.file, - ), - ) + run_ukify(context, arch, stub, output, arguments, options) def systemd_stub_binary(context: Context) -> Path: From 807f4294636ce2fa7a57064eeecd8c0a4aa9ecb1 Mon Sep 17 00:00:00 2001 From: Michael Ferrari Date: Sun, 29 Sep 2024 00:35:47 +0200 Subject: [PATCH 2/2] Add support for building PE addons --- mkosi/__init__.py | 40 ++++++++++++++++++++++++++++++++++++ mkosi/config.py | 11 ++++++++++ mkosi/resources/man/mkosi.md | 8 ++++++++ tests/test_json.py | 4 ++++ 4 files changed, 63 insertions(+) diff --git a/mkosi/__init__.py b/mkosi/__init__.py index edcde2320..fb87670f5 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -1901,6 +1901,45 @@ def install_uki( f.write("fi\n") +def build_pe_addon(context: Context, arch: str, stub: Path, config: Path, output: Path) -> None: + arguments: list[PathString] = [ + "--config", config, + ] # fmt: skip + + options: list[PathString] = [ + "--ro-bind", config, config, + ] # fmt: skip + + with complete_step(f"Generating PE addon /{output.relative_to(context.root)}"): + run_ukify(context, arch, stub, output, arguments, options) + + +def install_pe_addons(context: Context) -> None: + if not context.config.pe_addons: + return + + if not (arch := context.config.architecture.to_efi()): + die(f"Architecture {context.config.architecture} does not support UEFI") + + stub = systemd_addon_stub_binary(context) + if not stub.exists(): + die(f"sd-stub not found at /{stub.relative_to(context.root)} in the image") + + addon_dir = context.root / "boot/loader/addons" + with umask(~0o755): + addon_dir.mkdir(parents=True, exist_ok=True) + + for addon in context.config.pe_addons: + output = addon_dir / addon.with_suffix(".addon.efi").name + build_pe_addon(context, arch, stub, config=addon, output=output) + + +def systemd_addon_stub_binary(context: Context) -> Path: + arch = context.config.architecture.to_efi() + stub = context.root / f"usr/lib/systemd/boot/efi/addon{arch}.efi.stub" + return stub + + def install_kernel(context: Context, partitions: Sequence[Partition]) -> None: # Iterates through all kernel versions included in the image and generates a combined # kernel+initrd+cmdline+osrelease EFI file from it and places it in the /EFI/Linux directory of @@ -3369,6 +3408,7 @@ def build_image(context: Context) -> None: install_systemd_boot(context) install_grub(context) install_shim(context) + install_pe_addons(context) run_sysusers(context) run_tmpfiles(context) run_preset(context) diff --git a/mkosi/config.py b/mkosi/config.py index 4ab7824a0..1822d44b4 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -1588,6 +1588,7 @@ class Config: shim_bootloader: ShimBootloader unified_kernel_images: ConfigFeature unified_kernel_image_format: str + pe_addons: list[Path] initrds: list[Path] initrd_packages: list[str] initrd_volatile_packages: list[str] @@ -2552,6 +2553,15 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple # default= help="Specify the format used for the UKI filename", ), + ConfigSetting( + dest="pe_addons", + long="--pe-addon", + metavar="PATH", + section="Content", + parse=config_make_list_parser(delimiter=",", parse=make_path_parser()), + recursive_paths=("mkosi.pe-addons/",), + help="Configuration files to generate PE addons", + ), ConfigSetting( dest="initrds", long="--initrd", @@ -4479,6 +4489,7 @@ def summary(config: Config) -> str: Shim Bootloader: {config.shim_bootloader} Unified Kernel Images: {config.unified_kernel_images} Unified Kernel Image Format: {config.unified_kernel_image_format} + Unified Kernel Image Addons: {line_join_list(config.pe_addons)} Initrds: {line_join_list(config.initrds)} Initrd Packages: {line_join_list(config.initrd_packages)} Initrd Volatile Packages: {line_join_list(config.initrd_volatile_packages)} diff --git a/mkosi/resources/man/mkosi.md b/mkosi/resources/man/mkosi.md index 8c8531061..25945d50e 100644 --- a/mkosi/resources/man/mkosi.md +++ b/mkosi/resources/man/mkosi.md @@ -984,6 +984,14 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, | `&h` | `roothash=` or `usrhash=` value of kernel argument | | `&c` | Number of tries used for boot attempt counting | +`PeAddons=`, `--pe-addon` +: Build additional PE addons. Takes a comma separated list of paths to + `ukify` config files. This option may be used multiple times in which case + each config gets built into a corresponding addon. Each addon has the name + of the config file, with the extension replaced with `.addon.efi`. + Config files in the `mkosi.pe-addons/` directory are automatically picked + up. + `Initrds=`, `--initrd` : Use user-provided initrd(s). Takes a comma separated list of paths to initrd files. This option may be used multiple times in which case the initrd lists diff --git a/tests/test_json.py b/tests/test_json.py index 8b56e7334..c27e1102f 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -207,6 +207,9 @@ def test_config() -> None: "abc" ], "Passphrase": null, + "PeAddons": [ + "/my-addon.conf" + ], "PostInstallationScripts": [ "/bar/qux" ], @@ -442,6 +445,7 @@ def test_config() -> None: packages=[], pass_environment=["abc"], passphrase=None, + pe_addons=[Path("/my-addon.conf")], postinst_scripts=[Path("/bar/qux")], postoutput_scripts=[Path("/foo/src")], prepare_scripts=[Path("/run/foo")],