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