]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
0fa2cac4 KS |
2 | |
3 | #include <efi.h> | |
4 | #include <efilib.h> | |
5 | ||
845707aa | 6 | #include "cpio.h" |
33bc9b75 | 7 | #include "devicetree.h" |
8110e144 | 8 | #include "disk.h" |
37fa3690 | 9 | #include "graphics.h" |
0fa2cac4 | 10 | #include "linux.h" |
d4cbada2 MG |
11 | #include "measure.h" |
12 | #include "pe.h" | |
ce0f078f | 13 | #include "secure-boot.h" |
cf0fbc49 TA |
14 | #include "splash.h" |
15 | #include "util.h" | |
0fa2cac4 KS |
16 | |
17 | /* magic string to find in the binary image */ | |
2ccd5986 | 18 | _used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####"; |
0fa2cac4 | 19 | |
845707aa LP |
20 | static EFI_STATUS combine_initrd( |
21 | EFI_PHYSICAL_ADDRESS initrd_base, UINTN initrd_size, | |
70cd15e9 | 22 | const void *credential_initrd, UINTN credential_initrd_size, |
f3b6f333 | 23 | const void *global_credential_initrd, UINTN global_credential_initrd_size, |
70cd15e9 | 24 | const void *sysext_initrd, UINTN sysext_initrd_size, |
845707aa LP |
25 | EFI_PHYSICAL_ADDRESS *ret_initrd_base, UINTN *ret_initrd_size) { |
26 | ||
27 | EFI_PHYSICAL_ADDRESS base = UINT32_MAX; /* allocate an area below the 32bit boundary for this */ | |
28 | EFI_STATUS err; | |
db4122d1 | 29 | uint8_t *p; |
845707aa LP |
30 | UINTN n; |
31 | ||
32 | assert(ret_initrd_base); | |
33 | assert(ret_initrd_size); | |
34 | ||
f3b6f333 | 35 | /* Combines four initrds into one, by simple concatenation in memory */ |
845707aa | 36 | |
b4e7df4a | 37 | n = ALIGN4(initrd_size); /* main initrd might not be padded yet */ |
845707aa LP |
38 | if (credential_initrd) { |
39 | if (n > UINTN_MAX - credential_initrd_size) | |
40 | return EFI_OUT_OF_RESOURCES; | |
41 | ||
42 | n += credential_initrd_size; | |
43 | } | |
f3b6f333 AV |
44 | if (global_credential_initrd) { |
45 | if (n > UINTN_MAX - global_credential_initrd_size) | |
46 | return EFI_OUT_OF_RESOURCES; | |
47 | ||
48 | n += global_credential_initrd_size; | |
49 | } | |
845707aa LP |
50 | if (sysext_initrd) { |
51 | if (n > UINTN_MAX - sysext_initrd_size) | |
52 | return EFI_OUT_OF_RESOURCES; | |
53 | ||
54 | n += sysext_initrd_size; | |
55 | } | |
56 | ||
12f32748 | 57 | err = BS->AllocatePages( |
845707aa LP |
58 | AllocateMaxAddress, |
59 | EfiLoaderData, | |
60 | EFI_SIZE_TO_PAGES(n), | |
61 | &base); | |
2a5e4fe4 | 62 | if (err != EFI_SUCCESS) |
845707aa LP |
63 | return log_error_status_stall(err, L"Failed to allocate space for combined initrd: %r", err); |
64 | ||
a0a644be | 65 | p = PHYSICAL_ADDRESS_TO_POINTER(base); |
845707aa LP |
66 | if (initrd_base != 0) { |
67 | UINTN pad; | |
68 | ||
69 | /* Order matters, the real initrd must come first, since it might include microcode updates | |
70 | * which the kernel only looks for in the first cpio archive */ | |
bbc1f2ea | 71 | memcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); |
845707aa LP |
72 | p += initrd_size; |
73 | ||
b4e7df4a | 74 | pad = ALIGN4(initrd_size) - initrd_size; |
845707aa | 75 | if (pad > 0) { |
bbc1f2ea | 76 | memset(p, 0, pad); |
845707aa LP |
77 | p += pad; |
78 | } | |
79 | } | |
80 | ||
81 | if (credential_initrd) { | |
bbc1f2ea | 82 | memcpy(p, credential_initrd, credential_initrd_size); |
845707aa LP |
83 | p += credential_initrd_size; |
84 | } | |
85 | ||
f3b6f333 | 86 | if (global_credential_initrd) { |
bbc1f2ea | 87 | memcpy(p, global_credential_initrd, global_credential_initrd_size); |
f3b6f333 AV |
88 | p += global_credential_initrd_size; |
89 | } | |
90 | ||
845707aa | 91 | if (sysext_initrd) { |
bbc1f2ea | 92 | memcpy(p, sysext_initrd, sysext_initrd_size); |
845707aa LP |
93 | p += sysext_initrd_size; |
94 | } | |
95 | ||
db4122d1 | 96 | assert((uint8_t*) PHYSICAL_ADDRESS_TO_POINTER(base) + n == p); |
845707aa LP |
97 | |
98 | *ret_initrd_base = base; | |
99 | *ret_initrd_size = n; | |
100 | ||
101 | return EFI_SUCCESS; | |
102 | } | |
103 | ||
b30a43df | 104 | static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { |
3639d1b0 | 105 | char16_t uuid[37]; |
5a186322 LP |
106 | |
107 | assert(loaded_image); | |
108 | ||
109 | /* Export the device path this image is started from, if it's not set yet */ | |
110 | if (efivar_get_raw(LOADER_GUID, L"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) | |
111 | if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) | |
112 | efivar_set(LOADER_GUID, L"LoaderDevicePartUUID", uuid, 0); | |
113 | ||
114 | /* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the | |
115 | * UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note | |
116 | * that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us, | |
117 | * in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong | |
118 | * here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath | |
119 | * is non-NULL explicitly.) */ | |
120 | if (efivar_get_raw(LOADER_GUID, L"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS && | |
121 | loaded_image->FilePath) { | |
3639d1b0 | 122 | _cleanup_free_ char16_t *s = NULL; |
5a186322 LP |
123 | |
124 | s = DevicePathToStr(loaded_image->FilePath); | |
9f048123 JJ |
125 | if (s) |
126 | efivar_set(LOADER_GUID, L"LoaderImageIdentifier", s, 0); | |
127 | else | |
128 | log_oom(); | |
5a186322 LP |
129 | } |
130 | ||
131 | /* if LoaderFirmwareInfo is not set, let's set it */ | |
132 | if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) { | |
3639d1b0 | 133 | _cleanup_free_ char16_t *s = NULL; |
ec4106af | 134 | s = xpool_print(L"%s %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); |
0a15a824 | 135 | efivar_set(LOADER_GUID, L"LoaderFirmwareInfo", s, 0); |
5a186322 LP |
136 | } |
137 | ||
138 | /* ditto for LoaderFirmwareType */ | |
139 | if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) { | |
3639d1b0 | 140 | _cleanup_free_ char16_t *s = NULL; |
ec4106af | 141 | s = xpool_print(L"UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); |
0a15a824 | 142 | efivar_set(LOADER_GUID, L"LoaderFirmwareType", s, 0); |
5a186322 LP |
143 | } |
144 | ||
145 | /* add StubInfo */ | |
146 | if (efivar_get_raw(LOADER_GUID, L"StubInfo", NULL, NULL) != EFI_SUCCESS) | |
147 | efivar_set(LOADER_GUID, L"StubInfo", L"systemd-stub " GIT_VERSION, 0); | |
148 | } | |
149 | ||
0fa2cac4 | 150 | EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { |
e41d3d89 LP |
151 | |
152 | enum { | |
153 | SECTION_CMDLINE, | |
154 | SECTION_LINUX, | |
155 | SECTION_INITRD, | |
156 | SECTION_SPLASH, | |
33bc9b75 | 157 | SECTION_DTB, |
e41d3d89 LP |
158 | _SECTION_MAX, |
159 | }; | |
160 | ||
07d0fde4 JJ |
161 | static const char * const sections[_SECTION_MAX + 1] = { |
162 | [SECTION_CMDLINE] = ".cmdline", | |
163 | [SECTION_LINUX] = ".linux", | |
164 | [SECTION_INITRD] = ".initrd", | |
165 | [SECTION_SPLASH] = ".splash", | |
166 | [SECTION_DTB] = ".dtb", | |
e41d3d89 | 167 | NULL, |
0fa2cac4 | 168 | }; |
5b5d365d | 169 | |
33bc9b75 | 170 | UINTN cmdline_len = 0, linux_size, initrd_size, dt_size; |
f3b6f333 AV |
171 | UINTN credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0; |
172 | _cleanup_freepool_ void *credential_initrd = NULL, *global_credential_initrd = NULL; | |
173 | _cleanup_freepool_ void *sysext_initrd = NULL; | |
33bc9b75 MR |
174 | EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base; |
175 | _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; | |
b30a43df | 176 | EFI_LOADED_IMAGE_PROTOCOL *loaded_image; |
e41d3d89 LP |
177 | UINTN addrs[_SECTION_MAX] = {}; |
178 | UINTN szs[_SECTION_MAX] = {}; | |
07d0fde4 JJ |
179 | char *cmdline = NULL; |
180 | _cleanup_free_ char *cmdline_owned = NULL; | |
0fa2cac4 KS |
181 | EFI_STATUS err; |
182 | ||
183 | InitializeLib(image, sys_table); | |
948d085e JJ |
184 | debug_hook(L"systemd-stub"); |
185 | /* Uncomment the next line if you need to wait for debugger. */ | |
186 | // debug_break(); | |
0fa2cac4 | 187 | |
12f32748 JJ |
188 | err = BS->OpenProtocol( |
189 | image, | |
190 | &LoadedImageProtocol, | |
191 | (void **)&loaded_image, | |
192 | image, | |
193 | NULL, | |
194 | EFI_OPEN_PROTOCOL_GET_PROTOCOL); | |
2a5e4fe4 | 195 | if (err != EFI_SUCCESS) |
8aba0eec | 196 | return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err); |
0fa2cac4 | 197 | |
6028dd6d | 198 | err = pe_memory_locate_sections(loaded_image->ImageBase, sections, addrs, szs); |
2a5e4fe4 JJ |
199 | if (err != EFI_SUCCESS || szs[SECTION_LINUX] == 0) { |
200 | if (err == EFI_SUCCESS) | |
65ff3d26 | 201 | err = EFI_NOT_FOUND; |
8aba0eec | 202 | return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err); |
65ff3d26 | 203 | } |
0fa2cac4 | 204 | |
94b81afb | 205 | /* Show splash screen as early as possible */ |
db4122d1 | 206 | graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[SECTION_SPLASH], szs[SECTION_SPLASH], NULL); |
94b81afb | 207 | |
e41d3d89 | 208 | if (szs[SECTION_CMDLINE] > 0) { |
07d0fde4 | 209 | cmdline = (char *) loaded_image->ImageBase + addrs[SECTION_CMDLINE]; |
e41d3d89 LP |
210 | cmdline_len = szs[SECTION_CMDLINE]; |
211 | } | |
0fa2cac4 | 212 | |
53a20455 | 213 | /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace the built-in one */ |
ce0f078f | 214 | if ((!secure_boot_enabled() || cmdline_len == 0) && loaded_image->LoadOptionsSize > 0 && |
3639d1b0 | 215 | *(char16_t *) loaded_image->LoadOptions > 0x1F) { |
07d0fde4 | 216 | cmdline_len = (loaded_image->LoadOptionsSize / sizeof(char16_t)) * sizeof(char); |
0af26643 | 217 | cmdline = cmdline_owned = xmalloc(cmdline_len); |
9f048123 | 218 | |
258f0970 | 219 | for (UINTN i = 0; i < cmdline_len; i++) |
3639d1b0 | 220 | cmdline[i] = ((char16_t *) loaded_image->LoadOptions)[i]; |
92ed3bb4 | 221 | |
e6e24af5 LP |
222 | /* Let's measure the passed kernel command line into the TPM. Note that this possibly |
223 | * duplicates what we already did in the boot menu, if that was already used. However, since | |
224 | * we want the boot menu to support an EFI binary, and want to this stub to be usable from | |
225 | * any boot menu, let's measure things anyway. */ | |
70cd15e9 | 226 | (void) tpm_log_load_options(loaded_image->LoadOptions); |
0fa2cac4 KS |
227 | } |
228 | ||
5a186322 | 229 | export_variables(loaded_image); |
1aa15def | 230 | |
70cd15e9 | 231 | (void) pack_cpio(loaded_image, |
f3b6f333 | 232 | NULL, |
845707aa | 233 | L".cred", |
07d0fde4 | 234 | ".extra/credentials", |
845707aa LP |
235 | /* dir_mode= */ 0500, |
236 | /* access_mode= */ 0400, | |
db4122d1 | 237 | /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, |
4d32507f | 238 | /* n_tpm_pcr= */ 2, |
845707aa LP |
239 | L"Credentials initrd", |
240 | &credential_initrd, | |
241 | &credential_initrd_size); | |
242 | ||
70cd15e9 | 243 | (void) pack_cpio(loaded_image, |
f3b6f333 AV |
244 | L"\\loader\\credentials", |
245 | L".cred", | |
07d0fde4 | 246 | ".extra/global_credentials", |
f3b6f333 AV |
247 | /* dir_mode= */ 0500, |
248 | /* access_mode= */ 0400, | |
db4122d1 | 249 | /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, |
4d32507f | 250 | /* n_tpm_pcr= */ 2, |
f3b6f333 AV |
251 | L"Global credentials initrd", |
252 | &global_credential_initrd, | |
253 | &global_credential_initrd_size); | |
254 | ||
255 | (void) pack_cpio(loaded_image, | |
256 | NULL, | |
845707aa | 257 | L".raw", |
07d0fde4 | 258 | ".extra/sysext", |
845707aa LP |
259 | /* dir_mode= */ 0555, |
260 | /* access_mode= */ 0444, | |
db4122d1 | 261 | /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_INITRD }, |
4d32507f | 262 | /* n_tpm_pcr= */ 1, |
845707aa LP |
263 | L"System extension initrd", |
264 | &sysext_initrd, | |
265 | &sysext_initrd_size); | |
266 | ||
dc467928 | 267 | linux_size = szs[SECTION_LINUX]; |
a0a644be | 268 | linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_LINUX]; |
5b5d365d LP |
269 | |
270 | initrd_size = szs[SECTION_INITRD]; | |
a0a644be | 271 | initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_INITRD] : 0; |
37fa3690 | 272 | |
33bc9b75 MR |
273 | dt_size = szs[SECTION_DTB]; |
274 | dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_DTB] : 0; | |
275 | ||
f3b6f333 | 276 | if (credential_initrd || global_credential_initrd || sysext_initrd) { |
845707aa LP |
277 | /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */ |
278 | err = combine_initrd( | |
279 | initrd_base, initrd_size, | |
280 | credential_initrd, credential_initrd_size, | |
f3b6f333 | 281 | global_credential_initrd, global_credential_initrd_size, |
845707aa LP |
282 | sysext_initrd, sysext_initrd_size, |
283 | &initrd_base, &initrd_size); | |
2a5e4fe4 | 284 | if (err != EFI_SUCCESS) |
845707aa LP |
285 | return err; |
286 | ||
287 | /* Given these might be large let's free them explicitly, quickly. */ | |
f3b6f333 AV |
288 | credential_initrd = mfree(credential_initrd); |
289 | global_credential_initrd = mfree(global_credential_initrd); | |
290 | sysext_initrd = mfree(sysext_initrd); | |
845707aa | 291 | } |
0fa2cac4 | 292 | |
33bc9b75 MR |
293 | if (dt_size > 0) { |
294 | err = devicetree_install_from_memory( | |
295 | &dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size); | |
2a5e4fe4 | 296 | if (err != EFI_SUCCESS) |
33bc9b75 MR |
297 | log_error_stall(L"Error loading embedded devicetree: %r", err); |
298 | } | |
299 | ||
a6089431 | 300 | err = linux_exec(image, cmdline, cmdline_len, |
dc467928 | 301 | PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size, |
a6089431 | 302 | PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); |
e5a1b8f9 | 303 | graphics_mode(false); |
8aba0eec | 304 | return log_error_status_stall(err, L"Execution of embedded linux image failed: %r", err); |
0fa2cac4 | 305 | } |