• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 21269554371

22 Jan 2026 06:13PM UTC coverage: 72.571% (-0.2%) from 72.798%
21269554371

push

github

YHNdnzj
pam_systemd: fix regression introduced in v258 by preserving the FIFO fd

Upstream commit 3180c4d introduced a version incompatibility between
pam_systemd.so v258 and logind v257. This is problematic because such version
mismatches can occur in practice: logind still cannot be restarted during a
systemd package upgrade (it's a long-standing limitation, see
https://github.com/systemd/systemd/issues/17308).

When pam_systemd requests a new session, logind v257 returns a FIFO
fd. pam_systemd.so v258 ignores this fd and closes it. logind interprets the
closure as the session leader exiting and immediately terminates the session.

This patch partially reverts commit 3180c4d and restores the handling of the
FIFO fd in pam_systemd. The change is limited to the D-Bus APIs, since the
varlink API was only introduced in logind v258.

Follow-up for 3180c4d46.

0 of 9 new or added lines in 1 file covered. (0.0%)

1194 existing lines in 35 files now uncovered.

310232 of 427490 relevant lines covered (72.57%)

1165572.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.67
/src/basic/virt.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#if defined(__i386__) || defined(__x86_64__)
4
#include <cpuid.h>
5
#endif
6
#include <stdlib.h>
7
#include <threads.h>
8
#include <unistd.h>
9

10
#include "alloc-util.h"
11
#include "dirent-util.h"        /* IWYU pragma: keep */
12
#include "env-util.h"
13
#include "extract-word.h"
14
#include "fd-util.h"
15
#include "fileio.h"
16
#include "log.h"
17
#include "namespace-util.h"
18
#include "parse-util.h"
19
#include "process-util.h"
20
#include "stat-util.h"
21
#include "string-table.h"
22
#include "string-util.h"
23
#include "strv.h"
24
#include "virt.h"
25

26
enum {
27
      SMBIOS_VM_BIT_SET,
28
      SMBIOS_VM_BIT_UNSET,
29
      SMBIOS_VM_BIT_UNKNOWN,
30
};
31

32
static Virtualization detect_vm_cpuid(void) {
577✔
33

34
        /* CPUID is an x86 specific interface. */
35
#if defined(__i386__) || defined(__x86_64__)
36

37
        static const struct {
577✔
38
                const char sig[13];
39
                Virtualization id;
40
        } vm_table[] = {
41
                { "XenVMMXenVMM", VIRTUALIZATION_XEN       },
42
                { "KVMKVMKVM",    VIRTUALIZATION_KVM       }, /* qemu with KVM */
43
                { "Linux KVM Hv", VIRTUALIZATION_KVM       }, /* qemu with KVM + HyperV Enlightenments */
44
                { "TCGTCGTCGTCG", VIRTUALIZATION_QEMU      }, /* qemu without KVM */
45
                /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
46
                { "VMwareVMware", VIRTUALIZATION_VMWARE    },
47
                /* https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/reference/tlfs */
48
                { "Microsoft Hv", VIRTUALIZATION_MICROSOFT },
49
                /* https://wiki.freebsd.org/bhyve */
50
                { "bhyve bhyve ", VIRTUALIZATION_BHYVE     },
51
                { "QNXQVMBSQG",   VIRTUALIZATION_QNX       },
52
                /* https://projectacrn.org */
53
                { "ACRNACRNACRN", VIRTUALIZATION_ACRN      },
54
                /* https://www.lockheedmartin.com/en-us/products/Hardened-Security-for-Intel-Processors.html */
55
                { "SRESRESRESRE", VIRTUALIZATION_SRE       },
56
                { "Apple VZ",     VIRTUALIZATION_APPLE     },
57
        };
58

59
        uint32_t eax, ebx, ecx, edx;
577✔
60
        bool hypervisor;
577✔
61

62
        /* http://lwn.net/Articles/301888/ */
63

64
        /* First detect whether there is a hypervisor */
65
        if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
577✔
66
                return VIRTUALIZATION_NONE;
577✔
67

68
        hypervisor = ecx & 0x80000000U;
577✔
69

70
        if (hypervisor) {
577✔
71
                union {
577✔
72
                        uint32_t sig32[3];
73
                        char text[13];
74
                } sig = {};
577✔
75

76
                /* There is a hypervisor, see what it is */
77
                __cpuid(0x40000000U, eax, ebx, ecx, edx);
577✔
78

79
                sig.sig32[0] = ebx;
577✔
80
                sig.sig32[1] = ecx;
577✔
81
                sig.sig32[2] = edx;
577✔
82

83
                log_debug("Virtualization found, CPUID=%s", sig.text);
577✔
84

85
                FOREACH_ELEMENT(vm, vm_table)
1,170✔
86
                        if (memcmp_nn(sig.text, sizeof(sig.text),
593✔
87
                                      vm->sig, sizeof(vm->sig)) == 0)
1,170✔
88
                                return vm->id;
577✔
89

90
                log_debug("Unknown virtualization with CPUID=%s. Add to vm_table[]?", sig.text);
×
91
                return VIRTUALIZATION_VM_OTHER;
×
92
        }
93
#endif
94
        log_debug("No virtualization found in CPUID");
×
95

96
        return VIRTUALIZATION_NONE;
97
}
98

UNCOV
99
static Virtualization detect_vm_device_tree(void) {
×
100
#if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__) || defined(__riscv)
101
        _cleanup_free_ char *hvtype = NULL;
102
        int r;
103

104
        r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype);
105
        if (r == -ENOENT) {
106
                if (access("/proc/device-tree/ibm,partition-name", F_OK) == 0 &&
107
                    access("/proc/device-tree/hmc-managed?", F_OK) == 0 &&
108
                    access("/proc/device-tree/chosen/qemu,graphic-width", F_OK) != 0)
109
                        return VIRTUALIZATION_POWERVM;
110

111
                _cleanup_closedir_ DIR *dir = opendir("/proc/device-tree");
112
                if (!dir) {
113
                        if (errno == ENOENT) {
114
                                log_debug_errno(errno, "/proc/device-tree/ does not exist");
115
                                return VIRTUALIZATION_NONE;
116
                        }
117
                        return log_debug_errno(errno, "Opening /proc/device-tree/ failed: %m");
118
                }
119

120
                FOREACH_DIRENT(de, dir, return log_debug_errno(errno, "Failed to enumerate /proc/device-tree/ contents: %m"))
121
                        if (strstr(de->d_name, "fw-cfg")) {
122
                                log_debug("Virtualization QEMU: \"fw-cfg\" present in /proc/device-tree/%s", de->d_name);
123
                                return VIRTUALIZATION_QEMU;
124
                        }
125

126
                _cleanup_free_ char *compat = NULL;
127
                r = read_one_line_file("/proc/device-tree/compatible", &compat);
128
                if (r < 0 && r != -ENOENT)
129
                        return log_debug_errno(r, "Failed to read /proc/device-tree/compatible: %m");
130
                if (r >= 0) {
131
                        if (streq(compat, "qemu,pseries")) {
132
                                log_debug("Virtualization %s found in /proc/device-tree/compatible", compat);
133
                                return VIRTUALIZATION_QEMU;
134
                        }
135
                        if (streq(compat, "linux,dummy-virt")) {
136
                                /* https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/linux%2Cdummy-virt.yaml */
137
                                log_debug("Generic virtualization %s found in /proc/device-tree/compatible", compat);
138
                                return VIRTUALIZATION_VM_OTHER;
139
                        }
140
                }
141

142
                log_debug("No virtualization found in /proc/device-tree/*");
143
                return VIRTUALIZATION_NONE;
144
        } else if (r < 0)
145
                return r;
146

147
        log_debug("Virtualization %s found in /proc/device-tree/hypervisor/compatible", hvtype);
148
        if (streq(hvtype, "linux,kvm"))
149
                return VIRTUALIZATION_KVM;
150
        else if (strstr(hvtype, "xen"))
151
                return VIRTUALIZATION_XEN;
152
        else if (strstr(hvtype, "vmware"))
153
                return VIRTUALIZATION_VMWARE;
154
        else
155
                return VIRTUALIZATION_VM_OTHER;
156
#else
UNCOV
157
        log_debug("This platform does not support /proc/device-tree");
×
UNCOV
158
        return VIRTUALIZATION_NONE;
×
159
#endif
160
}
161

162
#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) || defined(__riscv)
163
static Virtualization detect_vm_dmi_vendor(void) {
577✔
164
        static const char* const dmi_vendors[] = {
577✔
165
                "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */
166
                "/sys/class/dmi/id/sys_vendor",
167
                "/sys/class/dmi/id/board_vendor",
168
                "/sys/class/dmi/id/bios_vendor",
169
                "/sys/class/dmi/id/product_version", /* For Hyper-V VMs test */
170
                NULL
171
        };
172

173
        static const struct {
577✔
174
                const char *vendor;
175
                Virtualization id;
176
        } dmi_vendor_table[] = {
177
                { "KVM",                   VIRTUALIZATION_KVM       },
178
                { "OpenStack",             VIRTUALIZATION_KVM       }, /* Detect OpenStack instance as KVM in non x86 architecture */
179
                { "KubeVirt",              VIRTUALIZATION_KVM       }, /* Detect KubeVirt instance as KVM in non x86 architecture */
180
                { "Amazon EC2",            VIRTUALIZATION_AMAZON    },
181
                { "QEMU",                  VIRTUALIZATION_QEMU      },
182
                { "VMware",                VIRTUALIZATION_VMWARE    }, /* https://kb.vmware.com/s/article/1009458 */
183
                { "VMW",                   VIRTUALIZATION_VMWARE    },
184
                { "innotek GmbH",          VIRTUALIZATION_ORACLE    },
185
                { "VirtualBox",            VIRTUALIZATION_ORACLE    },
186
                { "Oracle Corporation",    VIRTUALIZATION_ORACLE    }, /* Detect VirtualBox on some proprietary systems via the board_vendor */
187
                { "Xen",                   VIRTUALIZATION_XEN       },
188
                { "Bochs",                 VIRTUALIZATION_BOCHS     },
189
                { "Parallels",             VIRTUALIZATION_PARALLELS },
190
                /* https://wiki.freebsd.org/bhyve */
191
                { "BHYVE",                 VIRTUALIZATION_BHYVE     },
192
                { "Hyper-V",               VIRTUALIZATION_MICROSOFT },
193
                { "Apple Virtualization",  VIRTUALIZATION_APPLE     },
194
                { "Google Compute Engine", VIRTUALIZATION_GOOGLE    }, /* https://cloud.google.com/run/docs/container-contract#sandbox */
195
        };
196
        int r;
577✔
197

198
        STRV_FOREACH(vendor, dmi_vendors) {
1,166✔
199
                _cleanup_free_ char *s = NULL;
1,166✔
200

201
                r = read_one_line_file(*vendor, &s);
1,166✔
202
                if (r == -ENOENT)
1,166✔
203
                        continue;
×
204
                if (r < 0)
1,166✔
205
                        return r;
206

207
                FOREACH_ELEMENT(dmi_vendor, dmi_vendor_table)
13,527✔
208
                        if (startswith(s, dmi_vendor->vendor)) {
12,938✔
209
                                log_debug("Virtualization %s found in DMI (%s)", s, *vendor);
577✔
210
                                return dmi_vendor->id;
577✔
211
                        }
212
        }
UNCOV
213
        log_debug("No virtualization found in DMI vendor table.");
×
214
        return VIRTUALIZATION_NONE;
215
}
216

UNCOV
217
static int detect_vm_smbios(void) {
×
218
        /* The SMBIOS BIOS Characteristics Extension Byte 2 (Section 2.1.2.2 of
219
         * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.4.0.pdf), specifies that
220
         * the 4th bit being set indicates a VM. The BIOS Characteristics table is exposed via the kernel in
221
         * /sys/firmware/dmi/entries/0-0. Note that in the general case, this bit being unset should not
222
         * imply that the system is running on bare-metal.  For example, QEMU 3.1.0 (with or without KVM)
223
         * with SeaBIOS does not set this bit. */
UNCOV
224
        _cleanup_free_ char *s = NULL;
×
UNCOV
225
        size_t readsize;
×
UNCOV
226
        int r;
×
227

UNCOV
228
        r = read_full_virtual_file("/sys/firmware/dmi/entries/0-0/raw", &s, &readsize);
×
UNCOV
229
        if (r < 0) {
×
UNCOV
230
                log_debug_errno(r, "Unable to read /sys/firmware/dmi/entries/0-0/raw, "
×
231
                                "using the virtualization information found in DMI vendor table, ignoring: %m");
UNCOV
232
                return SMBIOS_VM_BIT_UNKNOWN;
×
233
        }
234
        if (readsize < 20 || s[1] < 20) {
×
235
                /* The spec indicates that byte 1 contains the size of the table, 0x12 + the number of
236
                 * extension bytes. The data we're interested in is in extension byte 2, which would be at
237
                 * 0x13. If we didn't read that much data, or if the BIOS indicates that we don't have that
238
                 * much data, we don't infer anything from the SMBIOS. */
239
                log_debug("Only read %zu bytes from /sys/firmware/dmi/entries/0-0/raw (expected 20). "
×
240
                          "Using the virtualization information found in DMI vendor table.", readsize);
241
                return SMBIOS_VM_BIT_UNKNOWN;
×
242
        }
243

244
        uint8_t byte = (uint8_t) s[19];
×
245
        if (byte & (1U<<4)) {
×
246
                log_debug("DMI BIOS Extension table indicates virtualization.");
×
247
                return SMBIOS_VM_BIT_SET;
×
248
        }
249
        log_debug("DMI BIOS Extension table does not indicate virtualization.");
×
250
        return SMBIOS_VM_BIT_UNSET;
251
}
252
#endif /* defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) */
253

254
static Virtualization detect_vm_dmi(void) {
577✔
255
#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) || defined(__riscv)
256

257
        int r;
577✔
258
        r = detect_vm_dmi_vendor();
577✔
259

260
        /* The DMI vendor tables in /sys/class/dmi/id don't help us distinguish between Amazon EC2
261
         * virtual machines and bare-metal instances, so we need to look at SMBIOS. */
262
        if (r == VIRTUALIZATION_AMAZON) {
577✔
263
                switch (detect_vm_smbios()) {
×
264
                case SMBIOS_VM_BIT_SET:
265
                        return VIRTUALIZATION_AMAZON;
266
                case SMBIOS_VM_BIT_UNSET:
×
267
                        return VIRTUALIZATION_NONE;
×
268
                case SMBIOS_VM_BIT_UNKNOWN: {
×
269
                        /* The DMI information we are after is only accessible to the root user,
270
                         * so we fallback to using the product name which is less restricted
271
                         * to distinguish metal systems from virtualized instances */
272
                        _cleanup_free_ char *s = NULL;
×
273
                        const char *e;
×
274

275
                        r = read_full_virtual_file("/sys/class/dmi/id/product_name", &s, NULL);
×
276
                        /* In EC2, virtualized is much more common than metal, so if for some reason
277
                         * we fail to read the DMI data, assume we are virtualized. */
278
                        if (r < 0) {
×
279
                                log_debug_errno(r, "Can't read /sys/class/dmi/id/product_name,"
×
280
                                                " assuming virtualized: %m");
281
                                return VIRTUALIZATION_AMAZON;
×
282
                        }
283
                        e = strstrafter(truncate_nl(s), ".metal");
×
284
                        if (e && IN_SET(*e, 0, '-')) {
×
285
                                log_debug("DMI product name has '.metal', assuming no virtualization");
×
286
                                return VIRTUALIZATION_NONE;
×
287
                        } else
288
                                return VIRTUALIZATION_AMAZON;
289
                }
290
                default:
×
291
                        assert_not_reached();
×
292
              }
293
        }
294

295
        /* If we haven't identified a VM, but the firmware indicates that there is one, indicate as much. We
296
         * have no further information about what it is. */
297
        if (r == VIRTUALIZATION_NONE && detect_vm_smbios() == SMBIOS_VM_BIT_SET)
577✔
298
                return VIRTUALIZATION_VM_OTHER;
×
299
        return r;
300
#else
301
        return VIRTUALIZATION_NONE;
302
#endif
303
}
304

305
#define XENFEAT_dom0 11 /* xen/include/public/features.h */
306
#define PATH_FEATURES "/sys/hypervisor/properties/features"
307
/* Returns -errno, or 0 for domU, or 1 for dom0 */
308
static int detect_vm_xen_dom0(void) {
×
309
        _cleanup_free_ char *domcap = NULL;
×
310
        int r;
×
311

312
        r = read_one_line_file(PATH_FEATURES, &domcap);
×
313
        if (r < 0 && r != -ENOENT)
×
314
                return r;
315
        if (r >= 0) {
×
316
                unsigned long features;
×
317

318
                /* Here, we need to use sscanf() instead of safe_atoul()
319
                 * as the string lacks the leading "0x". */
320
                r = sscanf(domcap, "%lx", &features);
×
321
                if (r == 1) {
×
322
                        r = !!(features & (1U << XENFEAT_dom0));
×
323
                        log_debug("Virtualization XEN, found %s with value %08lx, "
×
324
                                  "XENFEAT_dom0 (indicating the 'hardware domain') is%s set.",
325
                                  PATH_FEATURES, features, r ? "" : " not");
326
                        return r;
×
327
                }
328
                log_debug("Virtualization XEN, found %s, unhandled content '%s'",
×
329
                          PATH_FEATURES, domcap);
330
        }
331

332
        r = read_one_line_file("/proc/xen/capabilities", &domcap);
×
333
        if (r == -ENOENT) {
×
334
                log_debug("Virtualization XEN because /proc/xen/capabilities does not exist");
×
335
                return 0;
×
336
        }
337
        if (r < 0)
×
338
                return r;
339

340
        for (const char *i = domcap;;) {
×
341
                _cleanup_free_ char *cap = NULL;
×
342

343
                r = extract_first_word(&i, &cap, ",", 0);
×
344
                if (r < 0)
×
345
                        return r;
346
                if (r == 0) {
×
347
                        log_debug("Virtualization XEN DomU found (/proc/xen/capabilities)");
×
348
                        return 0;
×
349
                }
350

351
                if (streq(cap, "control_d")) {
×
352
                        log_debug("Virtualization XEN Dom0 ignored (/proc/xen/capabilities)");
×
353
                        return 1;
×
354
                }
355
        }
356
}
357

358
static Virtualization detect_vm_xen(void) {
577✔
359
        /* The presence of /proc/xen indicates some form of a Xen domain
360
           The check for Dom0 is handled outside this function */
361
        if (access("/proc/xen", F_OK) < 0) {
577✔
362
                log_debug("Virtualization XEN not found, /proc/xen does not exist");
577✔
363
                return VIRTUALIZATION_NONE;
577✔
364
        }
365
        log_debug("Virtualization XEN found (/proc/xen exists)");
×
366
        return VIRTUALIZATION_XEN;
367
}
368

UNCOV
369
static Virtualization detect_vm_hypervisor(void) {
×
UNCOV
370
        _cleanup_free_ char *hvtype = NULL;
×
UNCOV
371
        int r;
×
372

UNCOV
373
        r = read_one_line_file("/sys/hypervisor/type", &hvtype);
×
UNCOV
374
        if (r == -ENOENT)
×
375
                return VIRTUALIZATION_NONE;
376
        if (r < 0)
×
377
                return r;
378

379
        log_debug("Virtualization %s found in /sys/hypervisor/type", hvtype);
×
380

381
        if (streq(hvtype, "xen"))
×
382
                return VIRTUALIZATION_XEN;
383
        else
384
                return VIRTUALIZATION_VM_OTHER;
×
385
}
386

387
static Virtualization detect_vm_uml(void) {
577✔
388
        _cleanup_fclose_ FILE *f = NULL;
577✔
389
        int r;
577✔
390

391
        /* Detect User-Mode Linux by reading /proc/cpuinfo */
392
        f = fopen("/proc/cpuinfo", "re");
577✔
393
        if (!f) {
577✔
394
                if (errno == ENOENT) {
×
395
                        log_debug("/proc/cpuinfo not found, assuming no UML virtualization.");
×
396
                        return VIRTUALIZATION_NONE;
×
397
                }
398
                return -errno;
×
399
        }
400

401
        for (;;) {
1,731✔
402
                _cleanup_free_ char *line = NULL;
577✔
403
                const char *t;
1,154✔
404

405
                r = read_line(f, LONG_LINE_MAX, &line);
1,154✔
406
                if (r < 0)
1,154✔
407
                        return r;
408
                if (r == 0)
1,154✔
409
                        break;
410

411
                t = startswith(line, "vendor_id\t: ");
1,154✔
412
                if (t) {
1,154✔
413
                        if (startswith(t, "User Mode Linux")) {
577✔
414
                                log_debug("UML virtualization found in /proc/cpuinfo");
×
415
                                return VIRTUALIZATION_UML;
×
416
                        }
417

418
                        break;
419
                }
420
        }
421

422
        log_debug("UML virtualization not found in /proc/cpuinfo.");
577✔
423
        return VIRTUALIZATION_NONE;
424
}
425

UNCOV
426
static Virtualization detect_vm_zvm(void) {
×
427

428
#if defined(__s390__)
429
        _cleanup_free_ char *t = NULL;
430
        int r;
431

432
        r = get_proc_field("/proc/sysinfo", "VM00 Control Program", &t);
433
        if (IN_SET(r, -ENOENT, -ENODATA))
434
                return VIRTUALIZATION_NONE;
435
        if (r < 0)
436
                return r;
437

438
        log_debug("Virtualization %s found in /proc/sysinfo", t);
439
        if (streq(t, "z/VM"))
440
                return VIRTUALIZATION_ZVM;
441
        else
442
                return VIRTUALIZATION_KVM;
443
#else
UNCOV
444
        log_debug("This platform does not support /proc/sysinfo");
×
UNCOV
445
        return VIRTUALIZATION_NONE;
×
446
#endif
447
}
448

449
/* Returns a short identifier for the various VM implementations */
450
Virtualization detect_vm(void) {
1,448✔
451
        static thread_local Virtualization cached_found = _VIRTUALIZATION_INVALID;
1,448✔
452
        bool other = false, hyperv = false;
1,448✔
453
        int xen_dom0 = 0;
1,448✔
454
        Virtualization v, dmi;
1,448✔
455

456
        if (cached_found >= 0)
1,448✔
457
                return cached_found;
458

459
        /* We have to use the correct order here:
460
         *
461
         * → First, try to detect Oracle Virtualbox, Amazon EC2 Nitro, Parallels, and Google Compute Engine,
462
         *   even if they use KVM, as well as Xen, even if it cloaks as Microsoft Hyper-V. Attempt to detect
463
         *   UML at this stage too, since it runs as a user-process nested inside other VMs. Also check for
464
         *   Xen now, because Xen PV mode does not override CPUID when nested inside another hypervisor.
465
         *
466
         * → Second, try to detect from CPUID. This will report KVM for whatever software is used even if
467
         *   info in DMI is overwritten.
468
         *
469
         * → Third, try to detect from DMI. */
470

471
        dmi = detect_vm_dmi();
577✔
472
        if (IN_SET(dmi,
577✔
473
                   VIRTUALIZATION_ORACLE,
474
                   VIRTUALIZATION_XEN,
475
                   VIRTUALIZATION_AMAZON,
476
                   /* Unable to distinguish a GCE machine from a VM to bare-metal
477
                    * for non-x86 architectures due to its necessity for cpuid
478
                    * detection, which functions solely on x86 platforms. Report
479
                    * as a VM for other architectures. */
480
#if !defined(__i386__) && !defined(__x86_64__)
481
                   VIRTUALIZATION_GOOGLE,
482
#endif
483
                   VIRTUALIZATION_PARALLELS)) {
484
                v = dmi;
×
485
                goto finish;
×
486
        }
487

488
        /* Detect UML */
489
        v = detect_vm_uml();
577✔
490
        if (v < 0)
577✔
491
                return v;
492
        if (v != VIRTUALIZATION_NONE)
577✔
493
                goto finish;
×
494

495
        /* Detect Xen */
496
        v = detect_vm_xen();
577✔
497
        if (v < 0)
577✔
498
                return v;
499
        if (v == VIRTUALIZATION_XEN) {
577✔
500
                 /* If we are Dom0, then we expect to not report as a VM. However, as we might be nested
501
                  * inside another hypervisor which can be detected via the CPUID check, wait to report this
502
                  * until after the CPUID check. */
503
                xen_dom0 = detect_vm_xen_dom0();
×
504
                if (xen_dom0 < 0)
×
505
                        return xen_dom0;
506
                if (xen_dom0 == 0)
×
507
                        goto finish;
×
508
        } else if (v != VIRTUALIZATION_NONE)
577✔
509
                assert_not_reached();
×
510

511
        /* Detect from CPUID */
512
        v = detect_vm_cpuid();
577✔
513
        if (v < 0)
577✔
514
                return v;
515
        if (v == VIRTUALIZATION_MICROSOFT)
577✔
516
                /* QEMU sets the CPUID string to hyperv's, in case it provides hyperv enlightenments. Let's
517
                 * hence not return Microsoft here but just use the other mechanisms first to make a better
518
                 * decision. */
519
                hyperv = true;
520
        else if (v == VIRTUALIZATION_VM_OTHER)
573✔
521
                other = true;
522
        else if (v == VIRTUALIZATION_KVM && dmi == VIRTUALIZATION_GOOGLE)
573✔
523
                /* The DMI vendor tables in /sys/class/dmi/id don't help us distinguish between GCE
524
                 * virtual machines and bare-metal instances, so we need to look at hypervisor. */
525
                return VIRTUALIZATION_GOOGLE;
526
        else if (v != VIRTUALIZATION_NONE)
573✔
527
                goto finish;
573✔
528

529
        /* If we are in Dom0 and have not yet finished, finish with the result of detect_vm_cpuid */
530
        if (xen_dom0 > 0)
4✔
531
                goto finish;
×
532

533
        /* Now, let's get back to DMI */
534
        if (dmi < 0)
4✔
535
                return dmi;
536
        if (dmi == VIRTUALIZATION_VM_OTHER)
4✔
537
                other = true;
538
        else if (!IN_SET(dmi, VIRTUALIZATION_NONE, VIRTUALIZATION_GOOGLE)) {
4✔
539
                /* At this point if GCE has been detected in dmi, do not report as a VM. It should
540
                 * be a bare-metal machine */
541
                v = dmi;
4✔
542
                goto finish;
4✔
543
        }
544

545
        /* Check high-level hypervisor sysfs file */
UNCOV
546
        v = detect_vm_hypervisor();
×
UNCOV
547
        if (v < 0)
×
548
                return v;
UNCOV
549
        if (v == VIRTUALIZATION_VM_OTHER)
×
550
                other = true;
UNCOV
551
        else if (v != VIRTUALIZATION_NONE)
×
552
                goto finish;
×
553

UNCOV
554
        v = detect_vm_device_tree();
×
UNCOV
555
        if (v < 0)
×
556
                return v;
UNCOV
557
        if (v == VIRTUALIZATION_VM_OTHER)
×
558
                other = true;
UNCOV
559
        else if (v != VIRTUALIZATION_NONE)
×
560
                goto finish;
×
561

UNCOV
562
        v = detect_vm_zvm();
×
UNCOV
563
        if (v < 0)
×
564
                return v;
565

UNCOV
566
finish:
×
567
        /* None of the checks above gave us a clear answer, hence let's now use fallback logic: if hyperv
568
         * enlightenments are available but the VMM wasn't recognized as anything yet, it's probably
569
         * Microsoft. */
570
        if (v == VIRTUALIZATION_NONE) {
577✔
UNCOV
571
                if (hyperv)
×
572
                        v = VIRTUALIZATION_MICROSOFT;
573
                else if (other)
×
574
                        v = VIRTUALIZATION_VM_OTHER;
×
575
        }
576

577
        cached_found = v;
577✔
578
        log_debug("Found VM virtualization %s", virtualization_to_string(v));
577✔
579
        return v;
580
}
581

582
static const char *const container_table[_VIRTUALIZATION_MAX] = {
583
        [VIRTUALIZATION_LXC]            = "lxc",
584
        [VIRTUALIZATION_LXC_LIBVIRT]    = "lxc-libvirt",
585
        [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
586
        [VIRTUALIZATION_DOCKER]         = "docker",
587
        [VIRTUALIZATION_PODMAN]         = "podman",
588
        [VIRTUALIZATION_RKT]            = "rkt",
589
        [VIRTUALIZATION_WSL]            = "wsl",
590
        [VIRTUALIZATION_PROOT]          = "proot",
591
        [VIRTUALIZATION_POUCH]          = "pouch",
592
};
593

594
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(container, int);
3,834✔
595

596
static int running_in_pidns(void) {
4,835✔
597
        int r;
4,835✔
598

599
        r = namespace_is_init(NAMESPACE_PID);
4,835✔
600
        if (r < 0)
4,835✔
601
                return log_debug_errno(r, "Failed to test if in root PID namespace, ignoring: %m");
14✔
602

603
        return !r;
4,821✔
604
}
605

606
static Virtualization detect_container_files(void) {
4,835✔
607
        static const struct {
4,835✔
608
                const char *file_path;
609
                Virtualization id;
610
        } container_file_table[] = {
611
                /* https://github.com/containers/podman/issues/6192 */
612
                /* https://github.com/containers/podman/issues/3586#issuecomment-661918679 */
613
                { "/run/.containerenv", VIRTUALIZATION_PODMAN },
614
                /* https://github.com/moby/moby/issues/18355 */
615
                /* Docker must be the last in this table, see below. */
616
                { "/.dockerenv",        VIRTUALIZATION_DOCKER },
617
        };
618

619
        FOREACH_ELEMENT(file, container_file_table) {
14,505✔
620
                if (access(file->file_path, F_OK) >= 0)
9,670✔
621
                        return file->id;
×
622

623
                if (errno != ENOENT)
9,670✔
624
                        log_debug_errno(errno,
9,670✔
625
                                        "Checking if %s exists failed, ignoring: %m",
626
                                        file->file_path);
627
        }
628

629
        return VIRTUALIZATION_NONE;
630
}
631

632
Virtualization detect_container(void) {
221,362✔
633
        static thread_local Virtualization cached_found = _VIRTUALIZATION_INVALID;
221,362✔
634
        _cleanup_free_ char *m = NULL, *o = NULL, *p = NULL;
221,362✔
635
        const char *e = NULL;
221,362✔
636
        Virtualization v;
221,362✔
637
        int r;
221,362✔
638

639
        if (cached_found >= 0)
221,362✔
640
                return cached_found;
641

642
        /* /proc/vz exists in container and outside of the container, /proc/bc only outside of the container. */
643
        if (access("/proc/vz", F_OK) < 0) {
8,669✔
644
                if (errno != ENOENT)
8,669✔
645
                        log_debug_errno(errno, "Failed to check if /proc/vz exists, ignoring: %m");
×
646
        } else if (access("/proc/bc", F_OK) < 0) {
×
647
                if (errno == ENOENT) {
×
648
                        v = VIRTUALIZATION_OPENVZ;
×
649
                        goto finish;
×
650
                }
651

652
                log_debug_errno(errno, "Failed to check if /proc/bc exists, ignoring: %m");
×
653
        }
654

655
        /* "Official" way of detecting WSL https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364 */
656
        r = read_one_line_file("/proc/sys/kernel/osrelease", &o);
8,669✔
657
        if (r < 0)
8,669✔
658
                log_debug_errno(r, "Failed to read /proc/sys/kernel/osrelease, ignoring: %m");
14✔
659
        else if (strstr(o, "Microsoft") || strstr(o, "WSL")) {
8,655✔
660
                v = VIRTUALIZATION_WSL;
×
661
                goto finish;
×
662
        }
663

664
        /* proot doesn't use PID namespacing, so we can just check if we have a matching tracer for this
665
         * invocation without worrying about it being elsewhere.
666
         */
667
        r = get_proc_field("/proc/self/status", "TracerPid", &p);
8,669✔
668
        if (r < 0)
8,669✔
669
                log_debug_errno(r, "Failed to read our own trace PID, ignoring: %m");
14✔
670
        else if (!streq(p, "0")) {
8,655✔
671
                pid_t ptrace_pid;
×
672

673
                r = parse_pid(p, &ptrace_pid);
×
674
                if (r < 0)
×
675
                        log_debug_errno(r, "Failed to parse our own tracer PID, ignoring: %m");
×
676
                else {
677
                        _cleanup_free_ char *ptrace_comm = NULL;
×
678
                        const char *pf;
×
679

680
                        pf = procfs_file_alloca(ptrace_pid, "comm");
×
681
                        r = read_one_line_file(pf, &ptrace_comm);
×
682
                        if (r < 0)
×
683
                                log_debug_errno(r, "Failed to read %s, ignoring: %m", pf);
×
684
                        else if (startswith(ptrace_comm, "proot")) {
×
685
                                v = VIRTUALIZATION_PROOT;
×
686
                                goto finish;
×
687
                        }
688
                }
689
        }
690

691
        /* The container manager might have placed this in the /run/host/ hierarchy for us, which is best
692
         * because we can be consumed just like that, without special privileges. */
693
        r = read_one_line_file("/run/host/container-manager", &m);
8,669✔
694
        if (r > 0) {
8,669✔
695
                e = m;
3,834✔
696
                goto translate_name;
3,834✔
697
        }
698
        if (!IN_SET(r, -ENOENT, 0))
4,835✔
699
                return log_debug_errno(r, "Failed to read /run/host/container-manager: %m");
×
700

701
        if (getpid_cached() == 1) {
4,835✔
702
                /* If we are PID 1 we can just check our own environment variable, and that's authoritative.
703
                 * We distinguish three cases:
704
                 * - the variable is not defined → we jump to other checks
705
                 * - the variable is defined to an empty value → we are not in a container
706
                 * - anything else → some container, either one of the known ones or "container-other"
707
                 */
708
                e = getenv("container");
23✔
709
                if (!e)
23✔
710
                        goto check_files;
23✔
711
                if (isempty(e)) {
×
712
                        v = VIRTUALIZATION_NONE;
×
713
                        goto finish;
×
714
                }
715

716
                goto translate_name;
×
717
        }
718

719
        /* Otherwise, PID 1 might have dropped this information into a file in /run. This is better than accessing
720
         * /proc/1/environ, since we don't need CAP_SYS_PTRACE for that. */
721
        r = read_one_line_file("/run/systemd/container", &m);
4,812✔
722
        if (r > 0) {
4,812✔
723
                e = m;
×
724
                goto translate_name;
×
725
        }
726
        if (!IN_SET(r, -ENOENT, 0))
4,812✔
727
                return log_debug_errno(r, "Failed to read /run/systemd/container: %m");
×
728

729
        /* Fallback for cases where PID 1 was not systemd (for example, cases where init=/bin/sh is used. */
730
        r = getenv_for_pid(1, "container", &m);
4,812✔
731
        if (r > 0) {
4,812✔
732
                e = m;
×
733
                goto translate_name;
×
734
        }
735
        if (r < 0) /* This only works if we have CAP_SYS_PTRACE, hence let's better ignore failures here */
4,812✔
736
                log_debug_errno(r, "Failed to read $container of PID 1, ignoring: %m");
1,793✔
737

738
check_files:
3,019✔
739
        /* Check for existence of some well-known files. We only do this after checking
740
         * for other specific container managers, otherwise we risk mistaking another
741
         * container manager for Docker: the /.dockerenv file could inadvertently end up
742
         * in a file system image. */
743
        v = detect_container_files();
4,835✔
744
        if (v < 0)
4,835✔
745
                return v;
746
        if (v != VIRTUALIZATION_NONE)
4,835✔
747
                goto finish;
×
748

749
        /* Finally, the root pid namespace has an hardcoded inode number of 0xEFFFFFFC since kernel 3.8, so
750
         * if all else fails we can check the inode number of our pid namespace and compare it. */
751
        if (running_in_pidns() > 0) {
4,835✔
752
                log_debug("Running in a pid namespace, assuming unknown container manager.");
1✔
753
                v = VIRTUALIZATION_CONTAINER_OTHER;
1✔
754
                goto finish;
1✔
755
        }
756

757
        /* If none of that worked, give up, assume no container manager. */
758
        v = VIRTUALIZATION_NONE;
4,834✔
759
        goto finish;
4,834✔
760

761
translate_name:
3,834✔
762
        if (streq(e, "oci")) {
3,834✔
763
                /* Some images hardcode container=oci, but OCI is not a specific container manager.
764
                 * Try to detect one based on well-known files. */
765
                v = detect_container_files();
×
766
                if (v == VIRTUALIZATION_NONE)
×
767
                        v = VIRTUALIZATION_CONTAINER_OTHER;
×
768
                goto finish;
×
769
        }
770
        v = container_from_string(e);
3,834✔
771
        if (v < 0)
3,834✔
772
                v = VIRTUALIZATION_CONTAINER_OTHER;
×
773

774
finish:
3,834✔
775
        log_debug("Found container virtualization %s.", virtualization_to_string(v));
8,669✔
776
        cached_found = v;
8,669✔
777
        return v;
8,669✔
778
}
779

780
Virtualization detect_virtualization(void) {
1,747✔
781
        int v;
1,747✔
782

783
        v = detect_container();
1,747✔
784
        if (v != VIRTUALIZATION_NONE)
1,747✔
785
                return v;
786

787
        return detect_vm();
1,337✔
788
}
789

790
int running_in_userns(void) {
85✔
791
        int r;
85✔
792

793
        r = namespace_is_init(NAMESPACE_USER);
85✔
794
        if (r < 0)
85✔
795
                return log_debug_errno(r, "Failed to test if in root user namespace, ignoring: %m");
×
796

797
        return !r;
85✔
798
}
799

800
int running_in_chroot(void) {
13,304✔
801
        int r;
13,304✔
802

803
        /* If we're PID1, /proc may not be mounted (and most likely we're not in a chroot). But PID1 will
804
         * mount /proc, so all other programs can assume that if /proc is *not* available, we're in some
805
         * chroot. */
806

807
        r = getenv_bool("SYSTEMD_IN_CHROOT");
13,304✔
808
        if (r >= 0)
13,304✔
809
                return r > 0;
4✔
810
        if (r != -ENXIO)
13,300✔
811
                log_debug_errno(r, "Failed to parse $SYSTEMD_IN_CHROOT, ignoring: %m");
×
812

813
        /* Deprecated but kept for backwards compatibility. */
814
        if (getenv_bool("SYSTEMD_IGNORE_CHROOT") > 0)
13,300✔
815
                return 0;
816

817
        r = inode_same("/proc/1/root", "/", /* flags= */ 0);
13,300✔
818
        if (r == -ENOENT) {
13,300✔
819
                r = proc_mounted();
×
820
                if (r == 0) {
×
821
                        if (getpid_cached() == 1)
×
822
                                return false; /* We will mount /proc, assuming we're not in a chroot. */
823

824
                        log_debug("/proc/ is not mounted, assuming we're in a chroot.");
×
825
                        return true;
×
826
                }
827
                if (r > 0) /* If we have fake /proc/, we can't do the check properly. */
×
828
                        return -ENOSYS;
829
        }
830
        if (r < 0)
13,300✔
831
                return log_debug_errno(r, "Failed to check if /proc/1/root and / are the same inode: %m");
203✔
832

833
        return r == 0;
13,097✔
834
}
835

836
#if defined(__i386__) || defined(__x86_64__)
837
struct cpuid_table_entry {
838
        uint32_t flag_bit;
839
        const char *name;
840
};
841

842
static const struct cpuid_table_entry leaf1_edx[] = {
843
        {  0, "fpu"     },
844
        {  1, "vme"     },
845
        {  2, "de"      },
846
        {  3, "pse"     },
847
        {  4, "tsc"     },
848
        {  5, "msr"     },
849
        {  6, "pae"     },
850
        {  7, "mce"     },
851
        {  8, "cx8"     },
852
        {  9, "apic"    },
853
        { 11, "sep"     },
854
        { 12, "mtrr"    },
855
        { 13, "pge"     },
856
        { 14, "mca"     },
857
        { 15, "cmov"    },
858
        { 16, "pat"     },
859
        { 17, "pse36"   },
860
        { 19, "clflush" },
861
        { 23, "mmx"     },
862
        { 24, "fxsr"    },
863
        { 25, "sse"     },
864
        { 26, "sse2"    },
865
        { 28, "ht"      },
866
};
867

868
static const struct cpuid_table_entry leaf1_ecx[] = {
869
        {  0, "pni"     },
870
        {  1, "pclmul"  },
871
        {  3, "monitor" },
872
        {  9, "ssse3"   },
873
        { 12, "fma3"    },
874
        { 13, "cx16"    },
875
        { 19, "sse4_1"  },
876
        { 20, "sse4_2"  },
877
        { 22, "movbe"   },
878
        { 23, "popcnt"  },
879
        { 25, "aes"     },
880
        { 26, "xsave"   },
881
        { 27, "osxsave" },
882
        { 28, "avx"     },
883
        { 29, "f16c"    },
884
        { 30, "rdrand"  },
885
};
886

887
static const struct cpuid_table_entry leaf7_ebx[] = {
888
        {  3, "bmi1"   },
889
        {  5, "avx2"   },
890
        {  8, "bmi2"   },
891
        { 18, "rdseed" },
892
        { 19, "adx"    },
893
        { 29, "sha_ni" },
894
};
895

896
static const struct cpuid_table_entry leaf81_edx[] = {
897
        { 11, "syscall" },
898
        { 27, "rdtscp"  },
899
        { 29, "lm"      },
900
};
901

902
static const struct cpuid_table_entry leaf81_ecx[] = {
903
        {  0, "lahf_lm" },
904
        {  5, "abm"     },
905
};
906

907
static const struct cpuid_table_entry leaf87_edx[] = {
908
        {  8, "constant_tsc" },
909
};
910

911
static bool given_flag_in_set(const char *flag, const struct cpuid_table_entry *set, size_t set_size, uint32_t val) {
14✔
912
        for (size_t i = 0; i < set_size; i++) {
132✔
913
                if ((UINT32_C(1) << set[i].flag_bit) & val &&
119✔
914
                                streq(flag, set[i].name))
116✔
915
                        return true;
916
        }
917
        return false;
918
}
919

920
static bool real_has_cpu_with_flag(const char *flag) {
3✔
921
        uint32_t eax, ebx, ecx, edx;
3✔
922

923
        if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
3✔
924
                if (given_flag_in_set(flag, leaf1_ecx, ELEMENTSOF(leaf1_ecx), ecx))
3✔
925
                        return true;
3✔
926

927
                if (given_flag_in_set(flag, leaf1_edx, ELEMENTSOF(leaf1_edx), edx))
3✔
928
                        return true;
929
        }
930

931
        if (__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx)) {
2✔
932
                if (given_flag_in_set(flag, leaf7_ebx, ELEMENTSOF(leaf7_ebx), ebx))
2✔
933
                        return true;
934
        }
935

936
        if (__get_cpuid(0x80000001U, &eax, &ebx, &ecx, &edx)) {
2✔
937
                if (given_flag_in_set(flag, leaf81_ecx, ELEMENTSOF(leaf81_ecx), ecx))
2✔
938
                        return true;
939

940
                if (given_flag_in_set(flag, leaf81_edx, ELEMENTSOF(leaf81_edx), edx))
2✔
941
                        return true;
942
        }
943

944
        if (__get_cpuid(0x80000007U, &eax, &ebx, &ecx, &edx))
2✔
945
                if (given_flag_in_set(flag, leaf87_edx, ELEMENTSOF(leaf87_edx), edx))
2✔
946
                        return true;
×
947

948
        return false;
949
}
950
#endif
951

952
bool has_cpu_with_flag(const char *flag) {
3✔
953
        /* CPUID is an x86 specific interface. Assume on all others that no CPUs have those flags. */
954
#if defined(__i386__) || defined(__x86_64__)
955
        return real_has_cpu_with_flag(flag);
3✔
956
#else
957
        return false;
958
#endif
959
}
960

961
static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
962
        [VIRTUALIZATION_NONE]            = "none",
963
        [VIRTUALIZATION_KVM]             = "kvm",
964
        [VIRTUALIZATION_AMAZON]          = "amazon",
965
        [VIRTUALIZATION_QEMU]            = "qemu",
966
        [VIRTUALIZATION_BOCHS]           = "bochs",
967
        [VIRTUALIZATION_XEN]             = "xen",
968
        [VIRTUALIZATION_UML]             = "uml",
969
        [VIRTUALIZATION_VMWARE]          = "vmware",
970
        [VIRTUALIZATION_ORACLE]          = "oracle",
971
        [VIRTUALIZATION_MICROSOFT]       = "microsoft",
972
        [VIRTUALIZATION_ZVM]             = "zvm",
973
        [VIRTUALIZATION_PARALLELS]       = "parallels",
974
        [VIRTUALIZATION_BHYVE]           = "bhyve",
975
        [VIRTUALIZATION_QNX]             = "qnx",
976
        [VIRTUALIZATION_ACRN]            = "acrn",
977
        [VIRTUALIZATION_POWERVM]         = "powervm",
978
        [VIRTUALIZATION_APPLE]           = "apple",
979
        [VIRTUALIZATION_SRE]             = "sre",
980
        [VIRTUALIZATION_GOOGLE]          = "google",
981
        [VIRTUALIZATION_VM_OTHER]        = "vm-other",
982

983
        [VIRTUALIZATION_SYSTEMD_NSPAWN]  = "systemd-nspawn",
984
        [VIRTUALIZATION_LXC_LIBVIRT]     = "lxc-libvirt",
985
        [VIRTUALIZATION_LXC]             = "lxc",
986
        [VIRTUALIZATION_OPENVZ]          = "openvz",
987
        [VIRTUALIZATION_DOCKER]          = "docker",
988
        [VIRTUALIZATION_PODMAN]          = "podman",
989
        [VIRTUALIZATION_RKT]             = "rkt",
990
        [VIRTUALIZATION_WSL]             = "wsl",
991
        [VIRTUALIZATION_PROOT]           = "proot",
992
        [VIRTUALIZATION_POUCH]           = "pouch",
993
        [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
994
};
995

996
DEFINE_STRING_TABLE_LOOKUP(virtualization, Virtualization);
1,725✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc