]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/import-creds.c
hexdecoct: make unbase64mem and unhexmem always use SIZE_MAX
[thirdparty/systemd.git] / src / core / import-creds.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/mount.h>
4
5 #include "confidential-virt.h"
6 #include "copy.h"
7 #include "creds-util.h"
8 #include "escape.h"
9 #include "fileio.h"
10 #include "format-util.h"
11 #include "fs-util.h"
12 #include "hexdecoct.h"
13 #include "import-creds.h"
14 #include "initrd-util.h"
15 #include "io-util.h"
16 #include "mkdir-label.h"
17 #include "mount-util.h"
18 #include "mountpoint-util.h"
19 #include "parse-util.h"
20 #include "path-util.h"
21 #include "proc-cmdline.h"
22 #include "recurse-dir.h"
23 #include "strv.h"
24 #include "virt.h"
25
26 /* This imports credentials passed in from environments higher up (VM manager, boot loader, …) and rearranges
27 * them so that later code can access them using our regular credential protocol
28 * (i.e. $CREDENTIALS_DIRECTORY). It's supposed to be minimal glue to unify behaviour how PID 1 (and
29 * generators invoked by it) can acquire credentials from outside, to mimic how we support it for containers,
30 * but on VM/physical environments.
31 *
32 * This does four things:
33 *
34 * 1. It imports credentials picked up by sd-boot (and placed in the /.extra/credentials/ dir in the initrd)
35 * and puts them in /run/credentials/@encrypted/. Note that during the initrd→host transition the initrd root
36 * file system is cleaned out, thus it is essential we pick up these files before they are deleted. Note
37 * that these credentials originate from an untrusted source, i.e. the ESP and are not
38 * pre-authenticated. They still have to be authenticated before use.
39 *
40 * 2. It imports credentials from /proc/cmdline and puts them in /run/credentials/@system/. These come from a
41 * trusted environment (i.e. the boot loader), and are typically authenticated (if authentication is done
42 * at all). However, they are world-readable, which might be less than ideal. Hence only use this for data
43 * that doesn't require trust.
44 *
45 * 3. It imports credentials passed in through qemu's fw_cfg logic. Specifically, credential data passed in
46 * /sys/firmware/qemu_fw_cfg/by_name/opt/io.systemd.credentials/ is picked up and also placed in
47 * /run/credentials/@system/.
48 *
49 * 4. It imports credentials passed in via the DMI/SMBIOS OEM string tables, quite similar to fw_cfg. It
50 * looks for strings starting with "io.systemd.credential:" and "io.systemd.credential.binary:". Both
51 * expect a key=value assignment, but in the latter case the value is Base64 decoded, allowing binary
52 * credentials to be passed in.
53 *
54 * If it picked up any credentials it will set the $CREDENTIALS_DIRECTORY and
55 * $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables to point to these directories, so that processes
56 * can find them there later on. If "ramfs" is available $CREDENTIALS_DIRECTORY will be backed by it (but
57 * $ENCRYPTED_CREDENTIALS_DIRECTORY is just a regular tmpfs).
58 *
59 * Net result: the service manager can pick up trusted credentials from $CREDENTIALS_DIRECTORY afterwards,
60 * and untrusted ones from $ENCRYPTED_CREDENTIALS_DIRECTORY. */
61
62 typedef struct ImportCredentialContext {
63 int target_dir_fd;
64 size_t size_sum;
65 unsigned n_credentials;
66 } ImportCredentialContext;
67
68 static void import_credentials_context_free(ImportCredentialContext *c) {
69 assert(c);
70
71 c->target_dir_fd = safe_close(c->target_dir_fd);
72 }
73
74 static int acquire_credential_directory(ImportCredentialContext *c, const char *path, bool with_mount) {
75 int r;
76
77 assert(c);
78 assert(path);
79
80 if (c->target_dir_fd >= 0)
81 return c->target_dir_fd;
82
83 r = path_is_mount_point(path, NULL, 0);
84 if (r < 0) {
85 if (r != -ENOENT)
86 return log_error_errno(r, "Failed to determine if %s is a mount point: %m", path);
87
88 r = mkdir_safe_label(path, 0700, 0, 0, MKDIR_WARN_MODE);
89 if (r < 0)
90 return log_error_errno(r, "Failed to create %s mount point: %m", path);
91
92 r = 0; /* Now it exists and is not a mount point */
93 }
94 if (r > 0)
95 /* If already a mount point, then remount writable */
96 (void) mount_nofollow_verbose(LOG_WARNING, NULL, path, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false), NULL);
97 else if (with_mount)
98 /* If not a mount point yet, and the credentials are not encrypted, then let's try to mount a no-swap fs there */
99 (void) mount_credentials_fs(path, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false);
100
101 c->target_dir_fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
102 if (c->target_dir_fd < 0)
103 return log_error_errno(errno, "Failed to open %s: %m", path);
104
105 return c->target_dir_fd;
106 }
107
108 static int open_credential_file_for_write(int target_dir_fd, const char *dir_name, const char *n) {
109 int fd;
110
111 assert(target_dir_fd >= 0);
112 assert(dir_name);
113 assert(n);
114
115 fd = openat(target_dir_fd, n, O_WRONLY|O_CLOEXEC|O_CREAT|O_EXCL|O_NOFOLLOW, 0400);
116 if (fd < 0) {
117 if (errno == EEXIST) /* In case of EEXIST we'll only debug log! */
118 return log_debug_errno(errno, "Credential '%s' set twice, ignoring.", n);
119
120 return log_error_errno(errno, "Failed to create %s/%s: %m", dir_name, n);
121 }
122
123 return fd;
124 }
125
126 static bool credential_size_ok(ImportCredentialContext *c, const char *name, uint64_t size) {
127 assert(c);
128 assert(name);
129
130 if (size > CREDENTIAL_SIZE_MAX) {
131 log_warning("Credential '%s' is larger than allowed limit (%s > %s), skipping.", name, FORMAT_BYTES(size), FORMAT_BYTES(CREDENTIAL_SIZE_MAX));
132 return false;
133 }
134
135 if (size > CREDENTIALS_TOTAL_SIZE_MAX - c->size_sum) {
136 log_warning("Accumulated credential size would be above allowed limit (%s+%s > %s), skipping '%s'.",
137 FORMAT_BYTES(c->size_sum), FORMAT_BYTES(size), FORMAT_BYTES(CREDENTIALS_TOTAL_SIZE_MAX), name);
138 return false;
139 }
140
141 return true;
142 }
143
144 static int finalize_credentials_dir(const char *dir, const char *envvar) {
145 int r;
146
147 assert(dir);
148 assert(envvar);
149
150 /* Try to make the credentials directory read-only now */
151
152 r = make_mount_point(dir);
153 if (r < 0)
154 log_warning_errno(r, "Failed to make '%s' a mount point, ignoring: %m", dir);
155 else
156 (void) mount_nofollow_verbose(LOG_WARNING, NULL, dir, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ true), NULL);
157
158 if (setenv(envvar, dir, /* overwrite= */ true) < 0)
159 return log_error_errno(errno, "Failed to set $%s environment variable: %m", envvar);
160
161 return 0;
162 }
163
164 static int import_credentials_boot(void) {
165 _cleanup_(import_credentials_context_free) ImportCredentialContext context = {
166 .target_dir_fd = -EBADF,
167 };
168 int r;
169
170 /* systemd-stub will wrap sidecar *.cred files from the UEFI kernel image directory into initrd
171 * cpios, so that they unpack into /.extra/. We'll pick them up from there and copy them into /run/
172 * so that we can access them during the entire runtime (note that the initrd file system is erased
173 * during the initrd → host transition). Note that these credentials originate from an untrusted
174 * source (i.e. the ESP typically) and thus need to be authenticated later. We thus put them in a
175 * directory separate from the usual credentials which are from a trusted source. */
176
177 if (!in_initrd())
178 return 0;
179
180 FOREACH_STRING(p,
181 "/.extra/credentials/", /* specific to this boot menu */
182 "/.extra/global_credentials/") { /* boot partition wide */
183
184 _cleanup_free_ DirectoryEntries *de = NULL;
185 _cleanup_close_ int source_dir_fd = -EBADF;
186
187 source_dir_fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
188 if (source_dir_fd < 0) {
189 if (errno == ENOENT) {
190 log_debug("No credentials passed via %s.", p);
191 continue;
192 }
193
194 log_warning_errno(errno, "Failed to open '%s', ignoring: %m", p);
195 continue;
196 }
197
198 r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
199 if (r < 0) {
200 log_warning_errno(r, "Failed to read '%s' contents, ignoring: %m", p);
201 continue;
202 }
203
204 for (size_t i = 0; i < de->n_entries; i++) {
205 const struct dirent *d = de->entries[i];
206 _cleanup_close_ int cfd = -EBADF, nfd = -EBADF;
207 _cleanup_free_ char *n = NULL;
208 const char *e;
209 struct stat st;
210
211 e = endswith(d->d_name, ".cred");
212 if (!e)
213 continue;
214
215 /* drop .cred suffix (which we want in the ESP sidecar dir, but not for our internal
216 * processing) */
217 n = strndup(d->d_name, e - d->d_name);
218 if (!n)
219 return log_oom();
220
221 if (!credential_name_valid(n)) {
222 log_warning("Credential '%s' has invalid name, ignoring.", d->d_name);
223 continue;
224 }
225
226 cfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_CLOEXEC);
227 if (cfd < 0) {
228 log_warning_errno(errno, "Failed to open %s, ignoring: %m", d->d_name);
229 continue;
230 }
231
232 if (fstat(cfd, &st) < 0) {
233 log_warning_errno(errno, "Failed to stat %s, ignoring: %m", d->d_name);
234 continue;
235 }
236
237 r = stat_verify_regular(&st);
238 if (r < 0) {
239 log_warning_errno(r, "Credential file %s is not a regular file, ignoring: %m", d->d_name);
240 continue;
241 }
242
243 if (!credential_size_ok(&context, n, st.st_size))
244 continue;
245
246 r = acquire_credential_directory(&context, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ false);
247 if (r < 0)
248 return r;
249
250 nfd = open_credential_file_for_write(context.target_dir_fd, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, n);
251 if (nfd == -EEXIST)
252 continue;
253 if (nfd < 0)
254 return nfd;
255
256 r = copy_bytes(cfd, nfd, st.st_size, 0);
257 if (r < 0) {
258 (void) unlinkat(context.target_dir_fd, n, 0);
259 return log_error_errno(r, "Failed to create credential '%s': %m", n);
260 }
261
262 context.size_sum += st.st_size;
263 context.n_credentials++;
264
265 log_debug("Successfully copied boot credential '%s'.", n);
266 }
267 }
268
269 if (context.n_credentials > 0) {
270 log_debug("Imported %u credentials from boot loader.", context.n_credentials);
271
272 r = finalize_credentials_dir(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, "ENCRYPTED_CREDENTIALS_DIRECTORY");
273 if (r < 0)
274 return r;
275 }
276
277 return 0;
278 }
279
280 static int proc_cmdline_callback(const char *key, const char *value, void *data) {
281 ImportCredentialContext *c = ASSERT_PTR(data);
282 _cleanup_free_ void *binary = NULL;
283 _cleanup_free_ char *n = NULL;
284 _cleanup_close_ int nfd = -EBADF;
285 const char *colon, *d;
286 bool base64;
287 size_t l;
288 int r;
289
290 assert(key);
291
292 if (proc_cmdline_key_streq(key, "systemd.set_credential"))
293 base64 = false;
294 else if (proc_cmdline_key_streq(key, "systemd.set_credential_binary"))
295 base64 = true;
296 else
297 return 0;
298
299 colon = value ? strchr(value, ':') : NULL;
300 if (!colon) {
301 log_warning("Credential assignment through kernel command line lacks ':' character, ignoring: %s", value);
302 return 0;
303 }
304
305 n = strndup(value, colon - value);
306 if (!n)
307 return log_oom();
308
309 if (!credential_name_valid(n)) {
310 log_warning("Credential name '%s' is invalid, ignoring.", n);
311 return 0;
312 }
313
314 colon++;
315
316 if (base64) {
317 r = unbase64mem(colon, &binary, &l);
318 if (r < 0) {
319 log_warning_errno(r, "Failed to decode binary credential '%s' data, ignoring: %m", n);
320 return 0;
321 }
322
323 d = binary;
324 } else {
325 d = colon;
326 l = strlen(colon);
327 }
328
329 if (!credential_size_ok(c, n, l))
330 return 0;
331
332 r = acquire_credential_directory(c, SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ true);
333 if (r < 0)
334 return r;
335
336 nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, n);
337 if (nfd == -EEXIST)
338 return 0;
339 if (nfd < 0)
340 return nfd;
341
342 r = loop_write(nfd, d, l);
343 if (r < 0) {
344 (void) unlinkat(c->target_dir_fd, n, 0);
345 return log_error_errno(r, "Failed to write credential: %m");
346 }
347
348 c->size_sum += l;
349 c->n_credentials++;
350
351 log_debug("Successfully processed kernel command line credential '%s'.", n);
352
353 return 0;
354 }
355
356 static int import_credentials_proc_cmdline(ImportCredentialContext *c) {
357 int r;
358
359 assert(c);
360
361 r = proc_cmdline_parse(proc_cmdline_callback, c, 0);
362 if (r < 0)
363 return log_error_errno(r, "Failed to parse /proc/cmdline: %m");
364
365 return 0;
366 }
367
368 #define QEMU_FWCFG_PATH "/sys/firmware/qemu_fw_cfg/by_name/opt/io.systemd.credentials"
369
370 static int import_credentials_qemu(ImportCredentialContext *c) {
371 _cleanup_free_ DirectoryEntries *de = NULL;
372 _cleanup_close_ int source_dir_fd = -EBADF;
373 int r;
374
375 assert(c);
376
377 if (detect_container() > 0) /* don't access /sys/ in a container */
378 return 0;
379
380 if (detect_confidential_virtualization() > 0) /* don't trust firmware if confidential VMs */
381 return 0;
382
383 source_dir_fd = open(QEMU_FWCFG_PATH, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
384 if (source_dir_fd < 0) {
385 if (errno == ENOENT) {
386 log_debug("No credentials passed via fw_cfg.");
387 return 0;
388 }
389
390 log_warning_errno(errno, "Failed to open '" QEMU_FWCFG_PATH "', ignoring: %m");
391 return 0;
392 }
393
394 r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
395 if (r < 0) {
396 log_warning_errno(r, "Failed to read '" QEMU_FWCFG_PATH "' contents, ignoring: %m");
397 return 0;
398 }
399
400 for (size_t i = 0; i < de->n_entries; i++) {
401 const struct dirent *d = de->entries[i];
402 _cleanup_close_ int vfd = -EBADF, rfd = -EBADF, nfd = -EBADF;
403 _cleanup_free_ char *szs = NULL;
404 uint64_t sz;
405
406 if (!credential_name_valid(d->d_name)) {
407 log_warning("Credential '%s' has invalid name, ignoring.", d->d_name);
408 continue;
409 }
410
411 vfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
412 if (vfd < 0) {
413 log_warning_errno(errno, "Failed to open '" QEMU_FWCFG_PATH "'/%s/, ignoring: %m", d->d_name);
414 continue;
415 }
416
417 r = read_virtual_file_at(vfd, "size", LINE_MAX, &szs, NULL);
418 if (r < 0) {
419 log_warning_errno(r, "Failed to read '" QEMU_FWCFG_PATH "'/%s/size, ignoring: %m", d->d_name);
420 continue;
421 }
422
423 r = safe_atou64(strstrip(szs), &sz);
424 if (r < 0) {
425 log_warning_errno(r, "Failed to parse size of credential '%s', ignoring: %s", d->d_name, szs);
426 continue;
427 }
428
429 if (!credential_size_ok(c, d->d_name, sz))
430 continue;
431
432 /* Ideally we'd just symlink the data here. Alas the kernel driver exports the raw file as
433 * having size zero, and we'd rather not have applications support such credential
434 * files. Let's hence copy the files to make them regular. */
435
436 rfd = openat(vfd, "raw", O_RDONLY|O_CLOEXEC);
437 if (rfd < 0) {
438 log_warning_errno(errno, "Failed to open '" QEMU_FWCFG_PATH "'/%s/raw, ignoring: %m", d->d_name);
439 continue;
440 }
441
442 r = acquire_credential_directory(c, SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ true);
443 if (r < 0)
444 return r;
445
446 nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
447 if (nfd == -EEXIST)
448 continue;
449 if (nfd < 0)
450 return nfd;
451
452 r = copy_bytes(rfd, nfd, sz, 0);
453 if (r < 0) {
454 (void) unlinkat(c->target_dir_fd, d->d_name, 0);
455 return log_error_errno(r, "Failed to create credential '%s': %m", d->d_name);
456 }
457
458 c->size_sum += sz;
459 c->n_credentials++;
460
461 log_debug("Successfully copied qemu fw_cfg credential '%s'.", d->d_name);
462 }
463
464 return 0;
465 }
466
467 static int parse_smbios_strings(ImportCredentialContext *c, const char *data, size_t size) {
468 size_t left, skip;
469 const char *p;
470 int r;
471
472 assert(c);
473 assert(data || size == 0);
474
475 /* Unpacks a packed series of SMBIOS OEM vendor strings. These are a series of NUL terminated
476 * strings, one after the other. */
477
478 for (p = data, left = size; left > 0; p += skip, left -= skip) {
479 _cleanup_free_ void *buf = NULL;
480 _cleanup_free_ char *cn = NULL;
481 _cleanup_close_ int nfd = -EBADF;
482 const char *nul, *n, *eq;
483 const void *cdata;
484 size_t buflen, cdata_len;
485 bool unbase64;
486
487 nul = memchr(p, 0, left);
488 if (nul)
489 skip = (nul - p) + 1;
490 else {
491 nul = p + left;
492 skip = left;
493 }
494
495 if (nul - p == 0) /* Skip empty strings */
496 continue;
497
498 /* Only care about strings starting with either of these two prefixes */
499 if ((n = memory_startswith(p, nul - p, "io.systemd.credential:")))
500 unbase64 = false;
501 else if ((n = memory_startswith(p, nul - p, "io.systemd.credential.binary:")))
502 unbase64 = true;
503 else {
504 _cleanup_free_ char *escaped = NULL;
505
506 escaped = cescape_length(p, nul - p);
507 log_debug("Ignoring OEM string: %s", strnull(escaped));
508 continue;
509 }
510
511 eq = memchr(n, '=', nul - n);
512 if (!eq) {
513 log_warning("SMBIOS OEM string lacks '=' character, ignoring.");
514 continue;
515 }
516
517 cn = memdup_suffix0(n, eq - n);
518 if (!cn)
519 return log_oom();
520
521 if (!credential_name_valid(cn)) {
522 log_warning("SMBIOS credential name '%s' is not valid, ignoring: %m", cn);
523 continue;
524 }
525
526 /* Optionally base64 decode the data, if requested, to allow binary credentials */
527 if (unbase64) {
528 r = unbase64mem_full(eq + 1, nul - (eq + 1), /* secure = */ false, &buf, &buflen);
529 if (r < 0) {
530 log_warning_errno(r, "Failed to base64 decode credential '%s', ignoring: %m", cn);
531 continue;
532 }
533
534 cdata = buf;
535 cdata_len = buflen;
536 } else {
537 cdata = eq + 1;
538 cdata_len = nul - (eq + 1);
539 }
540
541 if (!credential_size_ok(c, cn, cdata_len))
542 continue;
543
544 r = acquire_credential_directory(c, SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ true);
545 if (r < 0)
546 return r;
547
548 nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, cn);
549 if (nfd == -EEXIST)
550 continue;
551 if (nfd < 0)
552 return nfd;
553
554 r = loop_write(nfd, cdata, cdata_len);
555 if (r < 0) {
556 (void) unlinkat(c->target_dir_fd, cn, 0);
557 return log_error_errno(r, "Failed to write credential: %m");
558 }
559
560 c->size_sum += cdata_len;
561 c->n_credentials++;
562
563 log_debug("Successfully processed SMBIOS credential '%s'.", cn);
564 }
565
566 return 0;
567 }
568
569 static int import_credentials_smbios(ImportCredentialContext *c) {
570 int r;
571
572 /* Parses DMI OEM strings fields (SMBIOS type 11), as settable with qemu's -smbios type=11,value=… switch. */
573
574 if (detect_container() > 0) /* don't access /sys/ in a container */
575 return 0;
576
577 if (detect_confidential_virtualization() > 0) /* don't trust firmware if confidential VMs */
578 return 0;
579
580 for (unsigned i = 0;; i++) {
581 struct dmi_field_header {
582 uint8_t type;
583 uint8_t length;
584 uint16_t handle;
585 uint8_t count;
586 char contents[];
587 } _packed_ *dmi_field_header;
588 _cleanup_free_ char *p = NULL;
589 _cleanup_free_ void *data = NULL;
590 size_t size;
591
592 assert_cc(offsetof(struct dmi_field_header, contents) == 5);
593
594 if (asprintf(&p, "/sys/firmware/dmi/entries/11-%u/raw", i) < 0)
595 return log_oom();
596
597 r = read_virtual_file(p, sizeof(dmi_field_header) + CREDENTIALS_TOTAL_SIZE_MAX, (char**) &data, &size);
598 if (r < 0) {
599 /* Once we reach ENOENT there are no more DMI Type 11 fields around. */
600 log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, "Failed to open '%s', ignoring: %m", p);
601 break;
602 }
603
604 if (size < offsetof(struct dmi_field_header, contents))
605 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "DMI field header of '%s' too short.", p);
606
607 dmi_field_header = data;
608 if (dmi_field_header->type != 11 ||
609 dmi_field_header->length != offsetof(struct dmi_field_header, contents))
610 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid DMI field header.");
611
612 r = parse_smbios_strings(c, dmi_field_header->contents, size - offsetof(struct dmi_field_header, contents));
613 if (r < 0)
614 return r;
615
616 if (i == UINT_MAX) /* Prevent overflow */
617 break;
618 }
619
620 return 0;
621 }
622
623 static int import_credentials_initrd(ImportCredentialContext *c) {
624 _cleanup_free_ DirectoryEntries *de = NULL;
625 _cleanup_close_ int source_dir_fd = -EBADF;
626 int r;
627
628 assert(c);
629
630 /* This imports credentials from /run/credentials/@initrd/ into our credentials directory and deletes
631 * the source directory afterwards. This is run once after the initrd → host transition. This is
632 * supposed to establish a well-defined avenue for initrd-based host configurators to pass
633 * credentials into the main system. */
634
635 if (in_initrd())
636 return 0;
637
638 source_dir_fd = open("/run/credentials/@initrd", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
639 if (source_dir_fd < 0) {
640 if (errno == ENOENT)
641 log_debug_errno(errno, "No credentials passed from initrd.");
642 else
643 log_warning_errno(errno, "Failed to open '/run/credentials/@initrd', ignoring: %m");
644 return 0;
645 }
646
647 r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
648 if (r < 0) {
649 log_warning_errno(r, "Failed to read '/run/credentials/@initrd' contents, ignoring: %m");
650 return 0;
651 }
652
653 FOREACH_ARRAY(entry, de->entries, de->n_entries) {
654 _cleanup_close_ int cfd = -EBADF, nfd = -EBADF;
655 const struct dirent *d = *entry;
656 struct stat st;
657
658 if (!credential_name_valid(d->d_name)) {
659 log_warning("Credential '%s' has invalid name, ignoring.", d->d_name);
660 continue;
661 }
662
663 cfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_CLOEXEC);
664 if (cfd < 0) {
665 log_warning_errno(errno, "Failed to open %s, ignoring: %m", d->d_name);
666 continue;
667 }
668
669 if (fstat(cfd, &st) < 0) {
670 log_warning_errno(errno, "Failed to stat %s, ignoring: %m", d->d_name);
671 continue;
672 }
673
674 r = stat_verify_regular(&st);
675 if (r < 0) {
676 log_warning_errno(r, "Credential file %s is not a regular file, ignoring: %m", d->d_name);
677 continue;
678 }
679
680 if (!credential_size_ok(c, d->d_name, st.st_size))
681 continue;
682
683 r = acquire_credential_directory(c, SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ true);
684 if (r < 0)
685 return r;
686
687 nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
688 if (nfd == -EEXIST)
689 continue;
690 if (nfd < 0)
691 return nfd;
692
693 r = copy_bytes(cfd, nfd, st.st_size, 0);
694 if (r < 0) {
695 (void) unlinkat(c->target_dir_fd, d->d_name, 0);
696 return log_error_errno(r, "Failed to create credential '%s': %m", d->d_name);
697 }
698
699 c->size_sum += st.st_size;
700 c->n_credentials++;
701
702 log_debug("Successfully copied initrd credential '%s'.", d->d_name);
703
704 (void) unlinkat(source_dir_fd, d->d_name, 0);
705 }
706
707 source_dir_fd = safe_close(source_dir_fd);
708
709 if (rmdir("/run/credentials/@initrd") < 0)
710 log_warning_errno(errno, "Failed to remove /run/credentials/@initrd after import, ignoring: %m");
711
712 return 0;
713 }
714
715 static int import_credentials_trusted(void) {
716 _cleanup_(import_credentials_context_free) ImportCredentialContext c = {
717 .target_dir_fd = -EBADF,
718 };
719 int q, w, r, y;
720
721 /* This is invoked during early boot when no credentials have been imported so far. (Specifically, if
722 * the $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables are not set
723 * yet.) */
724
725 r = import_credentials_qemu(&c);
726 w = import_credentials_smbios(&c);
727 q = import_credentials_proc_cmdline(&c);
728 y = import_credentials_initrd(&c);
729
730 if (c.n_credentials > 0) {
731 int z;
732
733 log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg/initrd.", c.n_credentials);
734
735 z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
736 if (z < 0)
737 return z;
738 }
739
740 return r < 0 ? r : w < 0 ? w : q < 0 ? q : y;
741 }
742
743 static int merge_credentials_trusted(const char *creds_dir) {
744 _cleanup_(import_credentials_context_free) ImportCredentialContext c = {
745 .target_dir_fd = -EBADF,
746 };
747 int r;
748
749 /* This is invoked after the initrd → host transitions, when credentials already have been imported,
750 * but we might want to import some more from the initrd. */
751
752 if (in_initrd())
753 return 0;
754
755 /* Do not try to merge initrd credentials into foreign credentials directories */
756 if (!path_equal_ptr(creds_dir, SYSTEM_CREDENTIALS_DIRECTORY)) {
757 log_debug("Not importing initrd credentials, as foreign $CREDENTIALS_DIRECTORY has been set.");
758 return 0;
759 }
760
761 r = import_credentials_initrd(&c);
762
763 if (c.n_credentials > 0) {
764 int z;
765
766 log_debug("Merged %u credentials from initrd.", c.n_credentials);
767
768 z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
769 if (z < 0)
770 return z;
771 }
772
773 return r;
774 }
775
776 static int symlink_credential_dir(const char *envvar, const char *path, const char *where) {
777 int r;
778
779 assert(envvar);
780 assert(path);
781 assert(where);
782
783 if (!path_is_valid(path) || !path_is_absolute(path))
784 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String specified via $%s is not a valid absolute path, refusing: %s", envvar, path);
785
786 /* If the env var already points to where we intend to create the symlink, then most likely we
787 * already imported some creds earlier, and thus set the env var, and hence don't need to do
788 * anything. */
789 if (path_equal(path, where))
790 return 0;
791
792 r = symlink_idempotent(path, where, /* make_relative= */ true);
793 if (r < 0)
794 return log_error_errno(r, "Failed to link $%s to %s: %m", envvar, where);
795
796 return 0;
797 }
798
799 static int setenv_notify_socket(void) {
800 _cleanup_free_ char *address = NULL;
801 int r;
802
803 r = read_credential_with_decryption("vmm.notify_socket", (void **)&address, /* ret_size= */ NULL);
804 if (r < 0)
805 return log_warning_errno(r, "Failed to read 'vmm.notify_socket' credential, ignoring: %m");
806
807 if (isempty(address))
808 return 0;
809
810 if (setenv("NOTIFY_SOCKET", address, /* replace= */ 1) < 0)
811 return log_warning_errno(errno, "Failed to set $NOTIFY_SOCKET environment variable, ignoring: %m");
812
813 return 1;
814 }
815
816 static int report_credentials_per_func(const char *title, int (*get_directory_func)(const char **ret)) {
817 _cleanup_free_ DirectoryEntries *de = NULL;
818 _cleanup_free_ char *ll = NULL;
819 const char *d = NULL;
820 int r, c = 0;
821
822 assert(title);
823 assert(get_directory_func);
824
825 r = get_directory_func(&d);
826 if (r < 0) {
827 if (r == -ENXIO) /* Env var not set */
828 return 0;
829
830 return log_warning_errno(r, "Failed to determine %s directory: %m", title);
831 }
832
833 r = readdir_all_at(AT_FDCWD, d, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
834 if (r < 0)
835 return log_warning_errno(r, "Failed to enumerate credentials directory %s: %m", d);
836
837 FOREACH_ARRAY(entry, de->entries, de->n_entries) {
838 const struct dirent *e = *entry;
839
840 if (!credential_name_valid(e->d_name))
841 continue;
842
843 if (!strextend_with_separator(&ll, ", ", e->d_name))
844 return log_oom();
845
846 c++;
847 }
848
849 if (ll)
850 log_info("Received %s: %s", title, ll);
851
852 return c;
853 }
854
855 static void report_credentials(void) {
856 int p, q;
857
858 p = report_credentials_per_func("regular credentials", get_credentials_dir);
859 q = report_credentials_per_func("untrusted credentials", get_encrypted_credentials_dir);
860
861 log_full(p > 0 || q > 0 ? LOG_INFO : LOG_DEBUG,
862 "Acquired %i regular credentials, %i untrusted credentials.",
863 p > 0 ? p : 0,
864 q > 0 ? q : 0);
865 }
866
867 int import_credentials(void) {
868 const char *received_creds_dir = NULL, *received_encrypted_creds_dir = NULL;
869 bool envvar_set = false;
870 int r, q;
871
872 r = get_credentials_dir(&received_creds_dir);
873 if (r < 0 && r != -ENXIO) /* ENXIO → env var not set yet */
874 log_warning_errno(r, "Failed to determine credentials directory, ignoring: %m");
875
876 envvar_set = r >= 0;
877
878 r = get_encrypted_credentials_dir(&received_encrypted_creds_dir);
879 if (r < 0 && r != -ENXIO) /* ENXIO → env var not set yet */
880 log_warning_errno(r, "Failed to determine encrypted credentials directory, ignoring: %m");
881
882 envvar_set = envvar_set || r >= 0;
883
884 if (envvar_set) {
885 /* Maybe an earlier stage initrd already set this up? If so, don't try to import anything again. */
886 log_debug("Not importing credentials, $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY already set.");
887
888 /* But, let's make sure the creds are available from our regular paths. */
889 if (received_creds_dir)
890 r = symlink_credential_dir("CREDENTIALS_DIRECTORY", received_creds_dir, SYSTEM_CREDENTIALS_DIRECTORY);
891 else
892 r = 0;
893
894 if (received_encrypted_creds_dir) {
895 q = symlink_credential_dir("ENCRYPTED_CREDENTIALS_DIRECTORY", received_encrypted_creds_dir, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY);
896 if (r >= 0)
897 r = q;
898 }
899
900 q = merge_credentials_trusted(received_creds_dir);
901 if (r >= 0)
902 r = q;
903
904 } else {
905 _cleanup_free_ char *v = NULL;
906
907 r = proc_cmdline_get_key("systemd.import_credentials", PROC_CMDLINE_STRIP_RD_PREFIX, &v);
908 if (r < 0)
909 log_debug_errno(r, "Failed to check if 'systemd.import_credentials=' kernel command line option is set, ignoring: %m");
910 else if (r > 0) {
911 r = parse_boolean(v);
912 if (r < 0)
913 log_debug_errno(r, "Failed to parse 'systemd.import_credentials=' parameter, ignoring: %m");
914 else if (r == 0) {
915 log_notice("systemd.import_credentials=no is set, skipping importing of credentials.");
916 return 0;
917 }
918 }
919
920 r = import_credentials_boot();
921
922 q = import_credentials_trusted();
923 if (r >= 0)
924 r = q;
925 }
926
927 report_credentials();
928
929 /* Propagate vmm_notify_socket credential → $NOTIFY_SOCKET env var */
930 (void) setenv_notify_socket();
931
932 return r;
933 }