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