]>
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 | 14 | #include "splash.h" |
aa323c09 | 15 | #include "tpm-pcr.h" |
cf0fbc49 | 16 | #include "util.h" |
0fa2cac4 KS |
17 | |
18 | /* magic string to find in the binary image */ | |
2ccd5986 | 19 | _used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####"; |
0fa2cac4 | 20 | |
845707aa LP |
21 | static EFI_STATUS combine_initrd( |
22 | EFI_PHYSICAL_ADDRESS initrd_base, UINTN initrd_size, | |
70cd15e9 | 23 | const void *credential_initrd, UINTN credential_initrd_size, |
f3b6f333 | 24 | const void *global_credential_initrd, UINTN global_credential_initrd_size, |
70cd15e9 | 25 | const void *sysext_initrd, UINTN sysext_initrd_size, |
845707aa LP |
26 | EFI_PHYSICAL_ADDRESS *ret_initrd_base, UINTN *ret_initrd_size) { |
27 | ||
28 | EFI_PHYSICAL_ADDRESS base = UINT32_MAX; /* allocate an area below the 32bit boundary for this */ | |
29 | EFI_STATUS err; | |
db4122d1 | 30 | uint8_t *p; |
845707aa LP |
31 | UINTN n; |
32 | ||
33 | assert(ret_initrd_base); | |
34 | assert(ret_initrd_size); | |
35 | ||
f3b6f333 | 36 | /* Combines four initrds into one, by simple concatenation in memory */ |
845707aa | 37 | |
b4e7df4a | 38 | n = ALIGN4(initrd_size); /* main initrd might not be padded yet */ |
845707aa LP |
39 | if (credential_initrd) { |
40 | if (n > UINTN_MAX - credential_initrd_size) | |
41 | return EFI_OUT_OF_RESOURCES; | |
42 | ||
43 | n += credential_initrd_size; | |
44 | } | |
f3b6f333 AV |
45 | if (global_credential_initrd) { |
46 | if (n > UINTN_MAX - global_credential_initrd_size) | |
47 | return EFI_OUT_OF_RESOURCES; | |
48 | ||
49 | n += global_credential_initrd_size; | |
50 | } | |
845707aa LP |
51 | if (sysext_initrd) { |
52 | if (n > UINTN_MAX - sysext_initrd_size) | |
53 | return EFI_OUT_OF_RESOURCES; | |
54 | ||
55 | n += sysext_initrd_size; | |
56 | } | |
57 | ||
12f32748 | 58 | err = BS->AllocatePages( |
845707aa LP |
59 | AllocateMaxAddress, |
60 | EfiLoaderData, | |
61 | EFI_SIZE_TO_PAGES(n), | |
62 | &base); | |
2a5e4fe4 | 63 | if (err != EFI_SUCCESS) |
845707aa LP |
64 | return log_error_status_stall(err, L"Failed to allocate space for combined initrd: %r", err); |
65 | ||
a0a644be | 66 | p = PHYSICAL_ADDRESS_TO_POINTER(base); |
845707aa LP |
67 | if (initrd_base != 0) { |
68 | UINTN pad; | |
69 | ||
70 | /* Order matters, the real initrd must come first, since it might include microcode updates | |
71 | * which the kernel only looks for in the first cpio archive */ | |
515581d6 | 72 | p = mempcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); |
845707aa | 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 | ||
515581d6 JJ |
81 | if (credential_initrd) |
82 | p = mempcpy(p, credential_initrd, credential_initrd_size); | |
83 | if (global_credential_initrd) | |
84 | p = mempcpy(p, global_credential_initrd, global_credential_initrd_size); | |
85 | if (sysext_initrd) | |
86 | p = mempcpy(p, sysext_initrd, sysext_initrd_size); | |
845707aa | 87 | |
db4122d1 | 88 | assert((uint8_t*) PHYSICAL_ADDRESS_TO_POINTER(base) + n == p); |
845707aa LP |
89 | |
90 | *ret_initrd_base = base; | |
91 | *ret_initrd_size = n; | |
92 | ||
93 | return EFI_SUCCESS; | |
94 | } | |
95 | ||
b30a43df | 96 | static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { |
46d33672 LP |
97 | static const uint64_t stub_features = |
98 | EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */ | |
99 | EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */ | |
100 | EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */ | |
101 | EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */ | |
102 | 0; | |
103 | ||
3639d1b0 | 104 | char16_t uuid[37]; |
5a186322 LP |
105 | |
106 | assert(loaded_image); | |
107 | ||
108 | /* Export the device path this image is started from, if it's not set yet */ | |
109 | if (efivar_get_raw(LOADER_GUID, L"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) | |
110 | if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) | |
111 | efivar_set(LOADER_GUID, L"LoaderDevicePartUUID", uuid, 0); | |
112 | ||
113 | /* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the | |
114 | * UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note | |
115 | * that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us, | |
116 | * in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong | |
117 | * here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath | |
118 | * is non-NULL explicitly.) */ | |
119 | if (efivar_get_raw(LOADER_GUID, L"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS && | |
120 | loaded_image->FilePath) { | |
3639d1b0 | 121 | _cleanup_free_ char16_t *s = NULL; |
5a186322 LP |
122 | |
123 | s = DevicePathToStr(loaded_image->FilePath); | |
9f048123 JJ |
124 | if (s) |
125 | efivar_set(LOADER_GUID, L"LoaderImageIdentifier", s, 0); | |
126 | else | |
127 | log_oom(); | |
5a186322 LP |
128 | } |
129 | ||
130 | /* if LoaderFirmwareInfo is not set, let's set it */ | |
131 | if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) { | |
3639d1b0 | 132 | _cleanup_free_ char16_t *s = NULL; |
ec4106af | 133 | s = xpool_print(L"%s %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); |
0a15a824 | 134 | efivar_set(LOADER_GUID, L"LoaderFirmwareInfo", s, 0); |
5a186322 LP |
135 | } |
136 | ||
137 | /* ditto for LoaderFirmwareType */ | |
138 | if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) { | |
3639d1b0 | 139 | _cleanup_free_ char16_t *s = NULL; |
ec4106af | 140 | s = xpool_print(L"UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); |
0a15a824 | 141 | efivar_set(LOADER_GUID, L"LoaderFirmwareType", s, 0); |
5a186322 LP |
142 | } |
143 | ||
46d33672 | 144 | |
24120e40 LP |
145 | /* add StubInfo (this is one is owned by the stub, hence we unconditionally override this with our |
146 | * own data) */ | |
147 | (void) efivar_set(LOADER_GUID, L"StubInfo", L"systemd-stub " GIT_VERSION, 0); | |
46d33672 LP |
148 | |
149 | (void) efivar_set_uint64_le(LOADER_GUID, L"StubFeatures", stub_features, 0); | |
5a186322 LP |
150 | } |
151 | ||
0fa2cac4 | 152 | EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { |
33bc9b75 | 153 | UINTN cmdline_len = 0, linux_size, initrd_size, dt_size; |
f3b6f333 | 154 | UINTN credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0; |
93521e55 JJ |
155 | _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL; |
156 | _cleanup_free_ void *sysext_initrd = NULL; | |
33bc9b75 MR |
157 | EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base; |
158 | _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; | |
b30a43df | 159 | EFI_LOADED_IMAGE_PROTOCOL *loaded_image; |
6017eee9 | 160 | UINTN addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {}; |
07d0fde4 JJ |
161 | char *cmdline = NULL; |
162 | _cleanup_free_ char *cmdline_owned = NULL; | |
72c97c19 | 163 | int sections_measured = -1, parameters_measured = -1; |
de7ad6d4 | 164 | bool sysext_measured = false, m; |
0fa2cac4 KS |
165 | EFI_STATUS err; |
166 | ||
167 | InitializeLib(image, sys_table); | |
948d085e JJ |
168 | debug_hook(L"systemd-stub"); |
169 | /* Uncomment the next line if you need to wait for debugger. */ | |
170 | // debug_break(); | |
0fa2cac4 | 171 | |
12f32748 JJ |
172 | err = BS->OpenProtocol( |
173 | image, | |
174 | &LoadedImageProtocol, | |
175 | (void **)&loaded_image, | |
176 | image, | |
177 | NULL, | |
178 | EFI_OPEN_PROTOCOL_GET_PROTOCOL); | |
2a5e4fe4 | 179 | if (err != EFI_SUCCESS) |
8aba0eec | 180 | return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err); |
0fa2cac4 | 181 | |
6017eee9 LP |
182 | err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, addrs, szs); |
183 | if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) { | |
2a5e4fe4 | 184 | if (err == EFI_SUCCESS) |
65ff3d26 | 185 | err = EFI_NOT_FOUND; |
8aba0eec | 186 | return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err); |
65ff3d26 | 187 | } |
0fa2cac4 | 188 | |
72c97c19 LP |
189 | /* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written |
190 | * into so far), so that we have one PCR that we can nicely write policies against because it | |
191 | * contains all static data of this image, and thus can be easily be pre-calculated. */ | |
6017eee9 | 192 | for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { |
df7ee6f8 LP |
193 | |
194 | if (!unified_section_measure(section)) /* shall not measure? */ | |
195 | continue; | |
72c97c19 LP |
196 | |
197 | if (szs[section] == 0) /* not found */ | |
198 | continue; | |
199 | ||
df7ee6f8 LP |
200 | m = false; |
201 | ||
72c97c19 LP |
202 | /* First measure the name of the section */ |
203 | (void) tpm_log_event_ascii( | |
204 | TPM_PCR_INDEX_KERNEL_IMAGE, | |
6017eee9 LP |
205 | POINTER_TO_PHYSICAL_ADDRESS(unified_sections[section]), |
206 | strsize8(unified_sections[section]), /* including NUL byte */ | |
207 | unified_sections[section], | |
72c97c19 LP |
208 | &m); |
209 | ||
210 | sections_measured = sections_measured < 0 ? m : (sections_measured && m); | |
211 | ||
212 | /* Then measure the data of the section */ | |
213 | (void) tpm_log_event_ascii( | |
214 | TPM_PCR_INDEX_KERNEL_IMAGE, | |
215 | POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section], | |
216 | szs[section], | |
6017eee9 | 217 | unified_sections[section], |
72c97c19 LP |
218 | &m); |
219 | ||
220 | sections_measured = sections_measured < 0 ? m : (sections_measured && m); | |
221 | } | |
222 | ||
223 | /* After we are done, set an EFI variable that tells userspace this was done successfully, and encode | |
224 | * in it which PCR was used. */ | |
225 | if (sections_measured > 0) | |
226 | (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelImage", TPM_PCR_INDEX_KERNEL_IMAGE, 0); | |
227 | ||
94b81afb | 228 | /* Show splash screen as early as possible */ |
6017eee9 | 229 | graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH], NULL); |
94b81afb | 230 | |
6017eee9 LP |
231 | if (szs[UNIFIED_SECTION_CMDLINE] > 0) { |
232 | cmdline = (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE]; | |
233 | cmdline_len = szs[UNIFIED_SECTION_CMDLINE]; | |
e41d3d89 | 234 | } |
0fa2cac4 | 235 | |
03d5449f | 236 | /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace |
5c19169f | 237 | * the built-in one. We also do a superficial check whether first character of passed command line |
03d5449f LP |
238 | * is printable character (for compat with some Dell systems which fill in garbage?). */ |
239 | if ((!secure_boot_enabled() || cmdline_len == 0) && | |
240 | loaded_image->LoadOptionsSize > 0 && | |
241 | ((char16_t *) loaded_image->LoadOptions)[0] > 0x1F) { | |
07d0fde4 | 242 | cmdline_len = (loaded_image->LoadOptionsSize / sizeof(char16_t)) * sizeof(char); |
03d5449f | 243 | cmdline = cmdline_owned = xnew(char, cmdline_len); |
9f048123 | 244 | |
03d5449f LP |
245 | for (UINTN i = 0; i < cmdline_len; i++) { |
246 | char16_t c = ((char16_t *) loaded_image->LoadOptions)[i]; | |
247 | cmdline[i] = c > 0x1F && c < 0x7F ? c : ' '; /* convert non-printable and non_ASCII characters to spaces. */ | |
248 | } | |
92ed3bb4 | 249 | |
e6e24af5 LP |
250 | /* Let's measure the passed kernel command line into the TPM. Note that this possibly |
251 | * duplicates what we already did in the boot menu, if that was already used. However, since | |
252 | * we want the boot menu to support an EFI binary, and want to this stub to be usable from | |
253 | * any boot menu, let's measure things anyway. */ | |
599fe002 LP |
254 | m = false; |
255 | (void) tpm_log_load_options(loaded_image->LoadOptions, &m); | |
256 | parameters_measured = m; | |
0fa2cac4 KS |
257 | } |
258 | ||
5a186322 | 259 | export_variables(loaded_image); |
1aa15def | 260 | |
599fe002 LP |
261 | if (pack_cpio(loaded_image, |
262 | NULL, | |
263 | L".cred", | |
264 | ".extra/credentials", | |
265 | /* dir_mode= */ 0500, | |
266 | /* access_mode= */ 0400, | |
267 | /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, | |
268 | /* n_tpm_pcr= */ 2, | |
269 | L"Credentials initrd", | |
270 | &credential_initrd, | |
271 | &credential_initrd_size, | |
272 | &m) == EFI_SUCCESS) | |
273 | parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); | |
274 | ||
275 | if (pack_cpio(loaded_image, | |
276 | L"\\loader\\credentials", | |
277 | L".cred", | |
278 | ".extra/global_credentials", | |
279 | /* dir_mode= */ 0500, | |
280 | /* access_mode= */ 0400, | |
281 | /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, | |
282 | /* n_tpm_pcr= */ 2, | |
283 | L"Global credentials initrd", | |
284 | &global_credential_initrd, | |
285 | &global_credential_initrd_size, | |
286 | &m) == EFI_SUCCESS) | |
287 | parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); | |
f3b6f333 | 288 | |
de7ad6d4 LP |
289 | if (pack_cpio(loaded_image, |
290 | NULL, | |
291 | L".raw", | |
292 | ".extra/sysext", | |
293 | /* dir_mode= */ 0555, | |
294 | /* access_mode= */ 0444, | |
295 | /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_INITRD_SYSEXTS }, | |
296 | /* n_tpm_pcr= */ 1, | |
297 | L"System extension initrd", | |
298 | &sysext_initrd, | |
299 | &sysext_initrd_size, | |
300 | &m) == EFI_SUCCESS) | |
301 | sysext_measured = m; | |
599fe002 LP |
302 | |
303 | if (parameters_measured > 0) | |
304 | (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelParameters", TPM_PCR_INDEX_KERNEL_PARAMETERS, 0); | |
de7ad6d4 LP |
305 | if (sysext_measured) |
306 | (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrInitRDSysExts", TPM_PCR_INDEX_INITRD_SYSEXTS, 0); | |
845707aa | 307 | |
6017eee9 LP |
308 | linux_size = szs[UNIFIED_SECTION_LINUX]; |
309 | linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX]; | |
5b5d365d | 310 | |
6017eee9 LP |
311 | initrd_size = szs[UNIFIED_SECTION_INITRD]; |
312 | initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0; | |
37fa3690 | 313 | |
6017eee9 LP |
314 | dt_size = szs[UNIFIED_SECTION_DTB]; |
315 | dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0; | |
33bc9b75 | 316 | |
f3b6f333 | 317 | if (credential_initrd || global_credential_initrd || sysext_initrd) { |
845707aa LP |
318 | /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */ |
319 | err = combine_initrd( | |
320 | initrd_base, initrd_size, | |
321 | credential_initrd, credential_initrd_size, | |
f3b6f333 | 322 | global_credential_initrd, global_credential_initrd_size, |
845707aa LP |
323 | sysext_initrd, sysext_initrd_size, |
324 | &initrd_base, &initrd_size); | |
2a5e4fe4 | 325 | if (err != EFI_SUCCESS) |
845707aa LP |
326 | return err; |
327 | ||
328 | /* Given these might be large let's free them explicitly, quickly. */ | |
f3b6f333 AV |
329 | credential_initrd = mfree(credential_initrd); |
330 | global_credential_initrd = mfree(global_credential_initrd); | |
331 | sysext_initrd = mfree(sysext_initrd); | |
845707aa | 332 | } |
0fa2cac4 | 333 | |
33bc9b75 MR |
334 | if (dt_size > 0) { |
335 | err = devicetree_install_from_memory( | |
336 | &dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size); | |
2a5e4fe4 | 337 | if (err != EFI_SUCCESS) |
33bc9b75 MR |
338 | log_error_stall(L"Error loading embedded devicetree: %r", err); |
339 | } | |
340 | ||
a6089431 | 341 | err = linux_exec(image, cmdline, cmdline_len, |
dc467928 | 342 | PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size, |
a6089431 | 343 | PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); |
e5a1b8f9 | 344 | graphics_mode(false); |
8aba0eec | 345 | return log_error_status_stall(err, L"Execution of embedded linux image failed: %r", err); |
0fa2cac4 | 346 | } |