Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for building UKI addons #3074

Merged
merged 2 commits into from
Sep 29, 2024
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
146 changes: 106 additions & 40 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
NekkoDroid marked this conversation as resolved.
Show resolved Hide resolved
# 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",
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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 (
Expand All @@ -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:
Expand Down Expand Up @@ -1875,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
Expand Down Expand Up @@ -3343,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)
Expand Down
11 changes: 11 additions & 0 deletions mkosi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit too late at this, but I think this should mention "PE" somehow, too, e.g. "Unified Kernel Image PE Addons", since the option is named --pe-addon, that is what I would grep for in the output of mkosi summary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woops, forgot to rename that. Probably should only be "PE Addons" since that is what they are referred to in the spec.

Initrds: {line_join_list(config.initrds)}
Initrd Packages: {line_join_list(config.initrd_packages)}
Initrd Volatile Packages: {line_join_list(config.initrd_volatile_packages)}
Expand Down
8 changes: 8 additions & 0 deletions mkosi/resources/man/mkosi.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ def test_config() -> None:
"abc"
],
"Passphrase": null,
"PeAddons": [
"/my-addon.conf"
],
"PostInstallationScripts": [
"/bar/qux"
],
Expand Down Expand Up @@ -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")],
Expand Down
Loading