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