chiark / gitweb /
Prep v236 : Add missing SPDX-License-Identifier (2/9) src/basic
[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 #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 #if 0 /// UNNEEDED by elogind
40 static int detect_vm_cpuid(void) {
41
42         /* CPUID is an x86 specific interface. */
43 #if defined(__i386__) || defined(__x86_64__)
44
45         static const struct {
46                 const char *cpuid;
47                 int id;
48         } cpuid_vendor_table[] = {
49                 { "XenVMMXenVMM", VIRTUALIZATION_XEN       },
50                 { "KVMKVMKVM",    VIRTUALIZATION_KVM       },
51                 { "TCGTCGTCGTCG", VIRTUALIZATION_QEMU      },
52                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
53                 { "VMwareVMware", VIRTUALIZATION_VMWARE    },
54                 /* https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/reference/tlfs */
55                 { "Microsoft Hv", VIRTUALIZATION_MICROSOFT },
56                 /* https://wiki.freebsd.org/bhyve */
57                 { "bhyve bhyve ", VIRTUALIZATION_BHYVE     },
58         };
59
60         uint32_t eax, ecx;
61         bool hypervisor;
62
63         /* http://lwn.net/Articles/301888/ */
64
65 #if defined (__i386__)
66 #define REG_a "eax"
67 #define REG_b "ebx"
68 #elif defined (__amd64__)
69 #define REG_a "rax"
70 #define REG_b "rbx"
71 #endif
72
73         /* First detect whether there is a hypervisor */
74         eax = 1;
75         __asm__ __volatile__ (
76                 /* ebx/rbx is being used for PIC! */
77                 "  push %%"REG_b"         \n\t"
78                 "  cpuid                  \n\t"
79                 "  pop %%"REG_b"          \n\t"
80
81                 : "=a" (eax), "=c" (ecx)
82                 : "0" (eax)
83         );
84
85         hypervisor = !!(ecx & 0x80000000U);
86
87         if (hypervisor) {
88                 union {
89                         uint32_t sig32[3];
90                         char text[13];
91                 } sig = {};
92                 unsigned j;
93
94                 /* There is a hypervisor, see what it is */
95                 eax = 0x40000000U;
96                 __asm__ __volatile__ (
97                         /* ebx/rbx is being used for PIC! */
98                         "  push %%"REG_b"         \n\t"
99                         "  cpuid                  \n\t"
100                         "  mov %%ebx, %1          \n\t"
101                         "  pop %%"REG_b"          \n\t"
102
103                         : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
104                         : "0" (eax)
105                 );
106
107                 log_debug("Virtualization found, CPUID=%s", sig.text);
108
109                 for (j = 0; j < ELEMENTSOF(cpuid_vendor_table); j ++)
110                         if (streq(sig.text, cpuid_vendor_table[j].cpuid))
111                                 return cpuid_vendor_table[j].id;
112
113                 return VIRTUALIZATION_VM_OTHER;
114         }
115 #endif
116         log_debug("No virtualization found in CPUID");
117
118         return VIRTUALIZATION_NONE;
119 }
120
121 static int detect_vm_device_tree(void) {
122 #if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__)
123         _cleanup_free_ char *hvtype = NULL;
124         int r;
125
126         r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype);
127         if (r == -ENOENT) {
128                 _cleanup_closedir_ DIR *dir = NULL;
129                 struct dirent *dent;
130
131                 dir = opendir("/proc/device-tree");
132                 if (!dir) {
133                         if (errno == ENOENT) {
134                                 log_debug_errno(errno, "/proc/device-tree: %m");
135                                 return VIRTUALIZATION_NONE;
136                         }
137                         return -errno;
138                 }
139
140                 FOREACH_DIRENT(dent, dir, return -errno)
141                         if (strstr(dent->d_name, "fw-cfg")) {
142                                 log_debug("Virtualization QEMU: \"fw-cfg\" present in /proc/device-tree/%s", dent->d_name);
143                                 return VIRTUALIZATION_QEMU;
144                         }
145
146                 log_debug("No virtualization found in /proc/device-tree/*");
147                 return VIRTUALIZATION_NONE;
148         } else if (r < 0)
149                 return r;
150
151         log_debug("Virtualization %s found in /proc/device-tree/hypervisor/compatible", hvtype);
152         if (streq(hvtype, "linux,kvm"))
153                 return VIRTUALIZATION_KVM;
154         else if (strstr(hvtype, "xen"))
155                 return VIRTUALIZATION_XEN;
156         else
157                 return VIRTUALIZATION_VM_OTHER;
158 #else
159         log_debug("This platform does not support /proc/device-tree");
160         return VIRTUALIZATION_NONE;
161 #endif
162 }
163
164 static int detect_vm_dmi(void) {
165 #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
166
167         static const char *const dmi_vendors[] = {
168                 "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */
169                 "/sys/class/dmi/id/sys_vendor",
170                 "/sys/class/dmi/id/board_vendor",
171                 "/sys/class/dmi/id/bios_vendor"
172         };
173
174         static const struct {
175                 const char *vendor;
176                 int id;
177         } dmi_vendor_table[] = {
178                 { "KVM",           VIRTUALIZATION_KVM       },
179                 { "QEMU",          VIRTUALIZATION_QEMU      },
180                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
181                 { "VMware",        VIRTUALIZATION_VMWARE    },
182                 { "VMW",           VIRTUALIZATION_VMWARE    },
183                 { "innotek GmbH",  VIRTUALIZATION_ORACLE    },
184                 { "Xen",           VIRTUALIZATION_XEN       },
185                 { "Bochs",         VIRTUALIZATION_BOCHS     },
186                 { "Parallels",     VIRTUALIZATION_PARALLELS },
187                 /* https://wiki.freebsd.org/bhyve */
188                 { "BHYVE",         VIRTUALIZATION_BHYVE     },
189         };
190         unsigned i;
191         int r;
192
193         for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
194                 _cleanup_free_ char *s = NULL;
195                 unsigned j;
196
197                 r = read_one_line_file(dmi_vendors[i], &s);
198                 if (r < 0) {
199                         if (r == -ENOENT)
200                                 continue;
201
202                         return r;
203                 }
204
205                 for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++)
206                         if (startswith(s, dmi_vendor_table[j].vendor)) {
207                                 log_debug("Virtualization %s found in DMI (%s)", s, dmi_vendors[i]);
208                                 return dmi_vendor_table[j].id;
209                         }
210         }
211 #endif
212
213         log_debug("No virtualization found in DMI");
214
215         return VIRTUALIZATION_NONE;
216 }
217
218 static int detect_vm_xen(void) {
219
220         /* Check for Dom0 will be executed later in detect_vm_xen_dom0
221            The presence of /proc/xen indicates some form of a Xen domain */
222         if (access("/proc/xen", F_OK) < 0) {
223                 log_debug("Virtualization XEN not found, /proc/xen does not exist");
224                 return VIRTUALIZATION_NONE;
225         }
226
227         log_debug("Virtualization XEN found (/proc/xen exists)");
228         return VIRTUALIZATION_XEN;
229 }
230
231 #define XENFEAT_dom0 11 /* xen/include/public/features.h */
232 #define PATH_FEATURES "/sys/hypervisor/properties/features"
233 /* Returns -errno, or 0 for domU, or 1 for dom0 */
234 static int detect_vm_xen_dom0(void) {
235         _cleanup_free_ char *domcap = NULL;
236         char *cap, *i;
237         int r;
238
239         r = read_one_line_file(PATH_FEATURES, &domcap);
240         if (r < 0 && r != -ENOENT)
241                 return r;
242         if (r == 0) {
243                 unsigned long features;
244
245                 r = safe_atolu(domcap, &features);
246                 if (r == 0) {
247                         r = !!(features & (1U << XENFEAT_dom0));
248                         log_debug("Virtualization XEN, found %s with value %08lx, "
249                                   "XENFEAT_dom0 (indicating the 'hardware domain') is%s set.",
250                                   PATH_FEATURES, features, r ? "" : " not");
251                         return r;
252                 }
253                 log_debug("Virtualization XEN, found %s, unhandled content '%s'",
254                           PATH_FEATURES, domcap);
255         }
256
257         r = read_one_line_file("/proc/xen/capabilities", &domcap);
258         if (r == -ENOENT) {
259                 log_debug("Virtualization XEN because /proc/xen/capabilities does not exist");
260                 return 0;
261         }
262         if (r < 0)
263                 return r;
264
265         i = domcap;
266         while ((cap = strsep(&i, ",")))
267                 if (streq(cap, "control_d"))
268                         break;
269         if (!cap) {
270                 log_debug("Virtualization XEN DomU found (/proc/xen/capabilites)");
271                 return 0;
272         }
273
274         log_debug("Virtualization XEN Dom0 ignored (/proc/xen/capabilities)");
275         return 1;
276 }
277
278 static int detect_vm_hypervisor(void) {
279         _cleanup_free_ char *hvtype = NULL;
280         int r;
281
282         r = read_one_line_file("/sys/hypervisor/type", &hvtype);
283         if (r == -ENOENT)
284                 return VIRTUALIZATION_NONE;
285         if (r < 0)
286                 return r;
287
288         log_debug("Virtualization %s found in /sys/hypervisor/type", hvtype);
289
290         if (streq(hvtype, "xen"))
291                 return VIRTUALIZATION_XEN;
292         else
293                 return VIRTUALIZATION_VM_OTHER;
294 }
295
296 static int detect_vm_uml(void) {
297         _cleanup_free_ char *cpuinfo_contents = NULL;
298         int r;
299
300         /* Detect User-Mode Linux by reading /proc/cpuinfo */
301         r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
302         if (r < 0)
303                 return r;
304
305         if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
306                 log_debug("UML virtualization found in /proc/cpuinfo");
307                 return VIRTUALIZATION_UML;
308         }
309
310         log_debug("No virtualization found in /proc/cpuinfo.");
311         return VIRTUALIZATION_NONE;
312 }
313
314 static int detect_vm_zvm(void) {
315
316 #if defined(__s390__)
317         _cleanup_free_ char *t = NULL;
318         int r;
319
320         r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t);
321         if (r == -ENOENT)
322                 return VIRTUALIZATION_NONE;
323         if (r < 0)
324                 return r;
325
326         log_debug("Virtualization %s found in /proc/sysinfo", t);
327         if (streq(t, "z/VM"))
328                 return VIRTUALIZATION_ZVM;
329         else
330                 return VIRTUALIZATION_KVM;
331 #else
332         log_debug("This platform does not support /proc/sysinfo");
333         return VIRTUALIZATION_NONE;
334 #endif
335 }
336
337 /* Returns a short identifier for the various VM implementations */
338 int detect_vm(void) {
339         static thread_local int cached_found = _VIRTUALIZATION_INVALID;
340         int r, dmi;
341         bool other = false;
342
343         if (cached_found >= 0)
344                 return cached_found;
345
346         /* We have to use the correct order here:
347          *
348          * -> First try to detect Oracle Virtualbox, even if it uses KVM.
349          * -> Second try to detect from cpuid, this will report KVM for
350          *    whatever software is used even if info in dmi is overwritten.
351          * -> Third try to detect from dmi. */
352
353         dmi = detect_vm_dmi();
354         if (dmi == VIRTUALIZATION_ORACLE) {
355                 r = dmi;
356                 goto finish;
357         }
358
359         r = detect_vm_cpuid();
360         if (r < 0)
361                 return r;
362         if (r != VIRTUALIZATION_NONE) {
363                 if (r == VIRTUALIZATION_VM_OTHER)
364                         other = true;
365                 else
366                         goto finish;
367         }
368
369         r = dmi;
370         if (r < 0)
371                 return r;
372         if (r != VIRTUALIZATION_NONE) {
373                 if (r == VIRTUALIZATION_VM_OTHER)
374                         other = true;
375                 else
376                         goto finish;
377         }
378
379         /* x86 xen will most likely be detected by cpuid. If not (most likely
380          * because we're not an x86 guest), then we should try the /proc/xen
381          * directory next. If that's not found, then we check for the high-level
382          * hypervisor sysfs file.
383          */
384
385         r = detect_vm_xen();
386         if (r < 0)
387                 return r;
388         if (r != VIRTUALIZATION_NONE) {
389                 if (r == VIRTUALIZATION_VM_OTHER)
390                         other = true;
391                 else
392                         goto finish;
393         }
394
395         r = detect_vm_hypervisor();
396         if (r < 0)
397                 return r;
398         if (r != VIRTUALIZATION_NONE) {
399                 if (r == VIRTUALIZATION_VM_OTHER)
400                         other = true;
401                 else
402                         goto finish;
403         }
404
405         r = detect_vm_device_tree();
406         if (r < 0)
407                 return r;
408         if (r != VIRTUALIZATION_NONE) {
409                 if (r == VIRTUALIZATION_VM_OTHER)
410                         other = true;
411                 else
412                         goto finish;
413         }
414
415         r = detect_vm_uml();
416         if (r < 0)
417                 return r;
418         if (r != VIRTUALIZATION_NONE) {
419                 if (r == VIRTUALIZATION_VM_OTHER)
420                         other = true;
421                 else
422                         goto finish;
423         }
424
425         r = detect_vm_zvm();
426         if (r < 0)
427                 return r;
428
429 finish:
430         /* x86 xen Dom0 is detected as XEN in hypervisor and maybe others.
431          * In order to detect the Dom0 as not virtualization we need to
432          * double-check it */
433         if (r == VIRTUALIZATION_XEN) {
434                 int ret = detect_vm_xen_dom0();
435                 if (ret < 0)
436                         return ret;
437                 if (ret > 0)
438                         r = VIRTUALIZATION_NONE;
439         } else if (r == VIRTUALIZATION_NONE && other)
440                 r = VIRTUALIZATION_VM_OTHER;
441
442         cached_found = r;
443         log_debug("Found VM virtualization %s", virtualization_to_string(r));
444         return r;
445 }
446 #endif // 0
447
448 int detect_container(void) {
449
450         static const struct {
451                 const char *value;
452                 int id;
453         } value_table[] = {
454                 { "lxc",            VIRTUALIZATION_LXC            },
455                 { "lxc-libvirt",    VIRTUALIZATION_LXC_LIBVIRT    },
456                 { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN },
457                 { "docker",         VIRTUALIZATION_DOCKER         },
458                 { "rkt",            VIRTUALIZATION_RKT            },
459         };
460
461         static thread_local int cached_found = _VIRTUALIZATION_INVALID;
462         _cleanup_free_ char *m = NULL;
463         const char *e = NULL;
464         unsigned j;
465         int r;
466
467         if (cached_found >= 0)
468                 return cached_found;
469
470         /* /proc/vz exists in container and outside of the container, /proc/bc only outside of the container. */
471         if (access("/proc/vz", F_OK) >= 0 &&
472             access("/proc/bc", F_OK) < 0) {
473                 r = VIRTUALIZATION_OPENVZ;
474                 goto finish;
475         }
476
477         if (getpid_cached() == 1) {
478                 /* If we are PID 1 we can just check our own environment variable, and that's authoritative. */
479
480                 e = getenv("container");
481                 if (isempty(e)) {
482                         r = VIRTUALIZATION_NONE;
483                         goto finish;
484                 }
485
486                 goto translate_name;
487         }
488
489         /* Otherwise, PID 1 might have dropped this information into a file in /run. This is better than accessing
490          * /proc/1/environ, since we don't need CAP_SYS_PTRACE for that. */
491         r = read_one_line_file("/run/systemd/container", &m);
492         if (r >= 0) {
493                 e = m;
494                 goto translate_name;
495         }
496         if (r != -ENOENT)
497                 return log_debug_errno(r, "Failed to read /run/systemd/container: %m");
498
499         /* Fallback for cases where PID 1 was not systemd (for example, cases where init=/bin/sh is used. */
500         r = getenv_for_pid(1, "container", &m);
501         if (r > 0) {
502                 e = m;
503                 goto translate_name;
504         }
505         if (r < 0) /* This only works if we have CAP_SYS_PTRACE, hence let's better ignore failures here */
506                 log_debug_errno(r, "Failed to read $container of PID 1, ignoring: %m");
507
508         /* Interestingly /proc/1/sched actually shows the host's PID for what we see as PID 1. Hence, if the PID shown
509          * there is not 1, we know we are in a PID namespace. and hence a container. */
510         r = read_one_line_file("/proc/1/sched", &m);
511         if (r >= 0) {
512                 const char *t;
513
514                 t = strrchr(m, '(');
515                 if (!t)
516                         return -EIO;
517
518                 if (!startswith(t, "(1,")) {
519                         r = VIRTUALIZATION_CONTAINER_OTHER;
520                         goto finish;
521                 }
522         } else if (r != -ENOENT)
523                 return r;
524
525         /* If that didn't work, give up, assume no container manager. */
526         r = VIRTUALIZATION_NONE;
527         goto finish;
528
529 translate_name:
530         for (j = 0; j < ELEMENTSOF(value_table); j++)
531                 if (streq(e, value_table[j].value)) {
532                         r = value_table[j].id;
533                         goto finish;
534                 }
535
536         r = VIRTUALIZATION_CONTAINER_OTHER;
537
538 finish:
539         log_debug("Found container virtualization %s.", virtualization_to_string(r));
540         cached_found = r;
541         return r;
542 }
543
544 #if 0 /// UNNEEDED by elogind
545 int detect_virtualization(void) {
546         int r;
547
548         r = detect_container();
549         if (r == 0)
550                 r = detect_vm();
551
552         return r;
553 }
554
555 static int userns_has_mapping(const char *name) {
556         _cleanup_fclose_ FILE *f = NULL;
557         _cleanup_free_ char *buf = NULL;
558         size_t n_allocated = 0;
559         ssize_t n;
560         uint32_t a, b, c;
561         int r;
562
563         f = fopen(name, "re");
564         if (!f) {
565                 log_debug_errno(errno, "Failed to open %s: %m", name);
566                 return errno == ENOENT ? false : -errno;
567         }
568
569         n = getline(&buf, &n_allocated, f);
570         if (n < 0) {
571                 if (feof(f)) {
572                         log_debug("%s is empty, we're in an uninitialized user namespace", name);
573                         return true;
574                 }
575
576                 return log_debug_errno(errno, "Failed to read %s: %m", name);
577         }
578
579         r = sscanf(buf, "%"PRIu32" %"PRIu32" %"PRIu32, &a, &b, &c);
580         if (r < 3)
581                 return log_debug_errno(errno, "Failed to parse %s: %m", name);
582
583         if (a == 0 && b == 0 && c == UINT32_MAX) {
584                 /* The kernel calls mappings_overlap() and does not allow overlaps */
585                 log_debug("%s has a full 1:1 mapping", name);
586                 return false;
587         }
588
589         /* Anything else implies that we are in a user namespace */
590         log_debug("Mapping found in %s, we're in a user namespace", name);
591         return true;
592 }
593
594 int running_in_userns(void) {
595         _cleanup_free_ char *line = NULL;
596         int r;
597
598         r = userns_has_mapping("/proc/self/uid_map");
599         if (r != 0)
600                 return r;
601
602         r = userns_has_mapping("/proc/self/gid_map");
603         if (r != 0)
604                 return r;
605
606         /* "setgroups" file was added in kernel v3.18-rc6-15-g9cc46516dd. It is also
607          * possible to compile a kernel without CONFIG_USER_NS, in which case "setgroups"
608          * also does not exist. We cannot distinguish those two cases, so assume that
609          * we're running on a stripped-down recent kernel, rather than on an old one,
610          * and if the file is not found, return false.
611          */
612         r = read_one_line_file("/proc/self/setgroups", &line);
613         if (r < 0) {
614                 log_debug_errno(r, "/proc/self/setgroups: %m");
615                 return r == -ENOENT ? false : r;
616         }
617
618         truncate_nl(line);
619         r = streq(line, "deny");
620         /* See user_namespaces(7) for a description of this "setgroups" contents. */
621         log_debug("/proc/self/setgroups contains \"%s\", %s user namespace", line, r ? "in" : "not in");
622         return r;
623 }
624 #endif // 0
625
626 int running_in_chroot(void) {
627         int ret;
628
629 #if 0 /// elogind does not allow to ignore chroots, we are never init!
630         if (getenv_bool("SYSTEMD_IGNORE_CHROOT") > 0)
631                 return 0;
632 #endif // 0
633
634         ret = files_same("/proc/1/root", "/", 0);
635         if (ret < 0)
636                 return ret;
637
638         return ret == 0;
639 }
640
641 static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
642         [VIRTUALIZATION_NONE] = "none",
643         [VIRTUALIZATION_KVM] = "kvm",
644         [VIRTUALIZATION_QEMU] = "qemu",
645         [VIRTUALIZATION_BOCHS] = "bochs",
646         [VIRTUALIZATION_XEN] = "xen",
647         [VIRTUALIZATION_UML] = "uml",
648         [VIRTUALIZATION_VMWARE] = "vmware",
649         [VIRTUALIZATION_ORACLE] = "oracle",
650         [VIRTUALIZATION_MICROSOFT] = "microsoft",
651         [VIRTUALIZATION_ZVM] = "zvm",
652         [VIRTUALIZATION_PARALLELS] = "parallels",
653         [VIRTUALIZATION_BHYVE] = "bhyve",
654         [VIRTUALIZATION_VM_OTHER] = "vm-other",
655
656         [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
657         [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
658         [VIRTUALIZATION_LXC] = "lxc",
659         [VIRTUALIZATION_OPENVZ] = "openvz",
660         [VIRTUALIZATION_DOCKER] = "docker",
661         [VIRTUALIZATION_RKT] = "rkt",
662         [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
663 };
664
665 DEFINE_STRING_TABLE_LOOKUP(virtualization, int);