]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/virt.c
Merge pull request #7042 from vcaputo/iteratedcache
[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 /* Here, we need to use sscanf() instead of safe_atoul()
226 * as the string lacks the leading "0x". */
227 r = sscanf(domcap, "%lx", &features);
228 if (r == 1) {
229 r = !!(features & (1U << XENFEAT_dom0));
230 log_debug("Virtualization XEN, found %s with value %08lx, "
231 "XENFEAT_dom0 (indicating the 'hardware domain') is%s set.",
232 PATH_FEATURES, features, r ? "" : " not");
233 return r;
234 }
235 log_debug("Virtualization XEN, found %s, unhandled content '%s'",
236 PATH_FEATURES, domcap);
237 }
238
239 r = read_one_line_file("/proc/xen/capabilities", &domcap);
240 if (r == -ENOENT) {
241 log_debug("Virtualization XEN because /proc/xen/capabilities does not exist");
242 return 0;
243 }
244 if (r < 0)
245 return r;
246
247 i = domcap;
248 while ((cap = strsep(&i, ",")))
249 if (streq(cap, "control_d"))
250 break;
251 if (!cap) {
252 log_debug("Virtualization XEN DomU found (/proc/xen/capabilites)");
253 return 0;
254 }
255
256 log_debug("Virtualization XEN Dom0 ignored (/proc/xen/capabilities)");
257 return 1;
258 }
259
260 static int detect_vm_hypervisor(void) {
261 _cleanup_free_ char *hvtype = NULL;
262 int r;
263
264 r = read_one_line_file("/sys/hypervisor/type", &hvtype);
265 if (r == -ENOENT)
266 return VIRTUALIZATION_NONE;
267 if (r < 0)
268 return r;
269
270 log_debug("Virtualization %s found in /sys/hypervisor/type", hvtype);
271
272 if (streq(hvtype, "xen"))
273 return VIRTUALIZATION_XEN;
274 else
275 return VIRTUALIZATION_VM_OTHER;
276 }
277
278 static int detect_vm_uml(void) {
279 _cleanup_free_ char *cpuinfo_contents = NULL;
280 int r;
281
282 /* Detect User-Mode Linux by reading /proc/cpuinfo */
283 r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
284 if (r < 0)
285 return r;
286
287 if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
288 log_debug("UML virtualization found in /proc/cpuinfo");
289 return VIRTUALIZATION_UML;
290 }
291
292 log_debug("No virtualization found in /proc/cpuinfo.");
293 return VIRTUALIZATION_NONE;
294 }
295
296 static int detect_vm_zvm(void) {
297
298 #if defined(__s390__)
299 _cleanup_free_ char *t = NULL;
300 int r;
301
302 r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t);
303 if (r == -ENOENT)
304 return VIRTUALIZATION_NONE;
305 if (r < 0)
306 return r;
307
308 log_debug("Virtualization %s found in /proc/sysinfo", t);
309 if (streq(t, "z/VM"))
310 return VIRTUALIZATION_ZVM;
311 else
312 return VIRTUALIZATION_KVM;
313 #else
314 log_debug("This platform does not support /proc/sysinfo");
315 return VIRTUALIZATION_NONE;
316 #endif
317 }
318
319 /* Returns a short identifier for the various VM implementations */
320 int detect_vm(void) {
321 static thread_local int cached_found = _VIRTUALIZATION_INVALID;
322 int r, dmi;
323 bool other = false;
324
325 if (cached_found >= 0)
326 return cached_found;
327
328 /* We have to use the correct order here:
329 *
330 * -> First try to detect Oracle Virtualbox, even if it uses KVM.
331 * -> Second try to detect from cpuid, this will report KVM for
332 * whatever software is used even if info in dmi is overwritten.
333 * -> Third try to detect from dmi. */
334
335 dmi = detect_vm_dmi();
336 if (dmi == VIRTUALIZATION_ORACLE) {
337 r = dmi;
338 goto finish;
339 }
340
341 r = detect_vm_cpuid();
342 if (r < 0)
343 return r;
344 if (r != VIRTUALIZATION_NONE) {
345 if (r == VIRTUALIZATION_VM_OTHER)
346 other = true;
347 else
348 goto finish;
349 }
350
351 r = dmi;
352 if (r < 0)
353 return r;
354 if (r != VIRTUALIZATION_NONE) {
355 if (r == VIRTUALIZATION_VM_OTHER)
356 other = true;
357 else
358 goto finish;
359 }
360
361 /* x86 xen will most likely be detected by cpuid. If not (most likely
362 * because we're not an x86 guest), then we should try the /proc/xen
363 * directory next. If that's not found, then we check for the high-level
364 * hypervisor sysfs file.
365 */
366
367 r = detect_vm_xen();
368 if (r < 0)
369 return r;
370 if (r != VIRTUALIZATION_NONE) {
371 if (r == VIRTUALIZATION_VM_OTHER)
372 other = true;
373 else
374 goto finish;
375 }
376
377 r = detect_vm_hypervisor();
378 if (r < 0)
379 return r;
380 if (r != VIRTUALIZATION_NONE) {
381 if (r == VIRTUALIZATION_VM_OTHER)
382 other = true;
383 else
384 goto finish;
385 }
386
387 r = detect_vm_device_tree();
388 if (r < 0)
389 return r;
390 if (r != VIRTUALIZATION_NONE) {
391 if (r == VIRTUALIZATION_VM_OTHER)
392 other = true;
393 else
394 goto finish;
395 }
396
397 r = detect_vm_uml();
398 if (r < 0)
399 return r;
400 if (r != VIRTUALIZATION_NONE) {
401 if (r == VIRTUALIZATION_VM_OTHER)
402 other = true;
403 else
404 goto finish;
405 }
406
407 r = detect_vm_zvm();
408 if (r < 0)
409 return r;
410
411 finish:
412 /* x86 xen Dom0 is detected as XEN in hypervisor and maybe others.
413 * In order to detect the Dom0 as not virtualization we need to
414 * double-check it */
415 if (r == VIRTUALIZATION_XEN) {
416 int ret = detect_vm_xen_dom0();
417 if (ret < 0)
418 return ret;
419 if (ret > 0)
420 r = VIRTUALIZATION_NONE;
421 } else if (r == VIRTUALIZATION_NONE && other)
422 r = VIRTUALIZATION_VM_OTHER;
423
424 cached_found = r;
425 log_debug("Found VM virtualization %s", virtualization_to_string(r));
426 return r;
427 }
428
429 int detect_container(void) {
430
431 static const struct {
432 const char *value;
433 int id;
434 } value_table[] = {
435 { "lxc", VIRTUALIZATION_LXC },
436 { "lxc-libvirt", VIRTUALIZATION_LXC_LIBVIRT },
437 { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN },
438 { "docker", VIRTUALIZATION_DOCKER },
439 { "rkt", VIRTUALIZATION_RKT },
440 };
441
442 static thread_local int cached_found = _VIRTUALIZATION_INVALID;
443 _cleanup_free_ char *m = NULL;
444 const char *e = NULL;
445 unsigned j;
446 int r;
447
448 if (cached_found >= 0)
449 return cached_found;
450
451 /* /proc/vz exists in container and outside of the container, /proc/bc only outside of the container. */
452 if (access("/proc/vz", F_OK) >= 0 &&
453 access("/proc/bc", F_OK) < 0) {
454 r = VIRTUALIZATION_OPENVZ;
455 goto finish;
456 }
457
458 if (getpid_cached() == 1) {
459 /* If we are PID 1 we can just check our own environment variable, and that's authoritative. */
460
461 e = getenv("container");
462 if (isempty(e)) {
463 r = VIRTUALIZATION_NONE;
464 goto finish;
465 }
466
467 goto translate_name;
468 }
469
470 /* Otherwise, PID 1 might have dropped this information into a file in /run. This is better than accessing
471 * /proc/1/environ, since we don't need CAP_SYS_PTRACE for that. */
472 r = read_one_line_file("/run/systemd/container", &m);
473 if (r >= 0) {
474 e = m;
475 goto translate_name;
476 }
477 if (r != -ENOENT)
478 return log_debug_errno(r, "Failed to read /run/systemd/container: %m");
479
480 /* Fallback for cases where PID 1 was not systemd (for example, cases where init=/bin/sh is used. */
481 r = getenv_for_pid(1, "container", &m);
482 if (r > 0) {
483 e = m;
484 goto translate_name;
485 }
486 if (r < 0) /* This only works if we have CAP_SYS_PTRACE, hence let's better ignore failures here */
487 log_debug_errno(r, "Failed to read $container of PID 1, ignoring: %m");
488
489 /* Interestingly /proc/1/sched actually shows the host's PID for what we see as PID 1. Hence, if the PID shown
490 * there is not 1, we know we are in a PID namespace. and hence a container. */
491 r = read_one_line_file("/proc/1/sched", &m);
492 if (r >= 0) {
493 const char *t;
494
495 t = strrchr(m, '(');
496 if (!t)
497 return -EIO;
498
499 if (!startswith(t, "(1,")) {
500 r = VIRTUALIZATION_CONTAINER_OTHER;
501 goto finish;
502 }
503 } else if (r != -ENOENT)
504 return r;
505
506 /* If that didn't work, give up, assume no container manager. */
507 r = VIRTUALIZATION_NONE;
508 goto finish;
509
510 translate_name:
511 for (j = 0; j < ELEMENTSOF(value_table); j++)
512 if (streq(e, value_table[j].value)) {
513 r = value_table[j].id;
514 goto finish;
515 }
516
517 r = VIRTUALIZATION_CONTAINER_OTHER;
518
519 finish:
520 log_debug("Found container virtualization %s.", virtualization_to_string(r));
521 cached_found = r;
522 return r;
523 }
524
525 int detect_virtualization(void) {
526 int r;
527
528 r = detect_container();
529 if (r == 0)
530 r = detect_vm();
531
532 return r;
533 }
534
535 static int userns_has_mapping(const char *name) {
536 _cleanup_fclose_ FILE *f = NULL;
537 _cleanup_free_ char *buf = NULL;
538 size_t n_allocated = 0;
539 ssize_t n;
540 uint32_t a, b, c;
541 int r;
542
543 f = fopen(name, "re");
544 if (!f) {
545 log_debug_errno(errno, "Failed to open %s: %m", name);
546 return errno == ENOENT ? false : -errno;
547 }
548
549 n = getline(&buf, &n_allocated, f);
550 if (n < 0) {
551 if (feof(f)) {
552 log_debug("%s is empty, we're in an uninitialized user namespace", name);
553 return true;
554 }
555
556 return log_debug_errno(errno, "Failed to read %s: %m", name);
557 }
558
559 r = sscanf(buf, "%"PRIu32" %"PRIu32" %"PRIu32, &a, &b, &c);
560 if (r < 3)
561 return log_debug_errno(errno, "Failed to parse %s: %m", name);
562
563 if (a == 0 && b == 0 && c == UINT32_MAX) {
564 /* The kernel calls mappings_overlap() and does not allow overlaps */
565 log_debug("%s has a full 1:1 mapping", name);
566 return false;
567 }
568
569 /* Anything else implies that we are in a user namespace */
570 log_debug("Mapping found in %s, we're in a user namespace", name);
571 return true;
572 }
573
574 int running_in_userns(void) {
575 _cleanup_free_ char *line = NULL;
576 int r;
577
578 r = userns_has_mapping("/proc/self/uid_map");
579 if (r != 0)
580 return r;
581
582 r = userns_has_mapping("/proc/self/gid_map");
583 if (r != 0)
584 return r;
585
586 /* "setgroups" file was added in kernel v3.18-rc6-15-g9cc46516dd. It is also
587 * possible to compile a kernel without CONFIG_USER_NS, in which case "setgroups"
588 * also does not exist. We cannot distinguish those two cases, so assume that
589 * we're running on a stripped-down recent kernel, rather than on an old one,
590 * and if the file is not found, return false.
591 */
592 r = read_one_line_file("/proc/self/setgroups", &line);
593 if (r < 0) {
594 log_debug_errno(r, "/proc/self/setgroups: %m");
595 return r == -ENOENT ? false : r;
596 }
597
598 truncate_nl(line);
599 r = streq(line, "deny");
600 /* See user_namespaces(7) for a description of this "setgroups" contents. */
601 log_debug("/proc/self/setgroups contains \"%s\", %s user namespace", line, r ? "in" : "not in");
602 return r;
603 }
604
605 int running_in_chroot(void) {
606 int ret;
607
608 if (getenv_bool("SYSTEMD_IGNORE_CHROOT") > 0)
609 return 0;
610
611 ret = files_same("/proc/1/root", "/", 0);
612 if (ret < 0)
613 return ret;
614
615 return ret == 0;
616 }
617
618 static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
619 [VIRTUALIZATION_NONE] = "none",
620 [VIRTUALIZATION_KVM] = "kvm",
621 [VIRTUALIZATION_QEMU] = "qemu",
622 [VIRTUALIZATION_BOCHS] = "bochs",
623 [VIRTUALIZATION_XEN] = "xen",
624 [VIRTUALIZATION_UML] = "uml",
625 [VIRTUALIZATION_VMWARE] = "vmware",
626 [VIRTUALIZATION_ORACLE] = "oracle",
627 [VIRTUALIZATION_MICROSOFT] = "microsoft",
628 [VIRTUALIZATION_ZVM] = "zvm",
629 [VIRTUALIZATION_PARALLELS] = "parallels",
630 [VIRTUALIZATION_BHYVE] = "bhyve",
631 [VIRTUALIZATION_VM_OTHER] = "vm-other",
632
633 [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
634 [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
635 [VIRTUALIZATION_LXC] = "lxc",
636 [VIRTUALIZATION_OPENVZ] = "openvz",
637 [VIRTUALIZATION_DOCKER] = "docker",
638 [VIRTUALIZATION_RKT] = "rkt",
639 [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
640 };
641
642 DEFINE_STRING_TABLE_LOOKUP(virtualization, int);