/src/systemd/src/shared/efi-api.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <unistd.h> |
4 | | |
5 | | #include "alloc-util.h" |
6 | | #include "dirent-util.h" |
7 | | #include "efi-api.h" |
8 | | #include "efi.h" |
9 | | #include "efivars.h" |
10 | | #include "fd-util.h" |
11 | | #include "fileio.h" |
12 | | #include "log.h" |
13 | | #include "parse-util.h" |
14 | | #include "sort-util.h" |
15 | | #include "stat-util.h" |
16 | | #include "stdio-util.h" |
17 | | #include "string-util.h" |
18 | | #include "tpm2-util.h" /* IWYU pragma: keep */ |
19 | | #include "utf8.h" |
20 | | |
21 | 0 | #define EFI_TCG2_BOOT_HASH_ALG_SHA1 0x01 |
22 | 0 | #define EFI_TCG2_BOOT_HASH_ALG_SHA256 0x02 |
23 | 0 | #define EFI_TCG2_BOOT_HASH_ALG_SHA384 0x04 |
24 | 0 | #define EFI_TCG2_BOOT_HASH_ALG_SHA512 0x08 |
25 | | |
26 | 0 | #define LOAD_OPTION_ACTIVE 0x00000001 |
27 | 0 | #define MEDIA_DEVICE_PATH 0x04 |
28 | 0 | #define MEDIA_HARDDRIVE_DP 0x01 |
29 | 0 | #define MEDIA_FILEPATH_DP 0x04 |
30 | 0 | #define SIGNATURE_TYPE_GUID 0x02 |
31 | 0 | #define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02 |
32 | 0 | #define END_DEVICE_PATH_TYPE 0x7f |
33 | 0 | #define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff |
34 | | |
35 | 0 | #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI UINT64_C(0x0000000000000001) |
36 | | |
37 | | #define boot_option__contents \ |
38 | | { \ |
39 | | uint32_t attr; \ |
40 | | uint16_t path_len; \ |
41 | | uint16_t title[]; \ |
42 | | } |
43 | | |
44 | | struct boot_option boot_option__contents; |
45 | | struct boot_option__packed boot_option__contents _packed_; |
46 | | assert_cc(offsetof(struct boot_option, title) == offsetof(struct boot_option__packed, title)); |
47 | | /* sizeof(struct boot_option) != sizeof(struct boot_option__packed), so |
48 | | * the *size* of the structure should not be used anywhere below. */ |
49 | | |
50 | | struct drive_path { |
51 | | uint32_t part_nr; |
52 | | uint64_t part_start; |
53 | | uint64_t part_size; |
54 | | char signature[16]; |
55 | | uint8_t mbr_type; |
56 | | uint8_t signature_type; |
57 | | } _packed_; |
58 | | |
59 | | #define device_path__contents \ |
60 | | { \ |
61 | | uint8_t type; \ |
62 | | uint8_t sub_type; \ |
63 | | uint16_t length; \ |
64 | | union { \ |
65 | | uint16_t path[0]; \ |
66 | | struct drive_path drive; \ |
67 | | }; \ |
68 | | } |
69 | | |
70 | | struct device_path device_path__contents; |
71 | | struct device_path__packed device_path__contents _packed_; |
72 | | assert_cc(sizeof(struct device_path) == sizeof(struct device_path__packed)); |
73 | | |
74 | | #if ENABLE_EFI |
75 | 0 | static int get_os_indications(uint64_t *ret) { |
76 | 0 | static struct stat cache_stat = {}; |
77 | 0 | _cleanup_free_ void *v = NULL; |
78 | 0 | static uint64_t cache; |
79 | 0 | struct stat new_stat; |
80 | 0 | size_t s; |
81 | 0 | int r; |
82 | |
|
83 | 0 | assert(ret); |
84 | | |
85 | | /* Let's verify general support first */ |
86 | 0 | r = efi_reboot_to_firmware_supported(); |
87 | 0 | if (r < 0) |
88 | 0 | return r; |
89 | | |
90 | | /* stat() the EFI variable, to see if the mtime changed. If it did we need to cache again. */ |
91 | 0 | if (stat(EFIVAR_PATH(EFI_GLOBAL_VARIABLE_STR("OsIndications")), &new_stat) < 0) { |
92 | 0 | if (errno != ENOENT) |
93 | 0 | return -errno; |
94 | | |
95 | | /* Doesn't exist? Then we can exit early (also see below) */ |
96 | 0 | *ret = 0; |
97 | 0 | return 0; |
98 | |
|
99 | 0 | } else if (stat_inode_unmodified(&new_stat, &cache_stat)) { |
100 | | /* inode didn't change, we can return the cached value */ |
101 | 0 | *ret = cache; |
102 | 0 | return 0; |
103 | 0 | } |
104 | | |
105 | 0 | r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("OsIndications"), NULL, &v, &s); |
106 | 0 | if (r == -ENOENT) { |
107 | | /* Some firmware implementations that do support OsIndications and report that with |
108 | | * OsIndicationsSupported will remove the OsIndications variable when it is unset. Let's |
109 | | * pretend it's 0 then, to hide this implementation detail. Note that this call will return |
110 | | * -ENOENT then only if the support for OsIndications is missing entirely, as determined by |
111 | | * efi_reboot_to_firmware_supported() above. */ |
112 | 0 | *ret = 0; |
113 | 0 | return 0; |
114 | 0 | } |
115 | 0 | if (r < 0) |
116 | 0 | return r; |
117 | 0 | if (s != sizeof(uint64_t)) |
118 | 0 | return -EINVAL; |
119 | | |
120 | 0 | cache_stat = new_stat; |
121 | 0 | *ret = cache = *(uint64_t *)v; |
122 | 0 | return 0; |
123 | 0 | } |
124 | | |
125 | 0 | static ssize_t utf16_size(const uint16_t *s, size_t buf_len_bytes) { |
126 | 0 | size_t l = 0; |
127 | | |
128 | | /* Returns the size of the string in bytes without the terminating two zero bytes */ |
129 | |
|
130 | 0 | while (l < buf_len_bytes / sizeof(uint16_t)) { |
131 | 0 | if (s[l] == 0) |
132 | 0 | return (l + 1) * sizeof(uint16_t); |
133 | 0 | l++; |
134 | 0 | } |
135 | | |
136 | 0 | return -EINVAL; /* The terminator was not found */ |
137 | 0 | } |
138 | | |
139 | 0 | static void to_utf16(uint16_t *dest, const char *src) { |
140 | 0 | int i; |
141 | |
|
142 | 0 | for (i = 0; src[i] != '\0'; i++) |
143 | 0 | dest[i] = src[i]; |
144 | 0 | dest[i] = '\0'; |
145 | 0 | } |
146 | | |
147 | 0 | static uint16_t *tilt_slashes(uint16_t *s) { |
148 | 0 | for (uint16_t *p = s; *p; p++) |
149 | 0 | if (*p == '/') |
150 | 0 | *p = '\\'; |
151 | |
|
152 | 0 | return s; |
153 | 0 | } |
154 | | |
155 | 0 | static int boot_id_hex(const char s[static 4]) { |
156 | 0 | int id = 0; |
157 | |
|
158 | 0 | assert(s); |
159 | |
|
160 | 0 | for (int i = 0; i < 4; i++) |
161 | 0 | if (s[i] >= '0' && s[i] <= '9') |
162 | 0 | id |= (s[i] - '0') << (3 - i) * 4; |
163 | 0 | else if (s[i] >= 'A' && s[i] <= 'F') |
164 | 0 | id |= (s[i] - 'A' + 10) << (3 - i) * 4; |
165 | 0 | else |
166 | 0 | return -EINVAL; |
167 | | |
168 | 0 | return id; |
169 | 0 | } |
170 | | #endif |
171 | | |
172 | 0 | int efi_reboot_to_firmware_supported(void) { |
173 | 0 | #if ENABLE_EFI |
174 | 0 | _cleanup_free_ void *v = NULL; |
175 | 0 | static int cache = -1; |
176 | 0 | uint64_t b; |
177 | 0 | size_t s; |
178 | 0 | int r; |
179 | |
|
180 | 0 | if (cache > 0) |
181 | 0 | return 0; |
182 | 0 | if (cache == 0) |
183 | 0 | return -EOPNOTSUPP; |
184 | | |
185 | 0 | if (!is_efi_boot()) |
186 | 0 | goto not_supported; |
187 | | |
188 | 0 | r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("OsIndicationsSupported"), NULL, &v, &s); |
189 | 0 | if (r == -ENOENT) |
190 | 0 | goto not_supported; /* variable doesn't exist? it's not supported then */ |
191 | 0 | if (r < 0) |
192 | 0 | return r; |
193 | 0 | if (s != sizeof(uint64_t)) |
194 | 0 | return -EINVAL; |
195 | | |
196 | 0 | b = *(uint64_t*) v; |
197 | 0 | if (!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) |
198 | 0 | goto not_supported; /* bit unset? it's not supported then */ |
199 | | |
200 | 0 | cache = 1; |
201 | 0 | return 0; |
202 | | |
203 | 0 | not_supported: |
204 | 0 | cache = 0; |
205 | 0 | return -EOPNOTSUPP; |
206 | | #else |
207 | | return -EOPNOTSUPP; |
208 | | #endif |
209 | 0 | } |
210 | | |
211 | 0 | int efi_get_reboot_to_firmware(void) { |
212 | 0 | #if ENABLE_EFI |
213 | 0 | int r; |
214 | 0 | uint64_t b; |
215 | |
|
216 | 0 | r = get_os_indications(&b); |
217 | 0 | if (r < 0) |
218 | 0 | return r; |
219 | | |
220 | 0 | return !!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI); |
221 | | #else |
222 | | return -EOPNOTSUPP; |
223 | | #endif |
224 | 0 | } |
225 | | |
226 | 0 | int efi_set_reboot_to_firmware(bool value) { |
227 | 0 | #if ENABLE_EFI |
228 | 0 | int r; |
229 | 0 | uint64_t b, b_new; |
230 | |
|
231 | 0 | r = get_os_indications(&b); |
232 | 0 | if (r < 0) |
233 | 0 | return r; |
234 | | |
235 | 0 | b_new = UPDATE_FLAG(b, EFI_OS_INDICATIONS_BOOT_TO_FW_UI, value); |
236 | | |
237 | | /* Avoid writing to efi vars store if we can due to firmware bugs. */ |
238 | 0 | if (b != b_new) |
239 | 0 | return efi_set_variable(EFI_GLOBAL_VARIABLE_STR("OsIndications"), &b_new, sizeof(uint64_t)); |
240 | | |
241 | 0 | return 0; |
242 | | #else |
243 | | return -EOPNOTSUPP; |
244 | | #endif |
245 | 0 | } |
246 | | |
247 | | int efi_get_boot_option( |
248 | | uint16_t id, |
249 | | char **ret_title, |
250 | | sd_id128_t *ret_part_uuid, |
251 | | char **ret_path, |
252 | 0 | bool *ret_active) { |
253 | 0 | #if ENABLE_EFI |
254 | 0 | char variable[STRLEN(EFI_GLOBAL_VARIABLE_STR("Boot")) + 4 + 1]; |
255 | 0 | _cleanup_free_ uint8_t *buf = NULL; |
256 | 0 | size_t l; |
257 | 0 | struct boot_option *header; |
258 | 0 | ssize_t title_size; |
259 | 0 | _cleanup_free_ char *s = NULL, *p = NULL; |
260 | 0 | sd_id128_t p_uuid = SD_ID128_NULL; |
261 | 0 | int r; |
262 | |
|
263 | 0 | if (!is_efi_boot()) |
264 | 0 | return -EOPNOTSUPP; |
265 | | |
266 | 0 | xsprintf(variable, EFI_GLOBAL_VARIABLE_STR("Boot%04X"), id); |
267 | 0 | r = efi_get_variable(variable, NULL, (void **)&buf, &l); |
268 | 0 | if (r < 0) |
269 | 0 | return r; |
270 | 0 | if (l < offsetof(struct boot_option, title)) |
271 | 0 | return -ENOENT; |
272 | | |
273 | 0 | header = (struct boot_option *)buf; |
274 | 0 | title_size = utf16_size(header->title, l - offsetof(struct boot_option, title)); |
275 | 0 | if (title_size < 0) |
276 | 0 | return title_size; |
277 | | |
278 | 0 | if (ret_title) { |
279 | 0 | s = utf16_to_utf8(header->title, title_size); |
280 | 0 | if (!s) |
281 | 0 | return -ENOMEM; |
282 | 0 | } |
283 | | |
284 | 0 | if (header->path_len > 0) { |
285 | 0 | uint8_t *dbuf; |
286 | 0 | size_t dnext, doff, path_len; |
287 | |
|
288 | 0 | doff = offsetof(struct boot_option, title) + title_size; |
289 | 0 | dbuf = buf + doff; |
290 | 0 | path_len = MIN((size_t) header->path_len, l - doff); |
291 | |
|
292 | 0 | dnext = 0; |
293 | 0 | while (dnext < path_len) { |
294 | 0 | struct device_path *dpath; |
295 | 0 | size_t remaining = path_len - dnext; |
296 | |
|
297 | 0 | if (remaining < offsetof(struct device_path, path)) |
298 | 0 | break; |
299 | | |
300 | 0 | dpath = (struct device_path *)(dbuf + dnext); |
301 | 0 | if (dpath->length < offsetof(struct device_path, path) || |
302 | 0 | dpath->length > remaining) |
303 | 0 | break; |
304 | | |
305 | | /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ |
306 | 0 | if (dpath->type == END_DEVICE_PATH_TYPE && dpath->sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE) |
307 | 0 | break; |
308 | | |
309 | 0 | dnext += dpath->length; |
310 | | |
311 | | /* Type 0x04 – Media Device Path */ |
312 | 0 | if (dpath->type != MEDIA_DEVICE_PATH) |
313 | 0 | continue; |
314 | | |
315 | | /* Sub-Type 1 – Hard Drive */ |
316 | 0 | if (dpath->sub_type == MEDIA_HARDDRIVE_DP) { |
317 | 0 | if (dpath->length < offsetof(struct device_path, drive) + sizeof(struct drive_path)) |
318 | 0 | break; |
319 | | |
320 | | /* 0x02 – GUID Partition Table */ |
321 | 0 | if (dpath->drive.mbr_type != MBR_TYPE_EFI_PARTITION_TABLE_HEADER) |
322 | 0 | continue; |
323 | | |
324 | | /* 0x02 – GUID signature */ |
325 | 0 | if (dpath->drive.signature_type != SIGNATURE_TYPE_GUID) |
326 | 0 | continue; |
327 | | |
328 | 0 | if (ret_part_uuid) |
329 | 0 | p_uuid = efi_guid_to_id128(dpath->drive.signature); |
330 | 0 | continue; |
331 | 0 | } |
332 | | |
333 | | /* Sub-Type 4 – File Path */ |
334 | 0 | if (dpath->sub_type == MEDIA_FILEPATH_DP && !p && ret_path) { |
335 | 0 | p = utf16_to_utf8(dpath->path, dpath->length - offsetof(struct device_path, path)); |
336 | 0 | if (!p) |
337 | 0 | return -ENOMEM; |
338 | | |
339 | 0 | efi_tilt_backslashes(p); |
340 | 0 | continue; |
341 | 0 | } |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | 0 | if (ret_title) |
346 | 0 | *ret_title = TAKE_PTR(s); |
347 | 0 | if (ret_part_uuid) |
348 | 0 | *ret_part_uuid = p_uuid; |
349 | 0 | if (ret_path) |
350 | 0 | *ret_path = TAKE_PTR(p); |
351 | 0 | if (ret_active) |
352 | 0 | *ret_active = header->attr & LOAD_OPTION_ACTIVE; |
353 | |
|
354 | 0 | return 0; |
355 | | #else |
356 | | return -EOPNOTSUPP; |
357 | | #endif |
358 | 0 | } |
359 | | |
360 | | int efi_add_boot_option( |
361 | | uint16_t id, |
362 | | const char *title, |
363 | | uint32_t part, |
364 | | uint64_t pstart, |
365 | | uint64_t psize, |
366 | | sd_id128_t part_uuid, |
367 | 0 | const char *path) { |
368 | 0 | #if ENABLE_EFI |
369 | 0 | size_t size, title_len, path_len; |
370 | 0 | _cleanup_free_ char *buf = NULL; |
371 | 0 | struct boot_option *option; |
372 | 0 | struct device_path *devicep; |
373 | 0 | char variable[STRLEN(EFI_GLOBAL_VARIABLE_STR("Boot")) + 4 + 1]; |
374 | |
|
375 | 0 | if (!is_efi_boot()) |
376 | 0 | return -EOPNOTSUPP; |
377 | | |
378 | 0 | title_len = (strlen(title)+1) * 2; |
379 | 0 | path_len = (strlen(path)+1) * 2; |
380 | |
|
381 | 0 | buf = malloc0(offsetof(struct boot_option, title) + title_len + |
382 | 0 | sizeof(struct drive_path) + |
383 | 0 | sizeof(struct device_path) + path_len); |
384 | 0 | if (!buf) |
385 | 0 | return -ENOMEM; |
386 | | |
387 | | /* header */ |
388 | 0 | option = (struct boot_option *)buf; |
389 | 0 | option->attr = LOAD_OPTION_ACTIVE; |
390 | 0 | option->path_len = offsetof(struct device_path, drive) + sizeof(struct drive_path) + |
391 | 0 | offsetof(struct device_path, path) + path_len + |
392 | 0 | offsetof(struct device_path, path); |
393 | 0 | to_utf16(option->title, title); |
394 | 0 | size = offsetof(struct boot_option, title) + title_len; |
395 | | |
396 | | /* partition info */ |
397 | 0 | devicep = (struct device_path *)(buf + size); |
398 | 0 | devicep->type = MEDIA_DEVICE_PATH; |
399 | 0 | devicep->sub_type = MEDIA_HARDDRIVE_DP; |
400 | 0 | devicep->length = offsetof(struct device_path, drive) + sizeof(struct drive_path); |
401 | 0 | memcpy(&devicep->drive.part_nr, &part, sizeof(uint32_t)); |
402 | 0 | memcpy(&devicep->drive.part_start, &pstart, sizeof(uint64_t)); |
403 | 0 | memcpy(&devicep->drive.part_size, &psize, sizeof(uint64_t)); |
404 | 0 | efi_id128_to_guid(part_uuid, devicep->drive.signature); |
405 | 0 | devicep->drive.mbr_type = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; |
406 | 0 | devicep->drive.signature_type = SIGNATURE_TYPE_GUID; |
407 | 0 | size += devicep->length; |
408 | | |
409 | | /* path to loader */ |
410 | 0 | devicep = (struct device_path *)(buf + size); |
411 | 0 | devicep->type = MEDIA_DEVICE_PATH; |
412 | 0 | devicep->sub_type = MEDIA_FILEPATH_DP; |
413 | 0 | devicep->length = offsetof(struct device_path, path) + path_len; |
414 | 0 | to_utf16(devicep->path, path); |
415 | 0 | tilt_slashes(devicep->path); |
416 | 0 | size += devicep->length; |
417 | | |
418 | | /* end of path */ |
419 | 0 | devicep = (struct device_path *)(buf + size); |
420 | 0 | devicep->type = END_DEVICE_PATH_TYPE; |
421 | 0 | devicep->sub_type = END_ENTIRE_DEVICE_PATH_SUBTYPE; |
422 | 0 | devicep->length = offsetof(struct device_path, path); |
423 | 0 | size += devicep->length; |
424 | |
|
425 | 0 | xsprintf(variable, EFI_GLOBAL_VARIABLE_STR("Boot%04X"), id); |
426 | 0 | return efi_set_variable(variable, buf, size); |
427 | | #else |
428 | | return -EOPNOTSUPP; |
429 | | #endif |
430 | 0 | } |
431 | | |
432 | 0 | int efi_remove_boot_option(uint16_t id) { |
433 | 0 | #if ENABLE_EFI |
434 | 0 | char variable[STRLEN(EFI_GLOBAL_VARIABLE_STR("Boot")) + 4 + 1]; |
435 | |
|
436 | 0 | if (!is_efi_boot()) |
437 | 0 | return -EOPNOTSUPP; |
438 | | |
439 | 0 | xsprintf(variable, EFI_GLOBAL_VARIABLE_STR("Boot%04X"), id); |
440 | 0 | return efi_set_variable(variable, NULL, 0); |
441 | | #else |
442 | | return -EOPNOTSUPP; |
443 | | #endif |
444 | 0 | } |
445 | | |
446 | 0 | int efi_get_boot_order(uint16_t **ret_order) { |
447 | 0 | #if ENABLE_EFI |
448 | 0 | _cleanup_free_ void *buf = NULL; |
449 | 0 | size_t l; |
450 | 0 | int r; |
451 | |
|
452 | 0 | assert(ret_order); |
453 | |
|
454 | 0 | if (!is_efi_boot()) |
455 | 0 | return -EOPNOTSUPP; |
456 | | |
457 | 0 | r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("BootOrder"), NULL, &buf, &l); |
458 | 0 | if (r < 0) |
459 | 0 | return r; |
460 | | |
461 | 0 | if (l <= 0) |
462 | 0 | return -ENOENT; |
463 | | |
464 | 0 | if (l % sizeof(uint16_t) > 0 || |
465 | 0 | l / sizeof(uint16_t) > INT_MAX) |
466 | 0 | return -EINVAL; |
467 | | |
468 | 0 | *ret_order = TAKE_PTR(buf); |
469 | 0 | return (int) (l / sizeof(uint16_t)); |
470 | | #else |
471 | | return -EOPNOTSUPP; |
472 | | #endif |
473 | 0 | } |
474 | | |
475 | 0 | int efi_set_boot_order(const uint16_t *order, size_t n) { |
476 | 0 | #if ENABLE_EFI |
477 | 0 | if (!is_efi_boot()) |
478 | 0 | return -EOPNOTSUPP; |
479 | | |
480 | 0 | return efi_set_variable(EFI_GLOBAL_VARIABLE_STR("BootOrder"), order, n * sizeof(uint16_t)); |
481 | | #else |
482 | | return -EOPNOTSUPP; |
483 | | #endif |
484 | 0 | } |
485 | | |
486 | 0 | int efi_get_boot_options(uint16_t **ret_options) { |
487 | 0 | #if ENABLE_EFI |
488 | 0 | _cleanup_closedir_ DIR *dir = NULL; |
489 | 0 | _cleanup_free_ uint16_t *list = NULL; |
490 | 0 | int count = 0; |
491 | |
|
492 | 0 | assert(ret_options); |
493 | |
|
494 | 0 | if (!is_efi_boot()) |
495 | 0 | return -EOPNOTSUPP; |
496 | | |
497 | 0 | dir = opendir(EFIVAR_PATH(".")); |
498 | 0 | if (!dir) |
499 | 0 | return -errno; |
500 | | |
501 | 0 | FOREACH_DIRENT(de, dir, return -errno) { |
502 | 0 | int id; |
503 | |
|
504 | 0 | if (!startswith(de->d_name, "Boot")) |
505 | 0 | continue; |
506 | | |
507 | 0 | if (strlen(de->d_name) != 45) |
508 | 0 | continue; |
509 | | |
510 | 0 | if (!streq(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR(""))) /* generate variable suffix using macro */ |
511 | 0 | continue; |
512 | | |
513 | 0 | id = boot_id_hex(de->d_name + 4); |
514 | 0 | if (id < 0) |
515 | 0 | continue; |
516 | | |
517 | 0 | if (!GREEDY_REALLOC(list, count + 1)) |
518 | 0 | return -ENOMEM; |
519 | | |
520 | 0 | list[count++] = id; |
521 | 0 | } |
522 | | |
523 | 0 | typesafe_qsort(list, count, cmp_uint16); |
524 | |
|
525 | 0 | *ret_options = TAKE_PTR(list); |
526 | |
|
527 | 0 | return count; |
528 | | #else |
529 | | return -EOPNOTSUPP; |
530 | | #endif |
531 | 0 | } |
532 | | |
533 | 0 | int efi_get_active_pcr_banks(uint32_t *ret) { |
534 | 0 | #if ENABLE_EFI |
535 | 0 | static uint32_t cache = 0; |
536 | 0 | static bool cache_valid = false; |
537 | 0 | int r; |
538 | | |
539 | | /* Returns the enabled PCR banks as bitmask, as reported by firmware. If the bitmask is returned as |
540 | | * UINT32_MAX, the firmware supports the TCG protocol, but in a version too old to report this |
541 | | * information. */ |
542 | |
|
543 | 0 | if (!cache_valid) { |
544 | 0 | _cleanup_free_ char *active_pcr_banks = NULL; |
545 | 0 | r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderTpm2ActivePcrBanks"), &active_pcr_banks); |
546 | 0 | if (r < 0) |
547 | 0 | return log_debug_errno(r, "Failed to read LoaderTpm2ActivePcrBanks variable: %m"); |
548 | | |
549 | 0 | uint32_t efi_bits; |
550 | 0 | r = safe_atou32_full(active_pcr_banks, 16, &efi_bits); |
551 | 0 | if (r < 0) |
552 | 0 | return log_debug_errno(r, "Failed to parse LoaderTpm2ActivePcrBanks variable: %m"); |
553 | | |
554 | 0 | if (efi_bits == UINT32_MAX) |
555 | | /* UINT32_MAX means that the firmware API doesn't implement GetActivePcrBanks() and caller must guess */ |
556 | 0 | cache = UINT32_MAX; |
557 | 0 | else { |
558 | | /* EFI TPM protocol uses different bit values for the hash algorithms, let's convert */ |
559 | 0 | static const struct { |
560 | 0 | uint32_t efi; |
561 | 0 | uint32_t tcg; |
562 | 0 | } table[] = { |
563 | 0 | { EFI_TCG2_BOOT_HASH_ALG_SHA1, 1U << TPM2_ALG_SHA1 }, |
564 | 0 | { EFI_TCG2_BOOT_HASH_ALG_SHA256, 1U << TPM2_ALG_SHA256 }, |
565 | 0 | { EFI_TCG2_BOOT_HASH_ALG_SHA384, 1U << TPM2_ALG_SHA384 }, |
566 | 0 | { EFI_TCG2_BOOT_HASH_ALG_SHA512, 1U << TPM2_ALG_SHA512 }, |
567 | 0 | }; |
568 | |
|
569 | 0 | uint32_t tcg_bits = 0; |
570 | 0 | FOREACH_ELEMENT(t, table) |
571 | 0 | SET_FLAG(tcg_bits, t->tcg, efi_bits & t->efi); |
572 | |
|
573 | 0 | cache = tcg_bits; |
574 | 0 | } |
575 | |
|
576 | 0 | cache_valid = true; |
577 | 0 | } |
578 | | |
579 | 0 | if (ret) |
580 | 0 | *ret = cache; |
581 | |
|
582 | 0 | return 0; |
583 | | #else |
584 | | return -EOPNOTSUPP; |
585 | | #endif |
586 | 0 | } |
587 | | |
588 | | #if ENABLE_EFI |
589 | 0 | static int loader_has_tpm2(void) { |
590 | 0 | uint32_t active_pcr_banks; |
591 | 0 | int r; |
592 | |
|
593 | 0 | r = efi_get_active_pcr_banks(&active_pcr_banks); |
594 | 0 | if (r < 0) |
595 | 0 | return r; |
596 | | |
597 | 0 | return active_pcr_banks != 0; |
598 | 0 | } |
599 | | #endif |
600 | | |
601 | 0 | bool efi_has_tpm2(void) { |
602 | 0 | #if ENABLE_EFI |
603 | 0 | static int cache = -1; |
604 | 0 | int r; |
605 | | |
606 | | /* Returns whether the system has a TPM2 chip which is known to the EFI firmware. */ |
607 | |
|
608 | 0 | if (cache >= 0) |
609 | 0 | return cache; |
610 | | |
611 | | /* First, check if we are on an EFI boot at all. */ |
612 | 0 | if (!is_efi_boot()) |
613 | 0 | return (cache = false); |
614 | | |
615 | | /* Secondly, check if the loader told us, as that is the most accurate source of information |
616 | | * regarding the firmware's setup */ |
617 | 0 | r = loader_has_tpm2(); |
618 | 0 | if (r >= 0) |
619 | 0 | return (cache = r); |
620 | | |
621 | | /* Then, check if the ACPI table "TPM2" exists, which is the TPM2 event log table, see: |
622 | | * https://trustedcomputinggroup.org/wp-content/uploads/TCG_ACPIGeneralSpecification_v1.20_r8.pdf |
623 | | * This table exists whenever the firmware knows ACPI and is hooked up to TPM2. |
624 | | * Note that in some cases, for example with EDK2 2025.2 with the default arm64 config, this ACPI |
625 | | * table is present even if TPM2 support is not enabled in the firmware. */ |
626 | 0 | if (access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0) |
627 | 0 | return (cache = true); |
628 | 0 | if (errno != ENOENT) |
629 | 0 | log_debug_errno(errno, "Unable to test whether /sys/firmware/acpi/tables/TPM2 exists, assuming it doesn't: %m"); |
630 | | |
631 | | /* As the last try, check if the EFI firmware provides the EFI_TCG2_FINAL_EVENTS_TABLE |
632 | | * stored in EFI configuration table, see: |
633 | | * |
634 | | * https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf */ |
635 | 0 | if (access("/sys/kernel/security/tpm0/binary_bios_measurements", F_OK) >= 0) { |
636 | 0 | _cleanup_free_ char *major = NULL; |
637 | | |
638 | | /* The EFI table might exist for TPM 1.2 as well, hence let's check explicitly which TPM version we are looking at here. */ |
639 | 0 | r = read_virtual_file("/sys/class/tpm/tpm0/tpm_version_major", SIZE_MAX, &major, /* ret_size= */ NULL); |
640 | 0 | if (r >= 0) |
641 | 0 | return (cache = streq(strstrip(major), "2")); |
642 | | |
643 | 0 | log_debug_errno(r, "Unable to read /sys/class/tpm/tpm0/tpm_version_major, assuming TPM does not qualify as TPM2: %m"); |
644 | |
|
645 | 0 | } else if (errno != ENOENT) |
646 | 0 | log_debug_errno(errno, "Unable to test whether /sys/kernel/security/tpm0/binary_bios_measurements exists, assuming it doesn't: %m"); |
647 | | |
648 | 0 | return (cache = false); |
649 | | #else |
650 | | return -EOPNOTSUPP; |
651 | | #endif |
652 | 0 | } |
653 | | |
654 | 0 | sd_id128_t efi_guid_to_id128(const void *guid) { |
655 | 0 | EFI_GUID uuid; |
656 | 0 | sd_id128_t id128; |
657 | | |
658 | | /* The input pointer is not guaranteed to be aligned (e.g. when pointing into a serialized EFI |
659 | | * variable buffer), so copy into a properly aligned local first. */ |
660 | 0 | memcpy(&uuid, ASSERT_PTR(guid), sizeof(uuid)); |
661 | |
|
662 | 0 | id128.bytes[0] = (uuid.Data1 >> 24) & 0xff; |
663 | 0 | id128.bytes[1] = (uuid.Data1 >> 16) & 0xff; |
664 | 0 | id128.bytes[2] = (uuid.Data1 >> 8) & 0xff; |
665 | 0 | id128.bytes[3] = uuid.Data1 & 0xff; |
666 | |
|
667 | 0 | id128.bytes[4] = (uuid.Data2 >> 8) & 0xff; |
668 | 0 | id128.bytes[5] = uuid.Data2 & 0xff; |
669 | |
|
670 | 0 | id128.bytes[6] = (uuid.Data3 >> 8) & 0xff; |
671 | 0 | id128.bytes[7] = uuid.Data3 & 0xff; |
672 | |
|
673 | 0 | memcpy(&id128.bytes[8], uuid.Data4, sizeof(uuid.Data4)); |
674 | |
|
675 | 0 | return id128; |
676 | 0 | } |
677 | | |
678 | 0 | void efi_id128_to_guid(sd_id128_t id, void *ret_guid) { |
679 | 0 | assert(ret_guid); |
680 | |
|
681 | 0 | EFI_GUID uuid = { |
682 | 0 | .Data1 = (uint32_t) id.bytes[0] << 24 | (uint32_t) id.bytes[1] << 16 | (uint32_t) id.bytes[2] << 8 | id.bytes[3], |
683 | 0 | .Data2 = (uint16_t) id.bytes[4] << 8 | id.bytes[5], |
684 | 0 | .Data3 = (uint16_t) id.bytes[6] << 8 | id.bytes[7], |
685 | 0 | }; |
686 | 0 | memcpy(uuid.Data4, id.bytes+8, sizeof(uuid.Data4)); |
687 | 0 | memcpy(ret_guid, &uuid, sizeof(uuid)); |
688 | 0 | } |