]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/virt.c
virt: propagate errors in detect_vm_xen_dom0 (#7553)
[thirdparty/systemd.git] / src / basic / virt.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2011 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "alloc-util.h"
28 #include "dirent-util.h"
29 #include "env-util.h"
30 #include "fd-util.h"
31 #include "fileio.h"
32 #include "macro.h"
33 #include "process-util.h"
34 #include "stat-util.h"
35 #include "string-table.h"
36 #include "string-util.h"
37 #include "virt.h"
38
39 static int detect_vm_cpuid(void) {
40
41 /* CPUID is an x86 specific interface. */
42 #if defined(__i386__) || defined(__x86_64__)
43
44 static const struct {
45 const char *cpuid;
46 int id;
47 } cpuid_vendor_table[] = {
48 { "XenVMMXenVMM", VIRTUALIZATION_XEN },
49 { "KVMKVMKVM", VIRTUALIZATION_KVM },
50 { "TCGTCGTCGTCG", VIRTUALIZATION_QEMU },
51 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
52 { "VMwareVMware", VIRTUALIZATION_VMWARE },
53 /* https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/reference/tlfs */
54 { "Microsoft Hv", VIRTUALIZATION_MICROSOFT },
55 /* https://wiki.freebsd.org/bhyve */
56 { "bhyve bhyve ", VIRTUALIZATION_BHYVE },
57 };
58
59 uint32_t eax, ecx;
60 bool hypervisor;
61
62 /* http://lwn.net/Articles/301888/ */
63
64 #if defined (__i386__)
65 #define REG_a "eax"
66 #define REG_b "ebx"
67 #elif defined (__amd64__)
68 #define REG_a "rax"
69 #define REG_b "rbx"
70 #endif
71
72 /* First detect whether there is a hypervisor */
73 eax = 1;
74 __asm__ __volatile__ (
75 /* ebx/rbx is being used for PIC! */
76 " push %%"REG_b" \n\t"
77 " cpuid \n\t"
78 " pop %%"REG_b" \n\t"
79
80 : "=a" (eax), "=c" (ecx)
81 : "0" (eax)
82 );
83
84 hypervisor = !!(ecx & 0x80000000U);
85
86 if (hypervisor) {
87 union {
88 uint32_t sig32[3];
89 char text[13];
90 } sig = {};
91 unsigned j;
92
93 /* There is a hypervisor, see what it is */
94 eax = 0x40000000U;
95 __asm__ __volatile__ (
96 /* ebx/rbx is being used for PIC! */
97 " push %%"REG_b" \n\t"
98 " cpuid \n\t"
99 " mov %%ebx, %1 \n\t"
100 " pop %%"REG_b" \n\t"
101
102 : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
103 : "0" (eax)
104 );
105
106 log_debug("Virtualization found, CPUID=%s", sig.text);
107
108 for (j = 0; j < ELEMENTSOF(cpuid_vendor_table); j ++)
109 if (streq(sig.text, cpuid_vendor_table[j].cpuid))
110 return cpuid_vendor_table[j].id;
111
112 return VIRTUALIZATION_VM_OTHER;
113 }
114 #endif
115 log_debug("No virtualization found in CPUID");
116
117 return VIRTUALIZATION_NONE;
118 }
119
120 static int detect_vm_device_tree(void) {
121 #if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__)
122 _cleanup_free_ char *hvtype = NULL;
123 int r;
124
125 r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype);
126 if (r == -ENOENT) {
127 _cleanup_closedir_ DIR *dir = NULL;
128 struct dirent *dent;
129
130 dir = opendir("/proc/device-tree");
131 if (!dir) {
132 if (errno == ENOENT) {
133 log_debug_errno(errno, "/proc/device-tree: %m");
134 return VIRTUALIZATION_NONE;
135 }
136 return -errno;
137 }
138
139 FOREACH_DIRENT(dent, dir, return -errno)
140 if (strstr(dent->d_name, "fw-cfg")) {
141 log_debug("Virtualization QEMU: \"fw-cfg\" present in /proc/device-tree/%s", dent->d_name);
142 return VIRTUALIZATION_QEMU;
143 }
144
145 log_debug("No virtualization found in /proc/device-tree/*");
146 return VIRTUALIZATION_NONE;
147 } else if (r < 0)
148 return r;
149
150 log_debug("Virtualization %s found in /proc/device-tree/hypervisor/compatible", hvtype);
151 if (streq(hvtype, "linux,kvm"))
152 return VIRTUALIZATION_KVM;
153 else if (strstr(hvtype, "xen"))
154 return VIRTUALIZATION_XEN;
155 else
156 return VIRTUALIZATION_VM_OTHER;
157 #else
158 log_debug("This platform does not support /proc/device-tree");
159 return VIRTUALIZATION_NONE;
160 #endif
161 }
162
163 static int detect_vm_dmi(void) {
164 #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
165
166 static const char *const dmi_vendors[] = {
167 "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */
168 "/sys/class/dmi/id/sys_vendor",
169 "/sys/class/dmi/id/board_vendor",
170 "/sys/class/dmi/id/bios_vendor"
171 };
172
173 static const struct {
174 const char *vendor;
175 int id;
176 } dmi_vendor_table[] = {
177 { "KVM", VIRTUALIZATION_KVM },
178 { "QEMU", VIRTUALIZATION_QEMU },
179 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
180 { "VMware", VIRTUALIZATION_VMWARE },
181 { "VMW", VIRTUALIZATION_VMWARE },
182 { "innotek GmbH", VIRTUALIZATION_ORACLE },
183 { "Xen", VIRTUALIZATION_XEN },
184 { "Bochs", VIRTUALIZATION_BOCHS },
185 { "Parallels", VIRTUALIZATION_PARALLELS },
186 /* https://wiki.freebsd.org/bhyve */
187 { "BHYVE", VIRTUALIZATION_BHYVE },
188 };
189 unsigned i;
190 int r;
191
192 for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
193 _cleanup_free_ char *s = NULL;
194 unsigned j;
195
196 r = read_one_line_file(dmi_vendors[i], &s);
197 if (r < 0) {
198 if (r == -ENOENT)
199 continue;
200
201 return r;
202 }
203
204 for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++)
205 if (startswith(s, dmi_vendor_table[j].vendor)) {
206 log_debug("Virtualization %s found in DMI (%s)", s, dmi_vendors[i]);
207 return dmi_vendor_table[j].id;
208 }
209 }
210 #endif
211
212 log_debug("No virtualization found in DMI");
213
214 return VIRTUALIZATION_NONE;
215 }
216
217 static int detect_vm_xen(void) {
218
219 /* Check for Dom0 will be executed later in detect_vm_xen_dom0
220 The presence of /proc/xen indicates some form of a Xen domain */
221 if (access("/proc/xen", F_OK) < 0) {
222 log_debug("Virtualization XEN not found, /proc/xen does not exist");
223 return VIRTUALIZATION_NONE;
224 }
225
226 log_debug("Virtualization XEN found (/proc/xen exists)");
227 return VIRTUALIZATION_XEN;
228 }
229
230 /* Returns -errno, or 0 for domU, or 1 for dom0 */
231 static int detect_vm_xen_dom0(void) {
232 _cleanup_free_ char *domcap = NULL;
233 char *cap, *i;
234 int r;
235
236 r = read_one_line_file("/proc/xen/capabilities", &domcap);
237 if (r == -ENOENT) {
238 log_debug("Virtualization XEN because /proc/xen/capabilities does not exist");
239 return 0;
240 }
241 if (r < 0)
242 return r;
243
244 i = domcap;
245 while ((cap = strsep(&i, ",")))
246 if (streq(cap, "control_d"))
247 break;
248 if (!cap) {
249 log_debug("Virtualization XEN DomU found (/proc/xen/capabilites)");
250 return 0;
251 }
252
253 log_debug("Virtualization XEN Dom0 ignored (/proc/xen/capabilities)");
254 return 1;
255 }
256
257 static int detect_vm_hypervisor(void) {
258 _cleanup_free_ char *hvtype = NULL;
259 int r;
260
261 r = read_one_line_file("/sys/hypervisor/type", &hvtype);
262 if (r == -ENOENT)
263 return VIRTUALIZATION_NONE;
264 if (r < 0)
265 return r;
266
267 log_debug("Virtualization %s found in /sys/hypervisor/type", hvtype);
268
269 if (streq(hvtype, "xen"))
270 return VIRTUALIZATION_XEN;
271 else
272 return VIRTUALIZATION_VM_OTHER;
273 }
274
275 static int detect_vm_uml(void) {
276 _cleanup_free_ char *cpuinfo_contents = NULL;
277 int r;
278
279 /* Detect User-Mode Linux by reading /proc/cpuinfo */
280 r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
281 if (r < 0)
282 return r;
283
284 if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
285 log_debug("UML virtualization found in /proc/cpuinfo");
286 return VIRTUALIZATION_UML;
287 }
288
289 log_debug("No virtualization found in /proc/cpuinfo.");
290 return VIRTUALIZATION_NONE;
291 }
292
293 static int detect_vm_zvm(void) {
294
295 #if defined(__s390__)
296 _cleanup_free_ char *t = NULL;
297 int r;
298
299 r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t);
300 if (r == -ENOENT)
301 return VIRTUALIZATION_NONE;
302 if (r < 0)
303 return r;
304
305 log_debug("Virtualization %s found in /proc/sysinfo", t);
306 if (streq(t, "z/VM"))
307 return VIRTUALIZATION_ZVM;
308 else
309 return VIRTUALIZATION_KVM;
310 #else
311 log_debug("This platform does not support /proc/sysinfo");
312 return VIRTUALIZATION_NONE;
313 #endif
314 }
315
316 /* Returns a short identifier for the various VM implementations */
317 int detect_vm(void) {
318 static thread_local int cached_found = _VIRTUALIZATION_INVALID;
319 int r, dmi;
320 bool other = false;
321
322 if (cached_found >= 0)
323 return cached_found;
324
325 /* We have to use the correct order here:
326 *
327 * -> First try to detect Oracle Virtualbox, even if it uses KVM.
328 * -> Second try to detect from cpuid, this will report KVM for
329 * whatever software is used even if info in dmi is overwritten.
330 * -> Third try to detect from dmi. */
331
332 dmi = detect_vm_dmi();
333 if (dmi == VIRTUALIZATION_ORACLE) {
334 r = dmi;
335 goto finish;
336 }
337
338 r = detect_vm_cpuid();
339 if (r < 0)
340 return r;
341 if (r != VIRTUALIZATION_NONE) {
342 if (r == VIRTUALIZATION_VM_OTHER)
343 other = true;
344 else
345 goto finish;
346 }
347
348 r = dmi;
349 if (r < 0)
350 return r;
351 if (r != VIRTUALIZATION_NONE) {
352 if (r == VIRTUALIZATION_VM_OTHER)
353 other = true;
354 else
355 goto finish;
356 }
357
358 /* x86 xen will most likely be detected by cpuid. If not (most likely
359 * because we're not an x86 guest), then we should try the /proc/xen
360 * directory next. If that's not found, then we check for the high-level
361 * hypervisor sysfs file.
362 */
363
364 r = detect_vm_xen();
365 if (r < 0)
366 return r;
367 if (r != VIRTUALIZATION_NONE) {
368 if (r == VIRTUALIZATION_VM_OTHER)
369 other = true;
370 else
371 goto finish;
372 }
373
374 r = detect_vm_hypervisor();
375 if (r < 0)
376 return r;
377 if (r != VIRTUALIZATION_NONE) {
378 if (r == VIRTUALIZATION_VM_OTHER)
379 other = true;
380 else
381 goto finish;
382 }
383
384 r = detect_vm_device_tree();
385 if (r < 0)
386 return r;
387 if (r != VIRTUALIZATION_NONE) {
388 if (r == VIRTUALIZATION_VM_OTHER)
389 other = true;
390 else
391 goto finish;
392 }
393
394 r = detect_vm_uml();
395 if (r < 0)
396 return r;
397 if (r != VIRTUALIZATION_NONE) {
398 if (r == VIRTUALIZATION_VM_OTHER)
399 other = true;
400 else
401 goto finish;
402 }
403
404 r = detect_vm_zvm();
405 if (r < 0)
406 return r;
407
408 finish:
409 /* x86 xen Dom0 is detected as XEN in hypervisor and maybe others.
410 * In order to detect the Dom0 as not virtualization we need to
411 * double-check it */
412 if (r == VIRTUALIZATION_XEN) {
413 int ret = detect_vm_xen_dom0();
414 if (ret < 0)
415 return ret;
416 if (ret > 0)
417 r = VIRTUALIZATION_NONE;
418 } else if (r == VIRTUALIZATION_NONE && other)
419 r = VIRTUALIZATION_VM_OTHER;
420
421 cached_found = r;
422 log_debug("Found VM virtualization %s", virtualization_to_string(r));
423 return r;
424 }
425
426 int detect_container(void) {
427
428 static const struct {
429 const char *value;
430 int id;
431 } value_table[] = {
432 { "lxc", VIRTUALIZATION_LXC },
433 { "lxc-libvirt", VIRTUALIZATION_LXC_LIBVIRT },
434 { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN },
435 { "docker", VIRTUALIZATION_DOCKER },
436 { "rkt", VIRTUALIZATION_RKT },
437 };
438
439 static thread_local int cached_found = _VIRTUALIZATION_INVALID;
440 _cleanup_free_ char *m = NULL;
441 const char *e = NULL;
442 unsigned j;
443 int r;
444
445 if (cached_found >= 0)
446 return cached_found;
447
448 /* /proc/vz exists in container and outside of the container, /proc/bc only outside of the container. */
449 if (access("/proc/vz", F_OK) >= 0 &&
450 access("/proc/bc", F_OK) < 0) {
451 r = VIRTUALIZATION_OPENVZ;
452 goto finish;
453 }
454
455 if (getpid_cached() == 1) {
456 /* If we are PID 1 we can just check our own environment variable, and that's authoritative. */
457
458 e = getenv("container");
459 if (isempty(e)) {
460 r = VIRTUALIZATION_NONE;
461 goto finish;
462 }
463
464 goto translate_name;
465 }
466
467 /* Otherwise, PID 1 might have dropped this information into a file in /run. This is better than accessing
468 * /proc/1/environ, since we don't need CAP_SYS_PTRACE for that. */
469 r = read_one_line_file("/run/systemd/container", &m);
470 if (r >= 0) {
471 e = m;
472 goto translate_name;
473 }
474 if (r != -ENOENT)
475 return log_debug_errno(r, "Failed to read /run/systemd/container: %m");
476
477 /* Fallback for cases where PID 1 was not systemd (for example, cases where init=/bin/sh is used. */
478 r = getenv_for_pid(1, "container", &m);
479 if (r > 0) {
480 e = m;
481 goto translate_name;
482 }
483 if (r < 0) /* This only works if we have CAP_SYS_PTRACE, hence let's better ignore failures here */
484 log_debug_errno(r, "Failed to read $container of PID 1, ignoring: %m");
485
486 /* Interestingly /proc/1/sched actually shows the host's PID for what we see as PID 1. Hence, if the PID shown
487 * there is not 1, we know we are in a PID namespace. and hence a container. */
488 r = read_one_line_file("/proc/1/sched", &m);
489 if (r >= 0) {
490 const char *t;
491
492 t = strrchr(m, '(');
493 if (!t)
494 return -EIO;
495
496 if (!startswith(t, "(1,")) {
497 r = VIRTUALIZATION_CONTAINER_OTHER;
498 goto finish;
499 }
500 } else if (r != -ENOENT)
501 return r;
502
503 /* If that didn't work, give up, assume no container manager. */
504 r = VIRTUALIZATION_NONE;
505 goto finish;
506
507 translate_name:
508 for (j = 0; j < ELEMENTSOF(value_table); j++)
509 if (streq(e, value_table[j].value)) {
510 r = value_table[j].id;
511 goto finish;
512 }
513
514 r = VIRTUALIZATION_CONTAINER_OTHER;
515
516 finish:
517 log_debug("Found container virtualization %s.", virtualization_to_string(r));
518 cached_found = r;
519 return r;
520 }
521
522 int detect_virtualization(void) {
523 int r;
524
525 r = detect_container();
526 if (r == 0)
527 r = detect_vm();
528
529 return r;
530 }
531
532 static int userns_has_mapping(const char *name) {
533 _cleanup_fclose_ FILE *f = NULL;
534 _cleanup_free_ char *buf = NULL;
535 size_t n_allocated = 0;
536 ssize_t n;
537 uint32_t a, b, c;
538 int r;
539
540 f = fopen(name, "re");
541 if (!f) {
542 log_debug_errno(errno, "Failed to open %s: %m", name);
543 return errno == ENOENT ? false : -errno;
544 }
545
546 n = getline(&buf, &n_allocated, f);
547 if (n < 0) {
548 if (feof(f)) {
549 log_debug("%s is empty, we're in an uninitialized user namespace", name);
550 return true;
551 }
552
553 return log_debug_errno(errno, "Failed to read %s: %m", name);
554 }
555
556 r = sscanf(buf, "%"PRIu32" %"PRIu32" %"PRIu32, &a, &b, &c);
557 if (r < 3)
558 return log_debug_errno(errno, "Failed to parse %s: %m", name);
559
560 if (a == 0 && b == 0 && c == UINT32_MAX) {
561 /* The kernel calls mappings_overlap() and does not allow overlaps */
562 log_debug("%s has a full 1:1 mapping", name);
563 return false;
564 }
565
566 /* Anything else implies that we are in a user namespace */
567 log_debug("Mapping found in %s, we're in a user namespace", name);
568 return true;
569 }
570
571 int running_in_userns(void) {
572 _cleanup_free_ char *line = NULL;
573 int r;
574
575 r = userns_has_mapping("/proc/self/uid_map");
576 if (r != 0)
577 return r;
578
579 r = userns_has_mapping("/proc/self/gid_map");
580 if (r != 0)
581 return r;
582
583 /* "setgroups" file was added in kernel v3.18-rc6-15-g9cc46516dd. It is also
584 * possible to compile a kernel without CONFIG_USER_NS, in which case "setgroups"
585 * also does not exist. We cannot distinguish those two cases, so assume that
586 * we're running on a stripped-down recent kernel, rather than on an old one,
587 * and if the file is not found, return false.
588 */
589 r = read_one_line_file("/proc/self/setgroups", &line);
590 if (r < 0) {
591 log_debug_errno(r, "/proc/self/setgroups: %m");
592 return r == -ENOENT ? false : r;
593 }
594
595 truncate_nl(line);
596 r = streq(line, "deny");
597 /* See user_namespaces(7) for a description of this "setgroups" contents. */
598 log_debug("/proc/self/setgroups contains \"%s\", %s user namespace", line, r ? "in" : "not in");
599 return r;
600 }
601
602 int running_in_chroot(void) {
603 int ret;
604
605 if (getenv_bool("SYSTEMD_IGNORE_CHROOT") > 0)
606 return 0;
607
608 ret = files_same("/proc/1/root", "/", 0);
609 if (ret < 0)
610 return ret;
611
612 return ret == 0;
613 }
614
615 static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
616 [VIRTUALIZATION_NONE] = "none",
617 [VIRTUALIZATION_KVM] = "kvm",
618 [VIRTUALIZATION_QEMU] = "qemu",
619 [VIRTUALIZATION_BOCHS] = "bochs",
620 [VIRTUALIZATION_XEN] = "xen",
621 [VIRTUALIZATION_UML] = "uml",
622 [VIRTUALIZATION_VMWARE] = "vmware",
623 [VIRTUALIZATION_ORACLE] = "oracle",
624 [VIRTUALIZATION_MICROSOFT] = "microsoft",
625 [VIRTUALIZATION_ZVM] = "zvm",
626 [VIRTUALIZATION_PARALLELS] = "parallels",
627 [VIRTUALIZATION_BHYVE] = "bhyve",
628 [VIRTUALIZATION_VM_OTHER] = "vm-other",
629
630 [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
631 [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
632 [VIRTUALIZATION_LXC] = "lxc",
633 [VIRTUALIZATION_OPENVZ] = "openvz",
634 [VIRTUALIZATION_DOCKER] = "docker",
635 [VIRTUALIZATION_RKT] = "rkt",
636 [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
637 };
638
639 DEFINE_STRING_TABLE_LOOKUP(virtualization, int);