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