1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 /*
4  * Generic Linux boot protocol using the EFI/PE entry point of the kernel. Passes
5  * initrd with the LINUX_INITRD_MEDIA_GUID DevicePath and cmdline with
6  * EFI LoadedImageProtocol.
7  *
8  * This method works for Linux 5.8 and newer on ARM/Aarch64, x86/x68_64 and RISC-V.
9  */
10 
11 #include <efi.h>
12 #include <efilib.h>
13 
14 #include "initrd.h"
15 #include "linux.h"
16 #include "pe.h"
17 #include "util.h"
18 
loaded_image_free(EFI_LOADED_IMAGE * img)19 static EFI_LOADED_IMAGE * loaded_image_free(EFI_LOADED_IMAGE *img) {
20         if (!img)
21                 return NULL;
22         mfree(img->LoadOptions);
23         return mfree(img);
24 }
25 
loaded_image_register(const CHAR8 * cmdline,UINTN cmdline_len,const void * linux_buffer,UINTN linux_length,EFI_HANDLE * ret_image)26 static EFI_STATUS loaded_image_register(
27                 const CHAR8 *cmdline, UINTN cmdline_len,
28                 const void *linux_buffer, UINTN linux_length,
29                 EFI_HANDLE *ret_image) {
30 
31         EFI_LOADED_IMAGE *loaded_image = NULL;
32         EFI_STATUS err;
33 
34         assert(cmdline || cmdline_len > 0);
35         assert(linux_buffer && linux_length > 0);
36         assert(ret_image);
37 
38         /* create and install new LoadedImage Protocol */
39         loaded_image = xnew(EFI_LOADED_IMAGE, 1);
40         *loaded_image = (EFI_LOADED_IMAGE) {
41                 .ImageBase = (void *) linux_buffer,
42                 .ImageSize = linux_length
43         };
44 
45         /* if a cmdline is set convert it to UCS2 */
46         if (cmdline) {
47                 loaded_image->LoadOptions = xstra_to_str(cmdline);
48                 loaded_image->LoadOptionsSize = StrSize(loaded_image->LoadOptions);
49         }
50 
51         /* install a new LoadedImage protocol. ret_handle is a new image handle */
52         err = BS->InstallMultipleProtocolInterfaces(
53                         ret_image,
54                         &LoadedImageProtocol, loaded_image,
55                         NULL);
56         if (EFI_ERROR(err))
57                 loaded_image = loaded_image_free(loaded_image);
58 
59         return err;
60 }
61 
loaded_image_unregister(EFI_HANDLE loaded_image_handle)62 static EFI_STATUS loaded_image_unregister(EFI_HANDLE loaded_image_handle) {
63         EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
64         EFI_STATUS err;
65 
66         if (!loaded_image_handle)
67                 return EFI_SUCCESS;
68 
69         /* get the LoadedImage protocol that we allocated earlier */
70         err = BS->OpenProtocol(
71                         loaded_image_handle, &LoadedImageProtocol, (void **) &loaded_image,
72                         NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
73         if (EFI_ERROR(err))
74                 return err;
75 
76         /* close the handle */
77         (void) BS->CloseProtocol(loaded_image_handle, &LoadedImageProtocol, NULL, NULL);
78         err = BS->UninstallMultipleProtocolInterfaces(
79                         loaded_image_handle,
80                         &LoadedImageProtocol, loaded_image,
81                         NULL);
82         if (EFI_ERROR(err))
83                 return err;
84         loaded_image_handle = NULL;
85         loaded_image = loaded_image_free(loaded_image);
86 
87         return EFI_SUCCESS;
88 }
89 
cleanup_initrd(EFI_HANDLE * initrd_handle)90 static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) {
91         (void) initrd_unregister(*initrd_handle);
92         *initrd_handle = NULL;
93 }
94 
cleanup_loaded_image(EFI_HANDLE * loaded_image_handle)95 static inline void cleanup_loaded_image(EFI_HANDLE *loaded_image_handle) {
96         (void) loaded_image_unregister(*loaded_image_handle);
97         *loaded_image_handle = NULL;
98 }
99 
100 /* struct to call cleanup_pages */
101 struct pages {
102         EFI_PHYSICAL_ADDRESS addr;
103         UINTN num;
104 };
105 
cleanup_pages(struct pages * p)106 static inline void cleanup_pages(struct pages *p) {
107         if (p->addr == 0)
108                 return;
109         (void) BS->FreePages(p->addr, p->num);
110 }
111 
linux_exec(EFI_HANDLE image,const CHAR8 * cmdline,UINTN cmdline_len,const void * linux_buffer,UINTN linux_length,const void * initrd_buffer,UINTN initrd_length)112 EFI_STATUS linux_exec(
113                 EFI_HANDLE image,
114                 const CHAR8 *cmdline, UINTN cmdline_len,
115                 const void *linux_buffer, UINTN linux_length,
116                 const void *initrd_buffer, UINTN initrd_length) {
117 
118         _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
119         _cleanup_(cleanup_loaded_image) EFI_HANDLE loaded_image_handle = NULL;
120         UINT32 kernel_alignment, kernel_size_of_image, kernel_entry_address;
121         EFI_IMAGE_ENTRY_POINT kernel_entry;
122         _cleanup_(cleanup_pages) struct pages kernel = {};
123         void *new_buffer;
124         EFI_STATUS err;
125 
126         assert(image);
127         assert(cmdline || cmdline_len == 0);
128         assert(linux_buffer && linux_length > 0);
129         assert(initrd_buffer || initrd_length == 0);
130 
131         /* get the necessary fields from the PE header */
132         err = pe_alignment_info(linux_buffer, &kernel_entry_address, &kernel_size_of_image, &kernel_alignment);
133         if (EFI_ERROR(err))
134                 return err;
135         /* sanity check */
136         assert(kernel_size_of_image >= linux_length);
137 
138         /* Linux kernel complains if it's not loaded at a properly aligned memory address. The correct alignment
139            is provided by Linux as the SegmentAlignment in the PeOptionalHeader. Additionally the kernel needs to
140            be in a memory segment that's SizeOfImage (again from PeOptionalHeader) large, so that the Kernel has
141            space for its BSS section. SizeOfImage is always larger than linux_length, which is only the size of
142            Code, (static) Data and Headers.
143 
144            Interrestingly only ARM/Aarch64 and RISC-V kernel stubs check these assertions and can even boot (with warnings)
145            if they are not met. x86 and x86_64 kernel stubs don't do checks and fail if the BSS section is too small.
146         */
147         /* allocate SizeOfImage + SectionAlignment because the new_buffer can move up to Alignment-1 bytes */
148         kernel.num = EFI_SIZE_TO_PAGES(ALIGN_TO(kernel_size_of_image, kernel_alignment) + kernel_alignment);
149         err = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, kernel.num, &kernel.addr);
150         if (EFI_ERROR(err))
151                 return EFI_OUT_OF_RESOURCES;
152         new_buffer = PHYSICAL_ADDRESS_TO_POINTER(ALIGN_TO(kernel.addr, kernel_alignment));
153         CopyMem(new_buffer, linux_buffer, linux_length);
154         /* zero out rest of memory (probably not needed, but BSS section should be 0) */
155         SetMem((UINT8 *)new_buffer + linux_length, kernel_size_of_image - linux_length, 0);
156 
157         /* get the entry point inside the relocated kernel */
158         kernel_entry = (EFI_IMAGE_ENTRY_POINT) ((const UINT8 *)new_buffer + kernel_entry_address);
159 
160         /* register a LoadedImage Protocol in order to pass on the commandline */
161         err = loaded_image_register(cmdline, cmdline_len, new_buffer, linux_length, &loaded_image_handle);
162         if (EFI_ERROR(err))
163                 return err;
164 
165         /* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */
166         err = initrd_register(initrd_buffer, initrd_length, &initrd_handle);
167         if (EFI_ERROR(err))
168                 return err;
169 
170         /* call the kernel */
171         return kernel_entry(loaded_image_handle, ST);
172 }
173