]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <fcntl.h> | |
4 | #include <sys/mount.h> | |
5 | #include <unistd.h> | |
6 | ||
7 | #include "sd-daemon.h" | |
8 | #include "sd-id128.h" | |
9 | ||
10 | #include "alloc-util.h" | |
11 | #include "chase.h" | |
12 | #include "creds-util.h" | |
13 | #include "fd-util.h" | |
14 | #include "fs-util.h" | |
15 | #include "id128-util.h" | |
16 | #include "initrd-util.h" | |
17 | #include "io-util.h" | |
18 | #include "log.h" | |
19 | #include "machine-id-setup.h" | |
20 | #include "mount-util.h" | |
21 | #include "mountpoint-util.h" | |
22 | #include "namespace-util.h" | |
23 | #include "path-util.h" | |
24 | #include "process-util.h" | |
25 | #include "stat-util.h" | |
26 | #include "string-util.h" | |
27 | #include "strv.h" | |
28 | #include "sync-util.h" | |
29 | #include "umask-util.h" | |
30 | #include "virt.h" | |
31 | ||
32 | static int acquire_machine_id_from_credential(sd_id128_t *ret_machine_id) { | |
33 | _cleanup_free_ char *buf = NULL; | |
34 | int r; | |
35 | ||
36 | assert(ret_machine_id); | |
37 | ||
38 | r = read_credential_with_decryption("system.machine_id", (void**) &buf, /* ret_size= */ NULL); | |
39 | if (r < 0) | |
40 | return log_warning_errno(r, "Failed to read system.machine_id credential, ignoring: %m"); | |
41 | if (r == 0) { | |
42 | /* not found */ | |
43 | *ret_machine_id = SD_ID128_NULL; | |
44 | return 0; | |
45 | } | |
46 | ||
47 | if (streq(buf, "firmware")) { | |
48 | *ret_machine_id = SD_ID128_NULL; | |
49 | return 1; | |
50 | } | |
51 | ||
52 | r = sd_id128_from_string(buf, ret_machine_id); | |
53 | if (r < 0) | |
54 | return log_warning_errno(r, "Failed to parse system.machine_id credential, ignoring: %m"); | |
55 | ||
56 | log_info("Initializing machine ID from credential."); | |
57 | return 1; | |
58 | } | |
59 | ||
60 | static int acquire_machine_id(const char *root, bool machine_id_from_firmware, sd_id128_t *ret) { | |
61 | _cleanup_close_ int fd = -EBADF; | |
62 | int r; | |
63 | ||
64 | assert(ret); | |
65 | ||
66 | /* First, try reading the machine ID from /run/machine-id, which may not be mounted on | |
67 | * /etc/machine-id yet. This is important on switching root especially on soft-reboot, Otherwise, | |
68 | * machine ID may be changed after the transition. */ | |
69 | if (isempty(root) && running_in_chroot() <= 0 && | |
70 | id128_read("/run/machine-id", ID128_FORMAT_PLAIN, ret) >= 0) { | |
71 | log_info("Reusing machine ID stored in /run/machine-id."); | |
72 | return 1; /* Indicate that the machine ID is reused. */ | |
73 | } | |
74 | ||
75 | /* Then, try reading the D-Bus machine ID, unless it is a symlink */ | |
76 | fd = chase_and_open("/var/lib/dbus/machine-id", root, CHASE_PREFIX_ROOT|CHASE_NOFOLLOW|CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); | |
77 | if (fd >= 0 && id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret) >= 0) { | |
78 | log_info("Initializing machine ID from D-Bus machine ID."); | |
79 | return 0; | |
80 | } | |
81 | ||
82 | if (isempty(root) && running_in_chroot() <= 0) { | |
83 | /* Let's use a system credential for the machine ID if we can */ | |
84 | sd_id128_t from_credential; | |
85 | r = acquire_machine_id_from_credential(&from_credential); | |
86 | if (r > 0) { | |
87 | if (!sd_id128_is_null(from_credential)) { | |
88 | /* got a valid machine id from creds */ | |
89 | *ret = from_credential; | |
90 | return 0; | |
91 | } | |
92 | ||
93 | /* We got a credential, and it was set to "firmware", hence definitely try that */ | |
94 | machine_id_from_firmware = true; | |
95 | } | |
96 | ||
97 | /* If that didn't work, see if we are running in a container, | |
98 | * and a machine ID was passed in via $container_uuid the way | |
99 | * libvirt/LXC does it */ | |
100 | ||
101 | if (detect_container() > 0) { | |
102 | _cleanup_free_ char *e = NULL; | |
103 | ||
104 | if (getenv_for_pid(1, "container_uuid", &e) > 0 && | |
105 | sd_id128_from_string(e, ret) >= 0) { | |
106 | log_info("Initializing machine ID from container UUID."); | |
107 | return 0; | |
108 | } | |
109 | ||
110 | } else if (IN_SET(detect_vm(), VIRTUALIZATION_KVM, VIRTUALIZATION_AMAZON, VIRTUALIZATION_QEMU, VIRTUALIZATION_XEN, VIRTUALIZATION_BHYVE) || machine_id_from_firmware) { | |
111 | ||
112 | /* If we are not running in a container, see if we are running in a VM that provides | |
113 | * a system UUID via the SMBIOS/DMI interfaces. Such environments include QEMU/KVM | |
114 | * with the -uuid on the qemu command line or the Amazon EC2 Nitro hypervisor. */ | |
115 | ||
116 | if (id128_get_product(ret) >= 0) { | |
117 | log_info("Initializing machine ID from SMBIOS/DMI UUID."); | |
118 | return 0; | |
119 | } | |
120 | } | |
121 | } | |
122 | ||
123 | /* If that didn't work, generate a random machine ID */ | |
124 | r = sd_id128_randomize(ret); | |
125 | if (r < 0) | |
126 | return log_error_errno(r, "Failed to generate randomized machine ID: %m"); | |
127 | ||
128 | log_info("Initializing machine ID from random generator."); | |
129 | return 0; | |
130 | } | |
131 | ||
132 | int machine_id_setup(const char *root, sd_id128_t machine_id, MachineIdSetupFlags flags, sd_id128_t *ret) { | |
133 | _cleanup_free_ char *etc_machine_id = NULL, *run_machine_id = NULL; | |
134 | bool writable, write_run_machine_id = true; | |
135 | _cleanup_close_ int fd = -EBADF, run_fd = -EBADF; | |
136 | bool unlink_run_machine_id = false; | |
137 | int r; | |
138 | ||
139 | WITH_UMASK(0000) { | |
140 | _cleanup_close_ int inode_fd = -EBADF; | |
141 | ||
142 | r = chase("/etc/machine-id", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_REGULAR, &etc_machine_id, &inode_fd); | |
143 | if (r == -ENOENT) { | |
144 | _cleanup_close_ int etc_fd = -EBADF; | |
145 | _cleanup_free_ char *etc = NULL; | |
146 | ||
147 | r = chase("/etc/", root, CHASE_PREFIX_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, &etc, &etc_fd); | |
148 | if (r < 0) | |
149 | return log_error_errno(r, "Failed to open %s: %m", "/etc/"); | |
150 | ||
151 | etc_machine_id = path_join(etc, "machine-id"); | |
152 | if (!etc_machine_id) | |
153 | return log_oom(); | |
154 | ||
155 | /* We create this 0444, to indicate that this isn't really something you should ever | |
156 | * modify. Of course, since the file will be owned by root it doesn't matter much, but maybe | |
157 | * people look. */ | |
158 | ||
159 | fd = openat(etc_fd, "machine-id", O_CREAT|O_EXCL|O_RDWR|O_NOFOLLOW|O_CLOEXEC, 0444); | |
160 | if (fd < 0) { | |
161 | if (errno == EROFS) | |
162 | return log_error_errno(errno, | |
163 | "System cannot boot: Missing %s and %s/ is read-only.\n" | |
164 | "Booting up is supported only when:\n" | |
165 | "1) /etc/machine-id exists and is populated.\n" | |
166 | "2) /etc/machine-id exists and is empty.\n" | |
167 | "3) /etc/machine-id is missing and /etc/ is writable.", | |
168 | etc_machine_id, | |
169 | etc); | |
170 | ||
171 | return log_error_errno(errno, "Cannot create '%s': %m", etc_machine_id); | |
172 | } | |
173 | ||
174 | log_debug("Successfully opened new '%s' file.", etc_machine_id); | |
175 | writable = true; | |
176 | } else if (r < 0) | |
177 | return log_error_errno(r, "Cannot open '/etc/machine-id': %m"); | |
178 | else { | |
179 | /* We pinned the inode, now try to convert it into a writable file */ | |
180 | ||
181 | fd = xopenat_full(inode_fd, /* path= */ NULL, O_RDWR|O_CLOEXEC, XO_REGULAR, 0444); | |
182 | if (fd < 0) { | |
183 | log_debug_errno(fd, "Failed to open '%s' in writable mode, retrying in read-only mode: %m", etc_machine_id); | |
184 | ||
185 | /* If that didn't work, convert it into a readable file */ | |
186 | fd = xopenat_full(inode_fd, /* path= */ NULL, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID); | |
187 | if (fd < 0) | |
188 | return log_error_errno(fd, "Cannot open '%s' in neither writable nor read-only mode: %m", etc_machine_id); | |
189 | ||
190 | log_debug("Successfully opened existing '%s' file in read-only mode.", etc_machine_id); | |
191 | writable = false; | |
192 | } else { | |
193 | log_debug("Successfully opened existing '%s' file in writable mode.", etc_machine_id); | |
194 | writable = true; | |
195 | } | |
196 | } | |
197 | } | |
198 | ||
199 | /* A we got a valid machine ID argument, that's what counts */ | |
200 | if (sd_id128_is_null(machine_id) || FLAGS_SET(flags, MACHINE_ID_SETUP_FORCE_FIRMWARE)) { | |
201 | ||
202 | /* Try to read any existing machine ID */ | |
203 | r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &machine_id); | |
204 | if (r >= 0) | |
205 | goto finish; | |
206 | ||
207 | log_debug_errno(r, "Unable to read current machine ID, acquiring new one: %m"); | |
208 | ||
209 | /* Hmm, so, the id currently stored is not useful, then let's acquire one. */ | |
210 | r = acquire_machine_id(root, FLAGS_SET(flags, MACHINE_ID_SETUP_FORCE_FIRMWARE), &machine_id); | |
211 | if (r < 0) | |
212 | return r; | |
213 | ||
214 | write_run_machine_id = !r; /* acquire_machine_id() returns 1 in case we read this machine ID | |
215 | * from /run/machine-id */ | |
216 | } | |
217 | ||
218 | if (writable) { | |
219 | if (lseek(fd, 0, SEEK_SET) < 0) | |
220 | return log_error_errno(errno, "Failed to seek %s: %m", etc_machine_id); | |
221 | ||
222 | if (ftruncate(fd, 0) < 0) | |
223 | return log_error_errno(errno, "Failed to truncate %s: %m", etc_machine_id); | |
224 | ||
225 | /* If the caller requested a transient machine-id, write the string "uninitialized\n" to | |
226 | * disk and overmount it with a transient file. | |
227 | * | |
228 | * Otherwise write the machine-id directly to disk. */ | |
229 | if (FLAGS_SET(flags, MACHINE_ID_SETUP_FORCE_TRANSIENT)) { | |
230 | r = loop_write(fd, "uninitialized\n", SIZE_MAX); | |
231 | if (r < 0) | |
232 | return log_error_errno(r, "Failed to write uninitialized %s: %m", etc_machine_id); | |
233 | ||
234 | r = fsync_full(fd); | |
235 | if (r < 0) | |
236 | return log_error_errno(r, "Failed to sync %s: %m", etc_machine_id); | |
237 | } else { | |
238 | r = id128_write_fd(fd, ID128_FORMAT_PLAIN | ID128_SYNC_ON_WRITE, machine_id); | |
239 | if (r < 0) | |
240 | return log_error_errno(r, "Failed to write %s: %m", etc_machine_id); | |
241 | ||
242 | goto finish; | |
243 | } | |
244 | } | |
245 | ||
246 | /* Hmm, we couldn't or shouldn't write the machine-id to /etc/? So let's write it to /run/machine-id | |
247 | * as a replacement */ | |
248 | ||
249 | if (write_run_machine_id) { | |
250 | _cleanup_free_ char *run = NULL; | |
251 | ||
252 | r = chase("/run/", root, CHASE_PREFIX_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, &run, &run_fd); | |
253 | if (r < 0) | |
254 | return log_error_errno(r, "Failed to open %s: %m", "/run/"); | |
255 | ||
256 | run_machine_id = path_join(run, "machine-id"); | |
257 | if (!run_machine_id) | |
258 | return log_oom(); | |
259 | ||
260 | WITH_UMASK(0022) { | |
261 | r = id128_write_at(run_fd, "machine-id", ID128_FORMAT_PLAIN, machine_id); | |
262 | if (r < 0) { | |
263 | (void) unlinkat(run_fd, "machine-id", /* flags = */ 0); | |
264 | return log_error_errno(r, "Cannot write '%s': %m", run_machine_id); | |
265 | } | |
266 | } | |
267 | ||
268 | unlink_run_machine_id = true; | |
269 | } else { | |
270 | r = chase("/run/machine-id", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_REGULAR, &run_machine_id, /* ret_fd= */ NULL); | |
271 | if (r < 0) | |
272 | return log_error_errno(r, "Failed to open %s: %m", "/run/machine-id"); | |
273 | } | |
274 | ||
275 | /* And now, let's mount it over */ | |
276 | r = mount_follow_verbose(LOG_ERR, run_machine_id, FORMAT_PROC_FD_PATH(fd), /* fstype= */ NULL, MS_BIND, /* options= */ NULL); | |
277 | if (r < 0) { | |
278 | if (unlink_run_machine_id) | |
279 | (void) unlinkat(ASSERT_FD(run_fd), "machine-id", /* flags = */ 0); | |
280 | return r; | |
281 | } | |
282 | ||
283 | log_full(FLAGS_SET(flags, MACHINE_ID_SETUP_FORCE_TRANSIENT) ? LOG_DEBUG : LOG_INFO, "Installed transient '%s' file.", etc_machine_id); | |
284 | ||
285 | /* Mark the mount read-only (note: we are not going via FORMAT_PROC_FD_PATH() here because that fd is not updated to our new bind mount) */ | |
286 | (void) mount_follow_verbose(LOG_WARNING, /* what= */ NULL, etc_machine_id, /* fstype= */ NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, /* options= */ NULL); | |
287 | ||
288 | finish: | |
289 | if (!in_initrd()) | |
290 | (void) sd_notifyf(/* unset_environment= */ false, "X_SYSTEMD_MACHINE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(machine_id)); | |
291 | ||
292 | if (ret) | |
293 | *ret = machine_id; | |
294 | ||
295 | return 0; | |
296 | } | |
297 | ||
298 | int machine_id_commit(const char *root) { | |
299 | sd_id128_t id; | |
300 | int r; | |
301 | ||
302 | if (empty_or_root(root)) { | |
303 | /* Before doing anything, sync everything to ensure any changes by first-boot units are | |
304 | * persisted. | |
305 | * | |
306 | * First, explicitly sync the file systems we care about and check if it worked. */ | |
307 | FOREACH_STRING(sync_path, "/etc/", "/var/") { | |
308 | r = syncfs_path(AT_FDCWD, sync_path); | |
309 | if (r < 0) | |
310 | return log_error_errno(r, "Cannot sync %s: %m", sync_path); | |
311 | } | |
312 | ||
313 | /* Afterwards, sync() the rest too, but we can't check the return value for these. */ | |
314 | sync(); | |
315 | } | |
316 | ||
317 | /* Replaces a tmpfs bind mount of /etc/machine-id by a proper file, atomically. For this, the umount is removed | |
318 | * in a mount namespace, a new file is created at the right place. Afterwards the mount is also removed in the | |
319 | * original mount namespace, thus revealing the file that was just created. */ | |
320 | ||
321 | _cleanup_close_ int etc_fd = -EBADF; | |
322 | _cleanup_free_ char *etc = NULL; | |
323 | r = chase("/etc/", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_DIRECTORY, &etc, &etc_fd); | |
324 | if (r < 0) | |
325 | return log_error_errno(r, "Failed to open %s: %m", "/etc/"); | |
326 | ||
327 | _cleanup_free_ char *etc_machine_id = path_join(etc, "machine-id"); | |
328 | if (!etc_machine_id) | |
329 | return log_oom(); | |
330 | ||
331 | r = is_mount_point_at(etc_fd, "machine-id", /* flags= */ 0); | |
332 | if (r < 0) | |
333 | return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id); | |
334 | if (r == 0) { | |
335 | log_debug("%s is not a mount point. Nothing to do.", etc_machine_id); | |
336 | return 0; | |
337 | } | |
338 | ||
339 | /* Read existing machine-id */ | |
340 | ||
341 | _cleanup_close_ int fd = xopenat_full(etc_fd, "machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, XO_REGULAR, MODE_INVALID); | |
342 | if (fd < 0) | |
343 | return log_error_errno(fd, "Cannot open %s: %m", etc_machine_id); | |
344 | ||
345 | etc_fd = safe_close(etc_fd); | |
346 | ||
347 | r = fd_is_temporary_fs(fd); | |
348 | if (r < 0) | |
349 | return log_error_errno(r, "Failed to determine whether %s is on a temporary file system: %m", etc_machine_id); | |
350 | if (r == 0) | |
351 | return log_error_errno(SYNTHETIC_ERRNO(EROFS), | |
352 | "%s is not on a temporary file system.", | |
353 | etc_machine_id); | |
354 | ||
355 | r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &id); | |
356 | if (r < 0) | |
357 | return log_error_errno(r, "We didn't find a valid machine ID in %s: %m", etc_machine_id); | |
358 | ||
359 | /* Store current mount namespace */ | |
360 | _cleanup_close_ int initial_mntns_fd = namespace_open_by_type(NAMESPACE_MOUNT); | |
361 | if (initial_mntns_fd < 0) | |
362 | return log_error_errno(initial_mntns_fd, "Can't fetch current mount namespace: %m"); | |
363 | ||
364 | /* Switch to a new mount namespace, isolate ourself and unmount etc_machine_id in our new namespace */ | |
365 | r = detach_mount_namespace(); | |
366 | if (r < 0) | |
367 | return log_error_errno(r, "Failed to set up new mount namespace: %m"); | |
368 | ||
369 | /* Open /etc/ again after we transitioned into our own private mount namespace */ | |
370 | _cleanup_close_ int etc_fd_again = -EBADF; | |
371 | r = chase("/etc/", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &etc_fd_again); | |
372 | if (r < 0) | |
373 | return log_error_errno(r, "Failed to open %s: %m", "/etc/"); | |
374 | ||
375 | r = umountat_detach_verbose(LOG_ERR, etc_fd_again, "machine-id"); | |
376 | if (r < 0) | |
377 | return r; | |
378 | ||
379 | /* Update a persistent version of etc_machine_id */ | |
380 | r = id128_write_at(etc_fd_again, "machine-id", ID128_FORMAT_PLAIN | ID128_SYNC_ON_WRITE, id); | |
381 | if (r < 0) | |
382 | return log_error_errno(r, "Cannot write %s. This is mandatory to get a persistent machine ID: %m", etc_machine_id); | |
383 | ||
384 | etc_fd_again = safe_close(etc_fd_again); | |
385 | ||
386 | /* Return to initial namespace and proceed a lazy tmpfs unmount */ | |
387 | r = namespace_enter(/* pidns_fd = */ -EBADF, | |
388 | initial_mntns_fd, | |
389 | /* netns_fd = */ -EBADF, | |
390 | /* userns_fd = */ -EBADF, | |
391 | /* root_fd = */ -EBADF); | |
392 | if (r < 0) | |
393 | return log_warning_errno(r, | |
394 | "Failed to switch back to initial mount namespace: %m.\n" | |
395 | "We'll keep transient %s file until next reboot.", etc_machine_id); | |
396 | ||
397 | r = umountat_detach_verbose(LOG_DEBUG, fd, /* where= */ NULL); | |
398 | if (r < 0) | |
399 | return log_warning_errno(r, | |
400 | "Failed to unmount transient %s file: %m.\n" | |
401 | "We keep that mount until next reboot.", etc_machine_id); | |
402 | ||
403 | return 0; | |
404 | } |