chiark / gitweb /
basic: detect_vm_cpuid: use gcc's __get_cpuid() function (#7758)
[elogind.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 #if 0 /// UNNEEDED by elogind
43 static int detect_vm_cpuid(void) {
44
45         /* CPUID is an x86 specific interface. */
46 #if defined(__i386__) || defined(__x86_64__)
47
48         static const struct {
49                 const char *cpuid;
50                 int id;
51         } cpuid_vendor_table[] = {
52                 { "XenVMMXenVMM", VIRTUALIZATION_XEN       },
53                 { "KVMKVMKVM",    VIRTUALIZATION_KVM       },
54                 { "TCGTCGTCGTCG", VIRTUALIZATION_QEMU      },
55                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
56                 { "VMwareVMware", VIRTUALIZATION_VMWARE    },
57                 /* https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/reference/tlfs */
58                 { "Microsoft Hv", VIRTUALIZATION_MICROSOFT },
59                 /* https://wiki.freebsd.org/bhyve */
60                 { "bhyve bhyve ", VIRTUALIZATION_BHYVE     },
61         };
62
63         uint32_t eax, ebx, ecx, edx;
64         bool hypervisor;
65
66         /* http://lwn.net/Articles/301888/ */
67
68         /* First detect whether there is a hypervisor */
69         if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
70                 return VIRTUALIZATION_NONE;
71
72         hypervisor = !!(ecx & 0x80000000U);
73
74         if (hypervisor) {
75                 union {
76                         uint32_t sig32[3];
77                         char text[13];
78                 } sig = {};
79                 unsigned j;
80
81                 /* There is a hypervisor, see what it is */
82                 if (__get_cpuid(0x40000000U, &eax, &ebx, &ecx, &edx) == 0)
83                         return VIRTUALIZATION_NONE;
84
85                 sig.sig32[0] = ebx;
86                 sig.sig32[1] = ecx;
87                 sig.sig32[2] = edx;
88
89                 log_debug("Virtualization found, CPUID=%s", sig.text);
90
91                 for (j = 0; j < ELEMENTSOF(cpuid_vendor_table); j ++)
92                         if (streq(sig.text, cpuid_vendor_table[j].cpuid))
93                                 return cpuid_vendor_table[j].id;
94
95                 return VIRTUALIZATION_VM_OTHER;
96         }
97 #endif
98         log_debug("No virtualization found in CPUID");
99
100         return VIRTUALIZATION_NONE;
101 }
102
103 static int detect_vm_device_tree(void) {
104 #if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__)
105         _cleanup_free_ char *hvtype = NULL;
106         int r;
107
108         r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype);
109         if (r == -ENOENT) {
110                 _cleanup_closedir_ DIR *dir = NULL;
111                 struct dirent *dent;
112
113                 dir = opendir("/proc/device-tree");
114                 if (!dir) {
115                         if (errno == ENOENT) {
116                                 log_debug_errno(errno, "/proc/device-tree: %m");
117                                 return VIRTUALIZATION_NONE;
118                         }
119                         return -errno;
120                 }
121
122                 FOREACH_DIRENT(dent, dir, return -errno)
123                         if (strstr(dent->d_name, "fw-cfg")) {
124                                 log_debug("Virtualization QEMU: \"fw-cfg\" present in /proc/device-tree/%s", dent->d_name);
125                                 return VIRTUALIZATION_QEMU;
126                         }
127
128                 log_debug("No virtualization found in /proc/device-tree/*");
129                 return VIRTUALIZATION_NONE;
130         } else if (r < 0)
131                 return r;
132
133         log_debug("Virtualization %s found in /proc/device-tree/hypervisor/compatible", hvtype);
134         if (streq(hvtype, "linux,kvm"))
135                 return VIRTUALIZATION_KVM;
136         else if (strstr(hvtype, "xen"))
137                 return VIRTUALIZATION_XEN;
138         else
139                 return VIRTUALIZATION_VM_OTHER;
140 #else
141         log_debug("This platform does not support /proc/device-tree");
142         return VIRTUALIZATION_NONE;
143 #endif
144 }
145
146 static int detect_vm_dmi(void) {
147 #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
148
149         static const char *const dmi_vendors[] = {
150                 "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */
151                 "/sys/class/dmi/id/sys_vendor",
152                 "/sys/class/dmi/id/board_vendor",
153                 "/sys/class/dmi/id/bios_vendor"
154         };
155
156         static const struct {
157                 const char *vendor;
158                 int id;
159         } dmi_vendor_table[] = {
160                 { "KVM",           VIRTUALIZATION_KVM       },
161                 { "QEMU",          VIRTUALIZATION_QEMU      },
162                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
163                 { "VMware",        VIRTUALIZATION_VMWARE    },
164                 { "VMW",           VIRTUALIZATION_VMWARE    },
165                 { "innotek GmbH",  VIRTUALIZATION_ORACLE    },
166                 { "Xen",           VIRTUALIZATION_XEN       },
167                 { "Bochs",         VIRTUALIZATION_BOCHS     },
168                 { "Parallels",     VIRTUALIZATION_PARALLELS },
169                 /* https://wiki.freebsd.org/bhyve */
170                 { "BHYVE",         VIRTUALIZATION_BHYVE     },
171         };
172         unsigned i;
173         int r;
174
175         for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
176                 _cleanup_free_ char *s = NULL;
177                 unsigned j;
178
179                 r = read_one_line_file(dmi_vendors[i], &s);
180                 if (r < 0) {
181                         if (r == -ENOENT)
182                                 continue;
183
184                         return r;
185                 }
186
187                 for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++)
188                         if (startswith(s, dmi_vendor_table[j].vendor)) {
189                                 log_debug("Virtualization %s found in DMI (%s)", s, dmi_vendors[i]);
190                                 return dmi_vendor_table[j].id;
191                         }
192         }
193 #endif
194
195         log_debug("No virtualization found in DMI");
196
197         return VIRTUALIZATION_NONE;
198 }
199
200 static int detect_vm_xen(void) {
201
202         /* Check for Dom0 will be executed later in detect_vm_xen_dom0
203            The presence of /proc/xen indicates some form of a Xen domain */
204         if (access("/proc/xen", F_OK) < 0) {
205                 log_debug("Virtualization XEN not found, /proc/xen does not exist");
206                 return VIRTUALIZATION_NONE;
207         }
208
209         log_debug("Virtualization XEN found (/proc/xen exists)");
210         return VIRTUALIZATION_XEN;
211 }
212
213 #define XENFEAT_dom0 11 /* xen/include/public/features.h */
214 #define PATH_FEATURES "/sys/hypervisor/properties/features"
215 /* Returns -errno, or 0 for domU, or 1 for dom0 */
216 static int detect_vm_xen_dom0(void) {
217         _cleanup_free_ char *domcap = NULL;
218         char *cap, *i;
219         int r;
220
221         r = read_one_line_file(PATH_FEATURES, &domcap);
222         if (r < 0 && r != -ENOENT)
223                 return r;
224         if (r == 0) {
225                 unsigned long features;
226
227                 r = safe_atolu(domcap, &features);
228                 if (r == 0) {
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 #endif // 0
429
430 int detect_container(void) {
431
432         static const struct {
433                 const char *value;
434                 int id;
435         } value_table[] = {
436                 { "lxc",            VIRTUALIZATION_LXC            },
437                 { "lxc-libvirt",    VIRTUALIZATION_LXC_LIBVIRT    },
438                 { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN },
439                 { "docker",         VIRTUALIZATION_DOCKER         },
440                 { "rkt",            VIRTUALIZATION_RKT            },
441         };
442
443         static thread_local int cached_found = _VIRTUALIZATION_INVALID;
444         _cleanup_free_ char *m = NULL;
445         const char *e = NULL;
446         unsigned j;
447         int r;
448
449         if (cached_found >= 0)
450                 return cached_found;
451
452         /* /proc/vz exists in container and outside of the container, /proc/bc only outside of the container. */
453         if (access("/proc/vz", F_OK) >= 0 &&
454             access("/proc/bc", F_OK) < 0) {
455                 r = VIRTUALIZATION_OPENVZ;
456                 goto finish;
457         }
458
459         if (getpid_cached() == 1) {
460                 /* If we are PID 1 we can just check our own environment variable, and that's authoritative. */
461
462                 e = getenv("container");
463                 if (isempty(e)) {
464                         r = VIRTUALIZATION_NONE;
465                         goto finish;
466                 }
467
468                 goto translate_name;
469         }
470
471         /* Otherwise, PID 1 might have dropped this information into a file in /run. This is better than accessing
472          * /proc/1/environ, since we don't need CAP_SYS_PTRACE for that. */
473         r = read_one_line_file("/run/systemd/container", &m);
474         if (r >= 0) {
475                 e = m;
476                 goto translate_name;
477         }
478         if (r != -ENOENT)
479                 return log_debug_errno(r, "Failed to read /run/systemd/container: %m");
480
481         /* Fallback for cases where PID 1 was not systemd (for example, cases where init=/bin/sh is used. */
482         r = getenv_for_pid(1, "container", &m);
483         if (r > 0) {
484                 e = m;
485                 goto translate_name;
486         }
487         if (r < 0) /* This only works if we have CAP_SYS_PTRACE, hence let's better ignore failures here */
488                 log_debug_errno(r, "Failed to read $container of PID 1, ignoring: %m");
489
490         /* Interestingly /proc/1/sched actually shows the host's PID for what we see as PID 1. Hence, if the PID shown
491          * there is not 1, we know we are in a PID namespace. and hence a container. */
492         r = read_one_line_file("/proc/1/sched", &m);
493         if (r >= 0) {
494                 const char *t;
495
496                 t = strrchr(m, '(');
497                 if (!t)
498                         return -EIO;
499
500                 if (!startswith(t, "(1,")) {
501                         r = VIRTUALIZATION_CONTAINER_OTHER;
502                         goto finish;
503                 }
504         } else if (r != -ENOENT)
505                 return r;
506
507         /* If that didn't work, give up, assume no container manager. */
508         r = VIRTUALIZATION_NONE;
509         goto finish;
510
511 translate_name:
512         for (j = 0; j < ELEMENTSOF(value_table); j++)
513                 if (streq(e, value_table[j].value)) {
514                         r = value_table[j].id;
515                         goto finish;
516                 }
517
518         r = VIRTUALIZATION_CONTAINER_OTHER;
519
520 finish:
521         log_debug("Found container virtualization %s.", virtualization_to_string(r));
522         cached_found = r;
523         return r;
524 }
525
526 #if 0 /// UNNEEDED by elogind
527 int detect_virtualization(void) {
528         int r;
529
530         r = detect_container();
531         if (r == 0)
532                 r = detect_vm();
533
534         return r;
535 }
536
537 static int userns_has_mapping(const char *name) {
538         _cleanup_fclose_ FILE *f = NULL;
539         _cleanup_free_ char *buf = NULL;
540         size_t n_allocated = 0;
541         ssize_t n;
542         uint32_t a, b, c;
543         int r;
544
545         f = fopen(name, "re");
546         if (!f) {
547                 log_debug_errno(errno, "Failed to open %s: %m", name);
548                 return errno == ENOENT ? false : -errno;
549         }
550
551         n = getline(&buf, &n_allocated, f);
552         if (n < 0) {
553                 if (feof(f)) {
554                         log_debug("%s is empty, we're in an uninitialized user namespace", name);
555                         return true;
556                 }
557
558                 return log_debug_errno(errno, "Failed to read %s: %m", name);
559         }
560
561         r = sscanf(buf, "%"PRIu32" %"PRIu32" %"PRIu32, &a, &b, &c);
562         if (r < 3)
563                 return log_debug_errno(errno, "Failed to parse %s: %m", name);
564
565         if (a == 0 && b == 0 && c == UINT32_MAX) {
566                 /* The kernel calls mappings_overlap() and does not allow overlaps */
567                 log_debug("%s has a full 1:1 mapping", name);
568                 return false;
569         }
570
571         /* Anything else implies that we are in a user namespace */
572         log_debug("Mapping found in %s, we're in a user namespace", name);
573         return true;
574 }
575
576 int running_in_userns(void) {
577         _cleanup_free_ char *line = NULL;
578         int r;
579
580         r = userns_has_mapping("/proc/self/uid_map");
581         if (r != 0)
582                 return r;
583
584         r = userns_has_mapping("/proc/self/gid_map");
585         if (r != 0)
586                 return r;
587
588         /* "setgroups" file was added in kernel v3.18-rc6-15-g9cc46516dd. It is also
589          * possible to compile a kernel without CONFIG_USER_NS, in which case "setgroups"
590          * also does not exist. We cannot distinguish those two cases, so assume that
591          * we're running on a stripped-down recent kernel, rather than on an old one,
592          * and if the file is not found, return false.
593          */
594         r = read_one_line_file("/proc/self/setgroups", &line);
595         if (r < 0) {
596                 log_debug_errno(r, "/proc/self/setgroups: %m");
597                 return r == -ENOENT ? false : r;
598         }
599
600         truncate_nl(line);
601         r = streq(line, "deny");
602         /* See user_namespaces(7) for a description of this "setgroups" contents. */
603         log_debug("/proc/self/setgroups contains \"%s\", %s user namespace", line, r ? "in" : "not in");
604         return r;
605 }
606 #endif // 0
607
608 int running_in_chroot(void) {
609         int ret;
610
611 #if 0 /// elogind does not allow to ignore chroots, we are never init!
612         if (getenv_bool("SYSTEMD_IGNORE_CHROOT") > 0)
613                 return 0;
614 #endif // 0
615
616         ret = files_same("/proc/1/root", "/", 0);
617         if (ret < 0)
618                 return ret;
619
620         return ret == 0;
621 }
622
623 static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
624         [VIRTUALIZATION_NONE] = "none",
625         [VIRTUALIZATION_KVM] = "kvm",
626         [VIRTUALIZATION_QEMU] = "qemu",
627         [VIRTUALIZATION_BOCHS] = "bochs",
628         [VIRTUALIZATION_XEN] = "xen",
629         [VIRTUALIZATION_UML] = "uml",
630         [VIRTUALIZATION_VMWARE] = "vmware",
631         [VIRTUALIZATION_ORACLE] = "oracle",
632         [VIRTUALIZATION_MICROSOFT] = "microsoft",
633         [VIRTUALIZATION_ZVM] = "zvm",
634         [VIRTUALIZATION_PARALLELS] = "parallels",
635         [VIRTUALIZATION_BHYVE] = "bhyve",
636         [VIRTUALIZATION_VM_OTHER] = "vm-other",
637
638         [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
639         [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
640         [VIRTUALIZATION_LXC] = "lxc",
641         [VIRTUALIZATION_OPENVZ] = "openvz",
642         [VIRTUALIZATION_DOCKER] = "docker",
643         [VIRTUALIZATION_RKT] = "rkt",
644         [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
645 };
646
647 DEFINE_STRING_TABLE_LOOKUP(virtualization, int);