Skip to content

Commit

Permalink
Only use already signed binaries when ShimBootloader=signed
Browse files Browse the repository at this point in the history
When we're using signed shim, we need to make sure we use already
signed bootloaders, kernel images and UKIs. Anything we sign ourselves
will cause security violations in shim.
  • Loading branch information
DaanDeMeyer committed Mar 12, 2024
1 parent 181dac2 commit 7d036b8
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 42 deletions.
2 changes: 1 addition & 1 deletion mkosi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

[Content]
Autologin=yes
@ShimBootloader=signed
@SELinuxRelabel=no
@ShimBootloader=unsigned
BuildSources=.
BuildSourcesEphemeral=yes

Expand Down
1 change: 0 additions & 1 deletion mkosi.conf.d/20-arch.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
Distribution=arch

[Content]
ShimBootloader=unsigned
Packages=
apt
archlinux-keyring
Expand Down
3 changes: 3 additions & 0 deletions mkosi.conf.d/20-opensuse.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Distribution=opensuse
@Release=tumbleweed

[Content]
# OpenSUSE does not ship an unsigned shim
@ShimBootloader=none
Packages=
bash
btrfs-progs
Expand All @@ -21,6 +23,7 @@ Packages=
e2fsprogs
erofs-utils
grep
grub2-efi
grub2-i386-pc
grub2-x86_64-efi
iproute
Expand Down
9 changes: 6 additions & 3 deletions mkosi.conf.d/30-centos-fedora/mkosi.conf.d/20-uefi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ Architecture=|arm64

[Content]
Packages=
systemd-boot
pesign
edk2-ovmf
shim
grub2-efi
grub2-efi-x64-modules
kernel-uki-virt
pesign
shim
shim-unsigned-x64
systemd-boot
3 changes: 2 additions & 1 deletion mkosi.conf.d/30-debian-ubuntu/mkosi.conf.d/20-x86-64.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Architecture=x86-64
[Content]
Packages=
amd64-microcode
grub-pc-bin
grub-efi
grub-efi-amd64
grub-pc-bin
intel-microcode
154 changes: 118 additions & 36 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,13 +914,14 @@ def install_systemd_boot(context: Context) -> None:
return

directory = context.root / "usr/lib/systemd/boot/efi"
if not directory.exists() or not any(directory.iterdir()):
signed = context.config.shim_bootloader == ShimBootloader.signed
if not directory.glob("*.efi.signed" if signed else "*.efi"):
if context.config.bootable == ConfigFeature.enabled:
die("A EFI bootable image with systemd-boot was requested but systemd-boot was not found at "
f"{directory.relative_to(context.root)}")
die(f"An EFI bootable image with systemd-boot was requested but a {'signed ' if signed else ''}"
f"systemd-boot binary was not found at {directory.relative_to(context.root)}")
return

if context.config.secure_boot:
if context.config.secure_boot and not signed:
with complete_step("Signing systemd-boot binaries…"):
for input in itertools.chain(directory.glob('*.efi'), directory.glob('*.EFI')):
output = directory / f"{input}.signed"
Expand Down Expand Up @@ -1129,9 +1130,10 @@ def want_grub_efi(context: Context) -> bool:
if context.config.bootloader != Bootloader.grub:
return False

have = find_grub_directory(context, target="x86_64-efi") is not None
if not have and context.config.bootable == ConfigFeature.enabled:
die("An EFI bootable image with grub was requested but grub for EFI is not installed")
if context.config.shim_bootloader != ShimBootloader.signed:
have = find_grub_directory(context, target="x86_64-efi") is not None
if not have and context.config.bootable == ConfigFeature.enabled:
die("An EFI bootable image with grub was requested but grub for EFI is not installed")

return True

Expand Down Expand Up @@ -1191,9 +1193,13 @@ def prepare_grub_config(context: Context) -> Optional[Path]:
f.write("set timeout=0\n")

if want_grub_efi(context):
# Signed EFI grub shipped by distributions reads its configuration from /EFI/<distribution>/grub.cfg in
# the ESP so let's put a shim there to redirect to the actual configuration file.
earlyconfig = context.root / "efi/EFI" / context.config.distribution.name / "grub.cfg"
# Signed EFI grub shipped by distributions reads its configuration from /EFI/<distribution>/grub.cfg (except
# in OpenSUSE) in the ESP so let's put a shim there to redirect to the actual configuration file.
if context.config.distribution == Distribution.opensuse:
earlyconfig = context.root / "efi/EFI/BOOT/grub.cfg"
else:
earlyconfig = context.root / "efi/EFI" / context.config.distribution.name / "grub.cfg"

with umask(~0o700):
earlyconfig.parent.mkdir(parents=True, exist_ok=True)

Expand All @@ -1203,7 +1209,14 @@ def prepare_grub_config(context: Context) -> Optional[Path]:
return config


def grub_mkimage(context: Context, *, target: str, modules: Sequence[str] = (), output: Optional[Path] = None) -> None:
def grub_mkimage(
context: Context,
*,
target: str,
modules: Sequence[str] = (),
output: Optional[Path] = None,
sbat: Optional[Path] = None,
) -> None:
mkimage = find_grub_binary("mkimage", root=context.config.tools())
assert mkimage

Expand Down Expand Up @@ -1233,24 +1246,61 @@ def grub_mkimage(context: Context, *, target: str, modules: Sequence[str] = (),
"--prefix", f"/{context.config.distribution.grub_prefix()}",
"--output", output or (directory / "core.img"),
"--format", target,
*(["--sbat", str(sbat)] if sbat else []),
*(["--disable-shim-lock"] if context.config.shim_bootloader == ShimBootloader.none else []),
*(["--verbose"] if ARG_DEBUG.get() else []),
"cat",
"cmp",
"div",
"echo",
"fat",
"hello",
"help",
"keylayouts",
"linux",
"loadenv",
"ls",
"normal",
"part_gpt",
"search",
"read",
"reboot",
"search_fs_file",
"normal",
"linux",
"search",
"sleep",
"test",
"tr",
"true",
*modules,
],
sandbox=context.sandbox(
options=[
"--bind", context.root, context.root,
"--ro-bind", earlyconfig.name, earlyconfig.name,
*(["--ro-bind", str(sbat), str(sbat)] if sbat else []),
],
),
)


def find_signed_grub_image(context: Context) -> Optional[Path]:
arch = context.config.architecture.to_efi()

patterns = [
f"usr/lib/grub/*-signed/grub{arch}.efi.signed", # Debian/Ubuntu
f"boot/efi/EFI/*/grub{arch}.efi", # Fedora/CentOS
"usr/share/efi/*/grub.efi", # OpenSUSE
]

for p in flatten(context.root.glob(pattern) for pattern in patterns):
if p.is_symlink() and p.readlink().is_absolute():
logging.warning(f"Ignoring signed grub EFI binary which is an absolute path to {p.readlink()}")
continue

return p

return None


def install_grub(context: Context) -> None:
if not want_grub_bios(context) and not want_grub_efi(context):
return
Expand All @@ -1267,9 +1317,28 @@ def install_grub(context: Context) -> None:
with umask(~0o700):
output.parent.mkdir(parents=True, exist_ok=True)

grub_mkimage(context, target="x86_64-efi", output=output, modules=("chain",))
if context.config.secure_boot:
sign_efi_binary(context, output, output)
if context.config.shim_bootloader == ShimBootloader.signed:
if not (signed := find_signed_grub_image(context)):
if context.config.bootable == ConfigFeature.enabled:
die("Couldn't find a signed grub EFI binary installed in the image")

return

rel = output.relative_to(context.root)
log_step(f"Installing signed grub EFI binary from /{signed.relative_to(context.root)} to /{rel}")
shutil.copy2(signed, output)
else:
if context.config.secure_boot and context.config.shim_bootloader != ShimBootloader.none:
if not (signed := find_signed_grub_image(context)):
die("Couldn't find a signed grub EFI binary installed in the image to extract SBAT from")

sbat = extract_pe_section(context, signed, ".sbat", context.workspace / "sbat")
else:
sbat = None

grub_mkimage(context, target="x86_64-efi", output=output, modules=("chain",), sbat=sbat)
if context.config.secure_boot:
sign_efi_binary(context, output, output)

dst = context.root / "efi" / context.config.distribution.grub_prefix() / "fonts"
with umask(~0o700):
Expand Down Expand Up @@ -1755,7 +1824,7 @@ def python_binary(config: Config) -> str:
return "python3" if config.tools_tree else os.getenv("MKOSI_INTERPRETER", "python3")


def extract_pe_section(context: Context, binary: Path, section: str, output: Path) -> None:
def extract_pe_section(context: Context, binary: Path, section: str, output: Path) -> Path:
# When using a tools tree, we want to use the pefile module from the tools tree instead of requiring that
# python-pefile is installed on the host. So we execute python as a subprocess to make sure we load
# pefile from the tools tree if one is used.
Expand All @@ -1780,6 +1849,8 @@ def extract_pe_section(context: Context, binary: Path, section: str, output: Pat
sandbox=context.sandbox(options=["--ro-bind", binary, binary])
)

return output


def want_signed_pcrs(config: Config) -> bool:
return (
Expand Down Expand Up @@ -1984,6 +2055,7 @@ def install_type1(
if (
want_efi(context.config) and
context.config.secure_boot and
context.config.shim_bootloader != ShimBootloader.signed and
KernelType.identify(context.config, kimg) == KernelType.pe
):
kimg = sign_efi_binary(context, kimg, dst / "vmlinuz")
Expand Down Expand Up @@ -2064,29 +2136,39 @@ def install_uki(context: Context, kver: str, kimg: Path, token: str, partitions:
else:
boot_binary = context.root / f"boot/EFI/Linux/{token}-{kver}{boot_count}.efi"

microcode = build_microcode_initrd(context)

initrds = [microcode] if microcode else []
initrds += context.config.initrds or [build_default_initrd(context)]

if context.config.kernel_modules_initrd:
initrds += [build_kernel_modules_initrd(context, kver)]

# Make sure the parent directory where we'll be writing the UKI exists.
with umask(~0o700):
boot_binary.parent.mkdir(parents=True, exist_ok=True)

build_uki(
context,
systemd_stub_binary(context),
kver,
context.root / kimg,
initrds,
finalize_cmdline(context, roothash),
boot_binary,
)
if context.config.shim_bootloader == ShimBootloader.signed:
for p in (context.root / "usr/lib/modules" / kver).glob("*.efi"):
shutil.copy2(p, boot_binary)
break
else:
if context.config.bootable == ConfigFeature.enabled:
die(f"Couldn't find a signed UKI binary installed at /usr/lib/modules/{kver} in the image")

return
else:
microcode = build_microcode_initrd(context)

initrds = [microcode] if microcode else []
initrds += context.config.initrds or [build_default_initrd(context)]

if context.config.kernel_modules_initrd:
initrds += [build_kernel_modules_initrd(context, kver)]

build_uki(
context,
systemd_stub_binary(context),
kver,
context.root / kimg,
initrds,
finalize_cmdline(context, roothash),
boot_binary,
)

print_output_size(boot_binary)
print_output_size(boot_binary)

if want_grub_efi(context):
config = prepare_grub_config(context)
Expand Down
5 changes: 5 additions & 0 deletions mkosi/resources/mkosi.md
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,11 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
on UEFI firmware is requested using other options
(`Bootable=`, `Bootloader=`).

: Note that when this option is enabled, mkosi will only install already
signed bootloader binaries, kernel image files and unified kernel
images as self-signed binaries would not be accepted by the signed
version of shim.

`UnifiedKernelImages=`, `--unified-kernel-images=`

: Specifies whether to use unified kernel images or not when
Expand Down

0 comments on commit 7d036b8

Please sign in to comment.