]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/bootspec.c
tree-wide: be more careful with the type of array sizes
[thirdparty/systemd.git] / src / shared / bootspec.c
CommitLineData
7e87c7d9
ZJS
1/***
2 This file is part of systemd.
3
4 Copyright 2017 Zbigniew Jędrzejewski-Szmek
7e87c7d9
ZJS
5***/
6
7#include <stdio.h>
af918182 8#include <linux/magic.h>
7e87c7d9 9
dccca82b
LP
10#include "sd-id128.h"
11
7e87c7d9 12#include "alloc-util.h"
af918182 13#include "blkid-util.h"
7e87c7d9
ZJS
14#include "bootspec.h"
15#include "conf-files.h"
16#include "def.h"
c67f84b0 17#include "device-nodes.h"
7e87c7d9
ZJS
18#include "efivars.h"
19#include "fd-util.h"
20#include "fileio.h"
af918182
ZJS
21#include "parse-util.h"
22#include "stat-util.h"
7e87c7d9
ZJS
23#include "string-util.h"
24#include "strv.h"
af918182 25#include "virt.h"
7e87c7d9
ZJS
26
27void boot_entry_free(BootEntry *entry) {
4fe2ba0e 28 assert(entry);
7e87c7d9 29
4fe2ba0e 30 free(entry->filename);
7e87c7d9 31 free(entry->title);
64f05708 32 free(entry->show_title);
7e87c7d9
ZJS
33 free(entry->version);
34 free(entry->machine_id);
35 free(entry->architecture);
36 strv_free(entry->options);
37 free(entry->kernel);
38 free(entry->efi);
39 strv_free(entry->initrd);
40 free(entry->device_tree);
41}
42
43int boot_entry_load(const char *path, BootEntry *entry) {
263195c6 44 _cleanup_(boot_entry_free) BootEntry tmp = {};
7e87c7d9
ZJS
45 _cleanup_fclose_ FILE *f = NULL;
46 unsigned line = 1;
263195c6 47 char *b, *c;
7e87c7d9
ZJS
48 int r;
49
4fe2ba0e
LP
50 assert(path);
51 assert(entry);
52
263195c6
YW
53 c = endswith_no_case(path, ".conf");
54 if (!c) {
55 log_error("Invalid loader entry filename: %s", path);
56 return -EINVAL;
57 }
7e87c7d9 58
263195c6
YW
59 b = basename(path);
60 tmp.filename = strndup(b, c - b);
7e87c7d9
ZJS
61 if (!tmp.filename)
62 return log_oom();
63
263195c6
YW
64 f = fopen(path, "re");
65 if (!f)
66 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
67
7e87c7d9 68 for (;;) {
f99fdc3e
YW
69 _cleanup_free_ char *buf = NULL, *field = NULL;
70 const char *p;
7e87c7d9
ZJS
71
72 r = read_line(f, LONG_LINE_MAX, &buf);
73 if (r == 0)
74 break;
75 if (r == -ENOBUFS)
76 return log_error_errno(r, "%s:%u: Line too long", path, line);
77 if (r < 0)
78 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
79
80 line++;
81
82 if (IN_SET(*strstrip(buf), '#', '\0'))
83 continue;
84
f99fdc3e
YW
85 p = buf;
86 r = extract_first_word(&p, &field, " \t", 0);
87 if (r < 0) {
88 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
89 continue;
90 }
91 if (r == 0) {
7e87c7d9
ZJS
92 log_warning("%s:%u: Bad syntax", path, line);
93 continue;
94 }
7e87c7d9 95
f99fdc3e 96 if (streq(field, "title"))
7e87c7d9 97 r = free_and_strdup(&tmp.title, p);
f99fdc3e 98 else if (streq(field, "version"))
7e87c7d9 99 r = free_and_strdup(&tmp.version, p);
f99fdc3e 100 else if (streq(field, "machine-id"))
7e87c7d9 101 r = free_and_strdup(&tmp.machine_id, p);
f99fdc3e 102 else if (streq(field, "architecture"))
7e87c7d9 103 r = free_and_strdup(&tmp.architecture, p);
f99fdc3e 104 else if (streq(field, "options"))
7e87c7d9 105 r = strv_extend(&tmp.options, p);
f99fdc3e 106 else if (streq(field, "linux"))
7e87c7d9 107 r = free_and_strdup(&tmp.kernel, p);
f99fdc3e 108 else if (streq(field, "efi"))
7e87c7d9 109 r = free_and_strdup(&tmp.efi, p);
f99fdc3e 110 else if (streq(field, "initrd"))
7e87c7d9 111 r = strv_extend(&tmp.initrd, p);
f99fdc3e 112 else if (streq(field, "devicetree"))
7e87c7d9
ZJS
113 r = free_and_strdup(&tmp.device_tree, p);
114 else {
f99fdc3e 115 log_notice("%s:%u: Unknown line \"%s\"", path, line, field);
7e87c7d9
ZJS
116 continue;
117 }
118 if (r < 0)
119 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
120 }
121
122 *entry = tmp;
123 tmp = (BootEntry) {};
124 return 0;
125}
126
127void boot_config_free(BootConfig *config) {
da6053d0 128 size_t i;
7e87c7d9 129
4fe2ba0e
LP
130 assert(config);
131
7e87c7d9
ZJS
132 free(config->default_pattern);
133 free(config->timeout);
134 free(config->editor);
c1d4e298
JJ
135 free(config->auto_entries);
136 free(config->auto_firmware);
7e87c7d9
ZJS
137
138 free(config->entry_oneshot);
139 free(config->entry_default);
140
141 for (i = 0; i < config->n_entries; i++)
142 boot_entry_free(config->entries + i);
143 free(config->entries);
144}
145
146int boot_loader_read_conf(const char *path, BootConfig *config) {
147 _cleanup_fclose_ FILE *f = NULL;
148 unsigned line = 1;
149 int r;
150
4fe2ba0e
LP
151 assert(path);
152 assert(config);
153
7e87c7d9
ZJS
154 f = fopen(path, "re");
155 if (!f)
156 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
157
158 for (;;) {
f99fdc3e
YW
159 _cleanup_free_ char *buf = NULL, *field = NULL;
160 const char *p;
7e87c7d9
ZJS
161
162 r = read_line(f, LONG_LINE_MAX, &buf);
163 if (r == 0)
164 break;
165 if (r == -ENOBUFS)
166 return log_error_errno(r, "%s:%u: Line too long", path, line);
167 if (r < 0)
168 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
169
170 line++;
171
172 if (IN_SET(*strstrip(buf), '#', '\0'))
173 continue;
174
f99fdc3e
YW
175 p = buf;
176 r = extract_first_word(&p, &field, " \t", 0);
177 if (r < 0) {
178 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
179 continue;
180 }
181 if (r == 0) {
7e87c7d9
ZJS
182 log_warning("%s:%u: Bad syntax", path, line);
183 continue;
184 }
7e87c7d9 185
f99fdc3e 186 if (streq(field, "default"))
7e87c7d9 187 r = free_and_strdup(&config->default_pattern, p);
f99fdc3e 188 else if (streq(field, "timeout"))
7e87c7d9 189 r = free_and_strdup(&config->timeout, p);
f99fdc3e 190 else if (streq(field, "editor"))
7e87c7d9 191 r = free_and_strdup(&config->editor, p);
790f84eb 192 else if (streq(field, "auto-entries"))
c1d4e298 193 r = free_and_strdup(&config->auto_entries, p);
790f84eb 194 else if (streq(field, "auto-firmware"))
c1d4e298 195 r = free_and_strdup(&config->auto_firmware, p);
790f84eb 196 else if (streq(field, "console-mode"))
d37b0737 197 r = free_and_strdup(&config->console_mode, p);
7e87c7d9 198 else {
f99fdc3e 199 log_notice("%s:%u: Unknown line \"%s\"", path, line, field);
7e87c7d9
ZJS
200 continue;
201 }
202 if (r < 0)
203 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
204 }
205
206 return 0;
207}
208
7e87c7d9 209static int boot_entry_compare(const void *a, const void *b) {
68c58c67 210 const BootEntry *aa = a, *bb = b;
7e87c7d9
ZJS
211
212 return str_verscmp(aa->filename, bb->filename);
213}
214
4fe2ba0e 215int boot_entries_find(const char *dir, BootEntry **ret_entries, size_t *ret_n_entries) {
7e87c7d9
ZJS
216 _cleanup_strv_free_ char **files = NULL;
217 char **f;
218 int r;
7e87c7d9
ZJS
219 BootEntry *array = NULL;
220 size_t n_allocated = 0, n = 0;
221
4fe2ba0e
LP
222 assert(dir);
223 assert(ret_entries);
224 assert(ret_n_entries);
225
7e87c7d9
ZJS
226 r = conf_files_list(&files, ".conf", NULL, 0, dir, NULL);
227 if (r < 0)
228 return log_error_errno(r, "Failed to list files in \"%s\": %m", dir);
229
230 STRV_FOREACH(f, files) {
231 if (!GREEDY_REALLOC0(array, n_allocated, n + 1))
232 return log_oom();
233
234 r = boot_entry_load(*f, array + n);
235 if (r < 0)
236 continue;
237
238 n++;
239 }
240
241 qsort_safe(array, n, sizeof(BootEntry), boot_entry_compare);
242
4fe2ba0e
LP
243 *ret_entries = array;
244 *ret_n_entries = n;
245
7e87c7d9
ZJS
246 return 0;
247}
248
64f05708 249static bool find_nonunique(BootEntry *entries, size_t n_entries, bool *arr) {
da6053d0 250 size_t i, j;
64f05708
ZJS
251 bool non_unique = false;
252
4fe2ba0e
LP
253 assert(entries || n_entries == 0);
254 assert(arr || n_entries == 0);
255
64f05708
ZJS
256 for (i = 0; i < n_entries; i++)
257 arr[i] = false;
258
259 for (i = 0; i < n_entries; i++)
260 for (j = 0; j < n_entries; j++)
261 if (i != j && streq(boot_entry_title(entries + i),
262 boot_entry_title(entries + j)))
263 non_unique = arr[i] = arr[j] = true;
264
265 return non_unique;
266}
267
268static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
269 char *s;
da6053d0 270 size_t i;
64f05708
ZJS
271 int r;
272 bool arr[n_entries];
273
4fe2ba0e
LP
274 assert(entries || n_entries == 0);
275
64f05708
ZJS
276 /* Find _all_ non-unique titles */
277 if (!find_nonunique(entries, n_entries, arr))
278 return 0;
279
280 /* Add version to non-unique titles */
281 for (i = 0; i < n_entries; i++)
282 if (arr[i] && entries[i].version) {
283 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version);
284 if (r < 0)
285 return -ENOMEM;
286
287 free_and_replace(entries[i].show_title, s);
288 }
289
290 if (!find_nonunique(entries, n_entries, arr))
291 return 0;
292
293 /* Add machine-id to non-unique titles */
294 for (i = 0; i < n_entries; i++)
295 if (arr[i] && entries[i].machine_id) {
296 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id);
297 if (r < 0)
298 return -ENOMEM;
299
300 free_and_replace(entries[i].show_title, s);
301 }
302
303 if (!find_nonunique(entries, n_entries, arr))
304 return 0;
305
306 /* Add file name to non-unique titles */
307 for (i = 0; i < n_entries; i++)
308 if (arr[i]) {
309 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].filename);
310 if (r < 0)
311 return -ENOMEM;
312
313 free_and_replace(entries[i].show_title, s);
314 }
315
316 return 0;
317}
318
ad1afd60 319static int boot_entries_select_default(const BootConfig *config) {
7e87c7d9
ZJS
320 int i;
321
4fe2ba0e
LP
322 assert(config);
323
7e87c7d9
ZJS
324 if (config->entry_oneshot)
325 for (i = config->n_entries - 1; i >= 0; i--)
326 if (streq(config->entry_oneshot, config->entries[i].filename)) {
327 log_debug("Found default: filename \"%s\" is matched by LoaderEntryOneShot",
328 config->entries[i].filename);
329 return i;
330 }
331
332 if (config->entry_default)
333 for (i = config->n_entries - 1; i >= 0; i--)
334 if (streq(config->entry_default, config->entries[i].filename)) {
335 log_debug("Found default: filename \"%s\" is matched by LoaderEntryDefault",
336 config->entries[i].filename);
337 return i;
338 }
339
340 if (config->default_pattern)
341 for (i = config->n_entries - 1; i >= 0; i--)
342 if (fnmatch(config->default_pattern, config->entries[i].filename, FNM_CASEFOLD) == 0) {
343 log_debug("Found default: filename \"%s\" is matched by pattern \"%s\"",
344 config->entries[i].filename, config->default_pattern);
345 return i;
346 }
347
348 if (config->n_entries > 0)
5838493a 349 log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].filename);
7e87c7d9
ZJS
350 else
351 log_debug("Found no default boot entry :(");
4fe2ba0e 352
7e87c7d9
ZJS
353 return config->n_entries - 1; /* -1 means "no default" */
354}
355
356int boot_entries_load_config(const char *esp_path, BootConfig *config) {
357 const char *p;
358 int r;
359
4fe2ba0e
LP
360 assert(esp_path);
361 assert(config);
362
7e87c7d9
ZJS
363 p = strjoina(esp_path, "/loader/loader.conf");
364 r = boot_loader_read_conf(p, config);
365 if (r < 0)
366 return log_error_errno(r, "Failed to read boot config from \"%s\": %m", p);
367
368 p = strjoina(esp_path, "/loader/entries");
369 r = boot_entries_find(p, &config->entries, &config->n_entries);
370 if (r < 0)
371 return log_error_errno(r, "Failed to read boot entries from \"%s\": %m", p);
372
64f05708
ZJS
373 r = boot_entries_uniquify(config->entries, config->n_entries);
374 if (r < 0)
375 return log_error_errno(r, "Failed to uniquify boot entries: %m");
376
7e87c7d9
ZJS
377 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", &config->entry_oneshot);
378 if (r < 0 && r != -ENOENT)
379 return log_error_errno(r, "Failed to read EFI var \"LoaderEntryOneShot\": %m");
380
381 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryDefault", &config->entry_default);
382 if (r < 0 && r != -ENOENT)
383 return log_error_errno(r, "Failed to read EFI var \"LoaderEntryDefault\": %m");
384
385 config->default_entry = boot_entries_select_default(config);
386 return 0;
387}
af918182
ZJS
388
389/********************************************************************************/
390
391static int verify_esp(
af918182 392 const char *p,
5caa3167
LP
393 bool searching,
394 bool unprivileged_mode,
af918182
ZJS
395 uint32_t *ret_part,
396 uint64_t *ret_pstart,
397 uint64_t *ret_psize,
398 sd_id128_t *ret_uuid) {
4e066f7f 399#if HAVE_BLKID
8e766630 400 _cleanup_(blkid_free_probep) blkid_probe b = NULL;
c67f84b0 401 char t[DEV_NUM_PATH_MAX];
4e066f7f
YW
402 const char *v;
403#endif
af918182
ZJS
404 uint64_t pstart = 0, psize = 0;
405 struct stat st, st2;
4e066f7f 406 const char *t2;
af918182
ZJS
407 struct statfs sfs;
408 sd_id128_t uuid = SD_ID128_NULL;
409 uint32_t part = 0;
af918182
ZJS
410 int r;
411
412 assert(p);
413
5caa3167
LP
414 /* Non-root user can only check the status, so if an error occured in the following, it does not cause any
415 * issues. Let's also, silence the error messages. */
af918182
ZJS
416
417 if (statfs(p, &sfs) < 0) {
418 /* If we are searching for the mount point, don't generate a log message if we can't find the path */
419 if (errno == ENOENT && searching)
420 return -ENOENT;
421
5caa3167 422 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
af918182
ZJS
423 "Failed to check file system type of \"%s\": %m", p);
424 }
425
426 if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) {
427 if (searching)
428 return -EADDRNOTAVAIL;
429
430 log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
431 return -ENODEV;
432 }
433
434 if (stat(p, &st) < 0)
5caa3167 435 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
af918182
ZJS
436 "Failed to determine block device node of \"%s\": %m", p);
437
438 if (major(st.st_dev) == 0) {
439 log_error("Block device node of %p is invalid.", p);
440 return -ENODEV;
441 }
442
443 t2 = strjoina(p, "/..");
444 r = stat(t2, &st2);
445 if (r < 0)
5caa3167 446 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
af918182
ZJS
447 "Failed to determine block device node of parent of \"%s\": %m", p);
448
449 if (st.st_dev == st2.st_dev) {
450 log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p);
451 return -ENODEV;
452 }
453
454 /* In a container we don't have access to block devices, skip this part of the verification, we trust the
455 * container manager set everything up correctly on its own. Also skip the following verification for non-root user. */
5caa3167 456 if (detect_container() > 0 || unprivileged_mode)
af918182
ZJS
457 goto finish;
458
4e066f7f 459#if HAVE_BLKID
c67f84b0 460 xsprintf_dev_num_path(t, "block", st.st_dev);
af918182
ZJS
461 errno = 0;
462 b = blkid_new_probe_from_filename(t);
463 if (!b)
464 return log_error_errno(errno ?: ENOMEM, "Failed to open file system \"%s\": %m", p);
465
466 blkid_probe_enable_superblocks(b, 1);
467 blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
468 blkid_probe_enable_partitions(b, 1);
469 blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
470
471 errno = 0;
472 r = blkid_do_safeprobe(b);
473 if (r == -2) {
474 log_error("File system \"%s\" is ambiguous.", p);
475 return -ENODEV;
476 } else if (r == 1) {
477 log_error("File system \"%s\" does not contain a label.", p);
478 return -ENODEV;
479 } else if (r != 0)
480 return log_error_errno(errno ?: EIO, "Failed to probe file system \"%s\": %m", p);
481
482 errno = 0;
483 r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
484 if (r != 0)
485 return log_error_errno(errno ?: EIO, "Failed to probe file system type \"%s\": %m", p);
486 if (!streq(v, "vfat")) {
487 log_error("File system \"%s\" is not FAT.", p);
488 return -ENODEV;
489 }
490
491 errno = 0;
492 r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
493 if (r != 0)
494 return log_error_errno(errno ?: EIO, "Failed to probe partition scheme \"%s\": %m", p);
495 if (!streq(v, "gpt")) {
496 log_error("File system \"%s\" is not on a GPT partition table.", p);
497 return -ENODEV;
498 }
499
500 errno = 0;
501 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
502 if (r != 0)
503 return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID \"%s\": %m", p);
504 if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) {
505 log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p);
506 return -ENODEV;
507 }
508
509 errno = 0;
510 r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
511 if (r != 0)
512 return log_error_errno(errno ?: EIO, "Failed to probe partition entry UUID \"%s\": %m", p);
513 r = sd_id128_from_string(v, &uuid);
514 if (r < 0) {
515 log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v);
516 return -EIO;
517 }
518
519 errno = 0;
520 r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
521 if (r != 0)
522 return log_error_errno(errno ?: EIO, "Failed to probe partition number \"%s\": m", p);
523 r = safe_atou32(v, &part);
524 if (r < 0)
525 return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
526
527 errno = 0;
528 r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
529 if (r != 0)
530 return log_error_errno(errno ?: EIO, "Failed to probe partition offset \"%s\": %m", p);
531 r = safe_atou64(v, &pstart);
532 if (r < 0)
533 return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
534
535 errno = 0;
536 r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
537 if (r != 0)
538 return log_error_errno(errno ?: EIO, "Failed to probe partition size \"%s\": %m", p);
539 r = safe_atou64(v, &psize);
540 if (r < 0)
541 return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
4e066f7f 542#endif
af918182
ZJS
543
544finish:
545 if (ret_part)
546 *ret_part = part;
547 if (ret_pstart)
548 *ret_pstart = pstart;
549 if (ret_psize)
550 *ret_psize = psize;
551 if (ret_uuid)
552 *ret_uuid = uuid;
553
554 return 0;
555}
556
5caa3167
LP
557int find_esp_and_warn(
558 const char *path,
559 bool unprivileged_mode,
560 char **ret_path,
561 uint32_t *ret_part,
562 uint64_t *ret_pstart,
563 uint64_t *ret_psize,
564 sd_id128_t *ret_uuid) {
af918182 565
af918182
ZJS
566 int r;
567
5caa3167
LP
568 /* This logs about all errors except:
569 *
570 * -ENOKEY → when we can't find the partition
571 * -EACCESS → when unprivileged_mode is true, and we can't access something
572 */
af918182 573
5caa3167
LP
574 if (path) {
575 r = verify_esp(path, false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
af918182
ZJS
576 if (r < 0)
577 return r;
578
5caa3167
LP
579 goto found;
580 }
581
582 FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") {
583
584 r = verify_esp(path, true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
585 if (r >= 0)
586 goto found;
587 if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
588 return r;
589 }
590
591 /* No logging here */
592 return -ENOKEY;
593
594found:
595 if (ret_path) {
596 char *c;
597
598 c = strdup(path);
599 if (!c)
af918182
ZJS
600 return log_oom();
601
5caa3167 602 *ret_path = c;
af918182
ZJS
603 }
604
5caa3167 605 return 0;
af918182 606}