-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
stub: Allow booting compressed aarch64 Linux kernel images #23788
Comments
#23347 is the matching RFE for the spec. |
Hmm, so there are two approaches here:
I figure the second approach is nicer: it means the firmware can invoke our images directly, i.e. the files become entirely self contained, which I think is good. It also means the sysext/creds pick-up that we do in sd-stub continues to work just fine, because the fs protocol object the stub PE binary is invoked from is a real fs one, so that we can use it to enumerate files adjacent to the image. If we already synthesized an fs protocol object at that point, this is not doable that easily. |
Mind you, |
Yeah, we'd have to embedd a minimal copy of zlib decompress (or whatever format is en vogue today) in our codebase i fear. yuck. |
You mean zstd here, right? The EFI_DECOMPRESS_PROTOCOL is provided by the firmware. :P We don't need to vendor those, in theory. Providing a path to a static lib during build should be enough so that we can link against it. |
Well, depends: if they rely on libc symbols (and I think zlib does, it provides fopen() wrappers, no?) then a regular distros static lib won't work. I doubt there's a way around vendoring this. But I am curious, what actually is en vogue as compressor for this usecase currently? gz? zstd? xz? The fewer we have to support, the better. |
yeah, I'd ignore this bit. sounds risky to rely on that given that such decompressors are parsers and hence security sensitive. |
There's a third option, to convince the Linux aarch64 maintainers to add a decompressor in the kernel. They are were against doing it in the past, i.e: http://lists.infradead.org/pipermail/linux-arm-kernel/2014-January/225277.html but really it's silly that all bootloaders need to vendor decompression libraries and catch-up with whatever format is used. |
En vouge is
That would not be an issue under secure boot. |
of course, I'd prefer not having to deal with all this. I don't really understand why the decompression code that apparently already exists in the kernel and is regularly used on x86 can't be enabled for arm too |
To make our lifes miserable? |
Me neither... other than the answer in the email thread that I referenced before. They also documented it in https://www.kernel.org/doc/html/latest/arm64/booting.html#decompress-the-kernel-image |
@poettering @medhefgo maybe a silly thought but what about instead just document in the BootLoaderSpec that some arches on Linux have this limitation and are not able to self-decompress? And make it explicit that the sd-stub isn't able to decompress and so if someone wants to use the BLS type 2 (unified kernels) on these arches, they must have their kernel decompressed? Because the Linux kernel supports a bunch of compression formats (gzip, bzip2, lzma, xz, lzo, lz4 and zstd) so even if the sd-stub supports what's in en vogue (ztsd IIUC) there will be cases where it won't work anyways depending on the compression algorithm used by the distro. |
Just my 3 cents: u-boot does not handle compressed initrd images |
The reason for avoiding a decompressor in the arm64 tree is not to make your lives miserable, I can assure you :-) The problem is not decompression per se - the problem is that you need to bootstrap another minimal execution environment, which needs to be built, linked and executed so you can invoke the decompression algorithm. On ARM, this needs the MMU and caches to be enabled, or it will not only be dead slow, but also reject things like unaligned accesses and zero-by-cacheline (DC ZVA) instructions. Since we cannot just map the entire address space with cacheable attributes (as speculative instruction fetches or data accesses from device regions must be avoided), we now have to parse either the device tree (DT) or the EFI memory map to find out where the memory lives to begin with, in order to map it. I had a stab at implementing a generic EFI decompressor for all non-x86 EFI architectures in Linux (arm64, ARM, RISC-V, LoongArch) here, which mostly works fine. The only issue there is that it breaks UEFI secure boot, unless we find a way to sign both the inner and outer PE/COFF images during the build. |
@ardbiesheuvel hmm, that's really helpful. The Secureboot signing thing is interesting indeed. If we had the decompression in sd-boot then indeed signing would be easier. In that light it might indeed be better to decompress in the boot loader instead of the kernel image. So I am not totally opposed to doing decompression in sd-boot. But I'd really like to keep this minimal. i.e. only one relevant compression algorithm (zstd then?) and I'd be really keen on managing this in a reasonable way so that we can still receive updates from the zsdt reference implementation in a sensible way. i.e. meson subproject stuff ideally. Not sure if that is possible with the reference implementation though given we also need it without referencing any libc symbols... I wonder how open the zstd people would be to make things like this easy. I think they actually support building with meson, question is if we can vendor it in as meson subproject that way and without linking to libc... |
Well, we're faced with the same issue already? The way we work around it right now is that we copy the kernel image from the stub to correctly allocated memory and then call the PE entry point directly. We don't even perform PE relocations (I guess we're lucky the kernel doesn't blow up on us?). Also, why not implement this decompression within the normal stub (so you'd only have the payload compressed in a section)? |
Who are 'we' in this context? systemd-boot? If systemd-boot implements EFI boot by using LoadImage and instead of using StartImage, doing something else to boot the image, it is definitely doing something non-portable, and this will only generally work with the x86 kernel. The same goes for the EFI handover protocol, which has been deprecated because it blurs the lines between EFI, Linux and architecture too much. Note that LoadImage() will take care of the PE/COFF relocations, so this should work even for chainloading arbitrary PE/COFF executables other than Linux (Linux PE/COFF images are built without relocations) But doing anything other than calling StartImage is a hack. This includes calling into shim to do load and/or start the kernel - shim+grub+stub on x86 is such a pile of hacks it is not even funny anymore.
The 'payload' in this case is the kernel's executable image. Which means we still need another executable image to perform the actual decompression. (On Linux/arm64, the stub and the kernel proper are essentially the same executable image with different entry points) |
With secure boot in the mix there really are only three options:
Pick your poison. I originally wanted to do 3 because it allows us to use |
So what is sd-stub? Is that part of systemd-boot?
Wait what? Are you using systemd-boot on arm64 does not use LoadImage/StartImage? Or only with secure boot enabled? In any case, this is fundamentally broken, and a huge burden in terms of technical debt - things like measure boot and other processing that is implicitly part of LoadImage/StartImage will have to be reimplemented in your LoadImage/StartImage implementation. This is exactly why shim+grub is such a horror show, and I am really disappointed to learn that the formerly 'EFI-clean' gummiboot/systemd-boot has gone down this road. |
sd-stub is the stub loader for unified kernel images. It allows stuffing the kernel image + discovery metadata + initrd + dtb + kernel args + splash image etc into a single efi binary (which is then supposed to be signed).
sd-boot has two types of boot entries: regular .conf ones and unified kernel images. The .conf ones are launched using We do intend to drop EFI handover at some point, but See Line 107 in 2fb1165
|
It's part of systemd, but not of systemd-boot (though it shares some sources with it). It's an UEFI stub, that does various nice things around unified kernels, boot splash, TPM measurements. You can use systemd-boot without buying into systemd-stub. You can also use systemd-stub without buying into systemd-boot. But ideally you use them in combination. Here's the man page of systemd-stub, to give you an idea why it is useful: https://www.freedesktop.org/software/systemd/man/systemd-stub.html |
As a temp fix (because I'm hacking systemd-boot support into anaconda as a POC) I've been detecting the combo of systemd-boot, and a compressed arm kernel and just decompressing it in kernel-install. That isn't ideal on some of these really slow SD/etc devices, but at the moment it works. |
Just FYI, this is also an issue on Asahi Linux (linux on M1 Macs). Asahi ships GRUB out of the box, and when switching to systemd-boot I hit this issue. I haven't been able to make it work even with an uncompressed kernel either. This last thing might be user error. Currently booting without the stub (e.g.: not using a UEFI bundle but separate kernel+initrd). |
Can you give some more information on how you did this? I've been trying to do the same and while decompressing the image works, that leaves you with the linux kernel image in ELF format which EFI doesn't recognize. How do you go back from the decompressed kernel image to the PE/COFF format that EFI recognizes? |
So I've been testing @ardbiesheuvel's generic compressed boot for efi patchset (https://lore.kernel.org/lkml/20220910081152.2238369-4-ardb@kernel.org/T/), and while it works perfectly for the regular linux image scenario started using sd-boot, it doesn't quite work for the stub yet. The issue I'm running into is that zboot is querying the loaded image device path protocol of the loaded image, but we never install a loaded image device path protocol in the stub, so zboot fails. The spec mentions that the device path protocol for the loaded image is optional. @ardbiesheuvel Would it be possible to make the loaded image device path protocol handling in zboot optional (don't do anything with it if it isn't in place)? If not, we'll need to modify sd-boot to install a loaded image device path protocol in the stub code to make this work. It's also interesting to note that the zboot patchset chooses to sign both the outer zboot image and the inner Linux image to make secure boot work, whereas in the stub we work around the secure boot issue by invoking the kernel PE entry point directly. Maybe it makes sense for us to adopt the same approach for unified kernel images so that we can use LoadImage/StartImage instead of the hacks we do now? cc @medhefgo since you mentioned using LoadImage()/StartImage() as well in the PR that added the PE kernel entry stuff. |
I am in the process of switching to LoadImage/StartImage in our stub. There is a neat hack using the EFI_SECURITY_ARCH_PROTOCOL to work around any unsigned payloads (effectively the same way we support shim protocol in sd-boot). I was also investigating adding zstd support into the stub, from the first look it should be fairly easy to do… |
Hmm, my copy of the EFI spec says "The Loaded Image Device Path Protocol must be installed onto the image handle of a PE/COFF image loaded through the EFI Boot Service LoadImage()." Where does the spec mention that it is optional?
You can just install it with a NULL pointer and things should work - this is what EFI itself does if LoadImage() is called with a NULL device path and source buffer and size.
As I have stated many times before, this is really a hack. I fully understand that this has been a necessary hack on x86 PCs built to run Windows, but on arm64, I strongly urge you not to roll your own LoadImage/StartImage() - this has been a constant source of bugs with Fedora's version of GRUB, for instance. (I mentioned this in my LPC talk last week as well)
That would be much better, yes.
|
Yes, I played around with that as well, but @mjg59 mentioned that this is not a reliable workaround, and therefore not enabled by default in shim
I just merged the EFI zboot support for 6.1 so you will only need this for older kernels. Still seems like a reasonable place to use compression, so I'm not saying it is a bad idea. |
Ah my statement was wrong, the spec says it's optional to provide a device path to LoadImage() if using the |
Actually, it just installs the protocol with NULL as the protocol pointer.
Not necessarily, but for debugging and logging etc, it would be best to pass a MemoryMapped() device path to LoadImage() even when using the source buffer and size. And if you are not using LoadImage(), install that as the LOADED_IMAGE_DEVICE_PATH_PROTOCOL onto the created handle. (type 1, subtype 3) |
Gee, the naming of these protocols is always confusing. We provide a loaded image protocol (we have to, for the cmdline), but not a loaded image device path protocol. But changing this to LoadImage/StartImage will fix it anyways.
Can you provide any links? Considering that there's tons of people using shim with sd-boot it seems reliable enough on x86. |
I mean even if disabling secure boot for the inner image doesn't work, worst case users have to sign their kernel image on top of their unified kernel image. This just means that users either have to that directly or w.e. tool (e.g. mkosi) that is building the unified kernel images needs to additionally sign the kernel image. I don't see this being a big problem tbh. If you're able to sign the UKI, you should be able to sign the kernel image individually as well. |
Yes, but shim removes its protocol once the unified kernel image is run by its trusted boot loader (you can easily test this by chainloading sd-boot from sd-boot and looking at the output from pressend P). So even if it's shim-signed, it won't be trusted by the firmware unless we do something about it. Of course this isn't a problem with custom signing keys. |
Should we add this to the 252 milestone? I'd very much like sd-boot to work with aarch64 and compressed kernels so that it can be used on the Apple M1 macs with Asahi Linux in a stable release. @medhefgo Is the port to LoadImage()/StartImage() a lot of work? |
I am not saying sd-stub does not work with shim. I am saying that shim itself relies on other means (such as rewriting LoadImage/StartImage) to work around secure boot restrictions on when only the MS certs are supported by the firmware. The EFI_SECURITY_ARCH_PROTOCOL workaround is only used when OVERRIDE_SECURITY_POLICY is set at built time, and AIUI, Fedora does not actually enable that in their production builds. EFI_SECURITY_ARCH_PROTOCOL is not part of the UEFI spec, in spite of its name. It is part of the PI spec, which is an internal firmware spec, and you can implement EFI without PI. So even if it works for most x86 systems, it's not something you can rely on in general. Also, recent firmwares have started to rely more and more on strict memory permission attributes, so poking the internals of an existing protocol is kind of flaky as well. For sd-stub in particular (which is guaranteed to be the penultimate boot stage in terms of PE/COFF applications), you might be better off using ReinstallProtocolInterface() to swap out the entire thing. |
The Load/StartImage is trivial and already done. I am mostly trying to flesh out the security arch thing.
That is true for shim itself. But sd-boot has to use this trick (and always had since shim support was added) to be able to chainload the actual boot entry. Because for some reason shim never provided a Load/StartImage as part of the shim protocol to do any of the work for use, instead we hook into security arch and then call shim_verify from there. I presume grub still relies on efi handover on x86 to not have this issue. |
So, not to drag this up, but IIRC the MOK methods exists to avoid the problem of the distro's being in a privileged state with regard to enrolling their keys in the DB (by getting MS to sign them). The fact that shim exists solves this problem, so there isn't any reason for not enrolling the distro keys at this point and skipping the MOK/etc for something like the systemd-boot/efi stubbed kernels, which the bootloader and kernels can both be signed with a distro key. The latest win11 guide already has a MS in a privileged position as the newer machines now have a bios switch for switching between the "MS" and "UEFI" keys. Which is effectively a Windows/Linux switch. So where is the problem providing a preloader type thing that pops up on the first boot of a distro CD and says something like "enroll RH/suse/ubuntu/etc key (Y/N)" if its not already enrolled and updates the db. Then the distro's can happily sign on the efi bits in the boot path with their own keys and we don't have to worry about all these hacky partially implemented loaders and just depend on UEFI for load/start. |
You'd have to ask the shim/MS signing people to get an answer to that. My guess: $$$$ (you gotta pay for a ms signature on your efi binary). (In case it's not clear: you cannot enroll a key into the database even with a signed efi image, unless the database update itself is sign by a trusted KEK, which MS presumably doesn't do.) |
I imagine MS want to prevent those keys from being used to sign doctored copies of the Windows loader. But as you mentioned, they recently started mandating 'MS keys only' for the more secure configurations, so this seems totally feasible, especially now that Linux is no longer the enemy. |
Here is some WIP code for zstd compressed kernels: https://github.com/medhefgo/systemd/tree/zstd It works for x86 in a vm for me. Also, I am assuming here that the kernel PE binary is just zstd compressed as is / can be passed to LoadImage right after decompression. Also, I am wondering if there is any reason to use the streaming decompression API or not… If you do test it, make sure to use manual VMA values for any stub sections you add, the zstd code will make the code too big for the default VMA values used everywhere: #24202. |
Let's move this to the next milestone. At this point it's not clear who should do what: the kernel, the stub, the bootloader, some combination of the above depending on circumstances? Let's at least wait for the situation in the kernel to settle down. |
@medhefgo what's the status on this? Wasn't this implemented kernel-side? Anything else we need to do? |
Afaik, the consensus was to leave this to the kernel. So nothing for us to do, really. |
Let's close the issue then |
JIRA: https://issues.redhat.com/browse/RHEL-25537 Upstream Status: https://gitlab.com/cki-project/kernel-ark Conflicts: redhat/configs/common/generic/CONFIG_EFI_ZBOOT (RHEL) redhat/configs/rhel/generic/CONFIG_EFI_ZBOOT (kernel-ark) -CONFIG_EFI_ZBOOT was originally enabled by kernel-ark commit caf3f5102ff6 ("aarch64: enable zboot") using redhat/configs/rhel/generic/CONFIG_EFI_ZBOOT. Later, kernel-ark commit 470f91e9328a ("Consolidate common configs for 6.2") moved this config file to redhat/configs/common/generic/CONFIG_EFI_ZBOOT commit caf3f5102ff659a773123796dda16c2ac91300b9 Author: Gerd Hoffmann <kraxel@redhat.com> Date: Wed Feb 8 08:22:31 2023 +0100 aarch64: enable zboot Enable CONFIG_EFI_ZBOOT. Also adapt %make_target and %kernel_image for zboot. With the kernel self-uncompressing itself the bootloader or the systemd-stub doesn't need to handle the uncompressing (and doesn't need to know how the kernel is compressed, i.e. whenever it is .gz or .xz). Some more background: systemd/systemd#23788 It also makes aarch64 work more like x86_64 where the kernel decompresses itself too. Signed-off-by: Gerd Hoffmann <kraxel@redhat.com> Signed-off-by: Lenny Szubowicz <lszubowi@redhat.com>
Is your feature request related to a problem? Please describe.
The Linux kernel does not have a decompressor for the aarch64 architecture, the arm64 booting documentation says:
Which means that if a Linux kernel image is shipped compressed, the
linuxaa64.efi.stub
won't be able to boot the image embedded in the.linux
section.Describe the solution you'd like
While it is possible to use uncompressed Linux kernel images, the stub should be able to decompress the images if needed. Since most distributions would ship the kernel images compressed for all the supported architectures. Since most bootloaders are able to decompress them.
Describe alternatives you've considered
The UEFI specification mentions a
EFI_DECOMPRESS_PROTOCOL.Decompress()
service that could be used by the stub to decompress the kernel images and not having to implement its own decompressor.The systemd version you checked that didn't have the feature you are asking for
Latest upstream main branch.
The text was updated successfully, but these errors were encountered: