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