blob: 6bd440f03203c12e83813d6758c2b2ee512291d8 [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <efi.h>
#include <efilib.h>
#include <stdbool.h>
#if defined(__i386__) || defined(__x86_64__)
# include <cpuid.h>
#endif
#include "drivers.h"
#include "efi-string.h"
#include "string-util-fundamental.h"
#include "util.h"
#define QEMU_KERNEL_LOADER_FS_MEDIA_GUID \
{ 0x1428f772, 0xb64a, 0x441e, { 0xb8, 0xc3, 0x9e, 0xbd, 0xd7, 0xf8, 0x93, 0xc7 } }
#define VMM_BOOT_ORDER_GUID \
{ 0x668f4529, 0x63d0, 0x4bb5, { 0xb6, 0x5d, 0x6f, 0xbb, 0x9d, 0x36, 0xa4, 0x4a } }
/* detect direct boot */
bool is_direct_boot(EFI_HANDLE device) {
EFI_STATUS err;
VENDOR_DEVICE_PATH *dp; /* NB: Alignment of this structure might be quirky! */
err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp);
if (err != EFI_SUCCESS)
return false;
/* 'qemu -kernel systemd-bootx64.efi' */
if (dp->Header.Type == MEDIA_DEVICE_PATH &&
dp->Header.SubType == MEDIA_VENDOR_DP &&
memcmp(&dp->Guid, MAKE_GUID_PTR(QEMU_KERNEL_LOADER_FS_MEDIA), sizeof(EFI_GUID)) == 0) /* Don't change to efi_guid_equal() because EFI device path objects are not necessarily aligned! */
return true;
/* loaded from firmware volume (sd-boot added to ovmf) */
if (dp->Header.Type == MEDIA_DEVICE_PATH &&
dp->Header.SubType == MEDIA_PIWG_FW_VOL_DP)
return true;
return false;
}
static bool device_path_startswith(const EFI_DEVICE_PATH *dp, const EFI_DEVICE_PATH *start) {
if (!start)
return true;
if (!dp)
return false;
for (;;) {
if (IsDevicePathEnd(start))
return true;
if (IsDevicePathEnd(dp))
return false;
size_t l1 = DevicePathNodeLength(start);
size_t l2 = DevicePathNodeLength(dp);
if (l1 != l2)
return false;
if (memcmp(dp, start, l1) != 0)
return false;
start = NextDevicePathNode(start);
dp = NextDevicePathNode(dp);
}
}
/*
* Try find ESP when not loaded from ESP
*
* Inspect all filesystems known to the firmware, try find the ESP. In case VMMBootOrderNNNN variables are
* present they are used to inspect the filesystems in the specified order. When nothing was found or the
* variables are not present the function will do one final search pass over all filesystems.
*
* Recent OVMF builds store the qemu boot order (as specified using the bootindex property on the qemu
* command line) in VMMBootOrderNNNN. The variables contain a device path.
*
* Example qemu command line:
* qemu -virtio-scsi-pci,addr=14.0 -device scsi-cd,scsi-id=4,bootindex=1
*
* Resulting variable:
* VMMBootOrder0000 = PciRoot(0x0)/Pci(0x14,0x0)/Scsi(0x4,0x0)
*/
EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) {
_cleanup_free_ EFI_HANDLE *handles = NULL;
size_t n_handles;
EFI_STATUS err, dp_err;
assert(ret_vmm_dev);
assert(ret_vmm_dir);
/* Make sure all file systems have been initialized. Only do this in VMs as this is slow
* on some real firmwares. */
(void) reconnect_all_drivers();
/* find all file system handles */
err = BS->LocateHandleBuffer(
ByProtocol, MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), NULL, &n_handles, &handles);
if (err != EFI_SUCCESS)
return err;
for (size_t order = 0;; order++) {
_cleanup_free_ EFI_DEVICE_PATH *dp = NULL;
_cleanup_free_ char16_t *order_str = xasprintf("VMMBootOrder%04zx", order);
dp_err = efivar_get_raw(MAKE_GUID_PTR(VMM_BOOT_ORDER), order_str, (char **) &dp, NULL);
for (size_t i = 0; i < n_handles; i++) {
_cleanup_(file_closep) EFI_FILE *root_dir = NULL, *efi_dir = NULL;
EFI_DEVICE_PATH *fs;
err = BS->HandleProtocol(
handles[i], MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &fs);
if (err != EFI_SUCCESS)
return err;
/* check against VMMBootOrderNNNN (if set) */
if (dp_err == EFI_SUCCESS && !device_path_startswith(fs, dp))
continue;
err = open_volume(handles[i], &root_dir);
if (err != EFI_SUCCESS)
continue;
/* simple ESP check */
err = root_dir->Open(root_dir, &efi_dir, (char16_t*) u"\\EFI",
EFI_FILE_MODE_READ,
EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY);
if (err != EFI_SUCCESS)
continue;
*ret_vmm_dev = handles[i];
*ret_vmm_dir = TAKE_PTR(root_dir);
return EFI_SUCCESS;
}
if (dp_err != EFI_SUCCESS)
return EFI_NOT_FOUND;
}
assert_not_reached();
}
static bool cpuid_in_hypervisor(void) {
#if defined(__i386__) || defined(__x86_64__)
unsigned eax, ebx, ecx, edx;
/* This is a dumbed down version of src/basic/virt.c's detect_vm() that safely works in the UEFI
* environment. */
if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
return false;
if (FLAGS_SET(ecx, 0x80000000U))
return true;
#endif
return false;
}
typedef struct {
uint8_t anchor_string[4];
uint8_t entry_point_structure_checksum;
uint8_t entry_point_length;
uint8_t major_version;
uint8_t minor_version;
uint16_t max_structure_size;
uint8_t entry_point_revision;
uint8_t formatted_area[5];
uint8_t intermediate_anchor_string[5];
uint8_t intermediate_checksum;
uint16_t table_length;
uint32_t table_address;
uint16_t number_of_smbios_structures;
uint8_t smbios_bcd_revision;
} _packed_ SmbiosEntryPoint;
typedef struct {
uint8_t anchor_string[5];
uint8_t entry_point_structure_checksum;
uint8_t entry_point_length;
uint8_t major_version;
uint8_t minor_version;
uint8_t docrev;
uint8_t entry_point_revision;
uint8_t reserved;
uint32_t table_maximum_size;
uint64_t table_address;
} _packed_ Smbios3EntryPoint;
typedef struct {
uint8_t type;
uint8_t length;
uint8_t handle[2];
} _packed_ SmbiosHeader;
typedef struct {
SmbiosHeader header;
uint8_t vendor;
uint8_t bios_version;
uint16_t bios_segment;
uint8_t bios_release_date;
uint8_t bios_size;
uint64_t bios_characteristics;
uint8_t bios_characteristics_ext[2];
} _packed_ SmbiosTableType0;
static void *find_smbios_configuration_table(uint64_t *ret_size) {
assert(ret_size);
Smbios3EntryPoint *entry3 = find_configuration_table(MAKE_GUID_PTR(SMBIOS3_TABLE));
if (entry3 && memcmp(entry3->anchor_string, "_SM3_", 5) == 0 &&
entry3->entry_point_length <= sizeof(*entry3)) {
*ret_size = entry3->table_maximum_size;
return PHYSICAL_ADDRESS_TO_POINTER(entry3->table_address);
}
SmbiosEntryPoint *entry = find_configuration_table(MAKE_GUID_PTR(SMBIOS_TABLE));
if (entry && memcmp(entry->anchor_string, "_SM_", 4) == 0 &&
entry->entry_point_length <= sizeof(*entry)) {
*ret_size = entry->table_length;
return PHYSICAL_ADDRESS_TO_POINTER(entry->table_address);
}
return NULL;
}
static SmbiosHeader *get_smbios_table(uint8_t type) {
uint64_t size = 0;
uint8_t *p = find_smbios_configuration_table(&size);
if (!p)
return false;
for (;;) {
if (size < sizeof(SmbiosHeader))
return NULL;
SmbiosHeader *header = (SmbiosHeader *) p;
/* End of table. */
if (header->type == 127)
return NULL;
if (size < header->length)
return NULL;
if (header->type == type)
return header; /* Yay! */
/* Skip over formatted area. */
size -= header->length;
p += header->length;
/* Skip over string table. */
for (;;) {
while (size > 0 && *p != '\0') {
p++;
size--;
}
if (size == 0)
return NULL;
p++;
size--;
/* Double NUL terminates string table. */
if (*p == '\0') {
if (size == 0)
return NULL;
p++;
break;
}
}
}
return NULL;
}
static bool smbios_in_hypervisor(void) {
/* Look up BIOS Information (Type 0). */
SmbiosTableType0 *type0 = (SmbiosTableType0 *) get_smbios_table(0);
if (!type0 || type0->header.length < sizeof(SmbiosTableType0))
return false;
/* Bit 4 of 2nd BIOS characteristics extension bytes indicates virtualization. */
return FLAGS_SET(type0->bios_characteristics_ext[1], 1 << 4);
}
bool in_hypervisor(void) {
static int cache = -1;
if (cache >= 0)
return cache;
cache = cpuid_in_hypervisor() || smbios_in_hypervisor();
return cache;
}