-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
blog: Add blog about
Unikraft EFI stub
Signed-off-by: Sergiu Moga <[email protected]>
- Loading branch information
1 parent
ff2c585
commit 63fdadd
Showing
1 changed file
with
37 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
+++ | ||
title = "Unikraft EFI Stub" | ||
date = "2023-07-05T13:00:00+01:00" | ||
author = "Sergiu Moga" | ||
tags = ["yes"] | ||
+++ | ||
|
||
# Unikraft UEFI Stub | ||
|
||
This blog post presents what it took to implement Unikraft's UEFI stub to enable `Unikraft` to boot without a bootloader on `x86_64` and `Aarch64` `UEFI` systems. | ||
|
||
## The journey | ||
### PIE Support | ||
If the address at which the `UEFI` image wants to be loaded at is marked as reserved by `UEFI`, then the firmware will try to load it at the next available memory region big enough to fit the image. Therefore, we need to be able to operate in a position independent manner. | ||
|
||
To achieve this, support for being built as `PIE` and being able to self relocate has been [implemented](https://github.com/unikraft/unikraft/pull/772) for both `AArch64` and `x86_64`. | ||
|
||
### Memory region integration | ||
To align all the other platforms and architectures with `x86_64`'s `KVM` `ukplat_memregion_*` API's and make them `UEFI` fiendly from a memory setup point of view, I have also [integrated](https://github.com/unikraft/unikraft/pull/848) these API's as well as `ukbootinfo` into them. This would also allow for easier `ASLR` integration in the future. While at it, I noticed that it would be easy to also integrate `ukvmem` into `AArch64` so [I did that as well](https://github.com/unikraft/unikraft/pull/908). | ||
|
||
### EFI stub | ||
The actual `UEFI` stub consists of three stages, before jumping to `Unikraft`'s entry point. You may find the implementation [here](https://github.com/unikraft/unikraft/pull/909). A useful [script](https://github.com/unikraft/unikraft/pull/910) has also been added to allow one to easily build `Unikraft` as both `ISO` and `DISK` images. | ||
|
||
The first stage is written in assembly and, therefore, depends on the architecture, although the steps are largely the same: | ||
- In the case of `x86_64` only, adjust the misaligned stack | ||
- Invoke relocator implemented in the `PIE` implementation. | ||
- Jump to the second stage | ||
|
||
The second stage is an architecture agnostic one and involved setting up the `Initial RAM Disk`, `Devicetree` and `Kernel Command-line` as well as the `Memory Map`. Each of these must be converted to the format of `struct ukplat_memregion_descriptor` so that they can be seen by the paging subsystem and be mapped accordingly. Furthermore, an additional, optional step, is that of enabling `Trusted Computing Group`'s `Reset Attack Mitigation` `UEFI` option to be checked for by the `BIOS` during its `POST` phase. The end of this second stage is marked by exiting the `UEFI` environment through `ExitBootService()` and jumping to the third and final stage. | ||
|
||
The third stage is architecture dependent and can be seen as the pre-kernel, as it applies finishing touches, post-bootloader setup, to accomodate bootloader given environment to the kernel's liking and capabilities. I believe this was by far the most difficult part to implement: bridging the gap between `UEFI` exit and `Unikraft` entry. | ||
The `x86_64` pre-kernel begins by ensuring it has access to a valid `.uk bootinfo` section and then proceeds to set up the startup arguments for Unikraft: a page-sized stack and an entry function, which in our case is the generic `_ukplat_entry`. After IRQ’s are disabled, we replace our current `UEFI` Page Tables, with `Unikraft`’s static `Boot Page Tables` to make sure that our initial mappings are those that `Unikraft` expects. Note that after `ExitBootServices` is called, regardless of the architecture, if the virtual mappings are changed, the `Operating System` must invoke the `Runtime Services`’ `SetVirtualAddressMap()`. This is a routine that sets the `Runtime Services` in `Virtual Mode` and can only be called once. However, we do not need to worry about this routine because `Unikraft`’s virtual mappings of the `Runtime Services` memory regions are linear just like `UEFI`’s. Due to how underdeveloped `Unikraft`’s `IRQ` subsystem is, this stage must make sure that we create an `IRQ` environment similar to what a `Legacy BIOS` would have created. Unless the, now deprecated, `UEFI CSM` is enabled, the firmware will mask the `8259 Legacy PIC`, since the firmware relies on `IO(x)APIC` which `Unikraft` does not support. Therefore, we must manually make sure it is unmasked. `Unikraft` does not have a `LAPIC` driver and, therefore, does not support the `LAPIC Timer`, usually ending up relying on the `Legacy PIT` that is only wired to the `Bootstrap Processor`. `UEFI` does rely on the `LAPIC Timer` for timer periodic events and therefore enables it, usually with a `10KHz` frequency, leaving us receiving a very high number of unhandled interrupts per second. Thus, we must take care of disabling this `UEFI` leftover component as well. The last step of the `x86_64` pre-kernel before jumping to `Unikraft`’s core through `lcpu_start64` is to make sure that the trigger type for the `PIIX3/4` chipset legacy `Shared PCI IRQ’s` are set to `level-triggered`. By default, they are set to `edge-triggered` and, since `UEFI` does not care about the `Legacy PIC` and simply masks it, these `IRQ`’s are never properly setup. This is easily achieved by updating the corresponding bitfields of the cascaded `Slave PIC`’s `Edge/Level Triggered Register`. | ||
|
||
In the case of `AArch64`, unlike `x86_64`, the pre-kernel setup was significantly easier to achieve, due to the fact that `Unikraft` does support the `ARM Generic Timer` and the fact that, by default, both `GICv2` and `GICv3`, for which `Unikraft` implements very minimalistic drivers, have `active-low`, `level-triggered` `IRQ`’s. This leaves us with very little things to create workarounds for and the pre-kernel ends up being largely the same as the `x86_64` one, except for the `IRQ` adjustments. One noteworthy step different from the `x86_64` pre-kernel we must take however, is that we must set some `System Control Registers` to their `Warm Reset` default values. | ||
|
||
## Next steps | ||
I believe a somewhat good next step would be implementing a somewhat `Secure Boot` compliant intermediate step, as `Unikraft` loads resources without authenticating them. I believe using `Unikraft`'s port of [`OpenSSL`](https://github.com/unikraft/lib-openssl) may come in handy here as it has plenty of cryptographic routines that we could use. For starters, the most basic and naive implementation would be signing the resources at build time and storing the hash or public key as strings in the Kernel. This way, we could authenticate a resource by comparing its hash. |