]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/vmm.c
boot: Split out device path functions
[thirdparty/systemd.git] / src / boot / efi / vmm.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
7 #include "device-path-util.h"
8 #include "drivers.h"
9 #include "efi-string.h"
10 #include "proto/device-path.h"
11 #include "string-util-fundamental.h"
12 #include "util.h"
13
14 #define QEMU_KERNEL_LOADER_FS_MEDIA_GUID \
15 { 0x1428f772, 0xb64a, 0x441e, { 0xb8, 0xc3, 0x9e, 0xbd, 0xd7, 0xf8, 0x93, 0xc7 } }
16
17 #define VMM_BOOT_ORDER_GUID \
18 { 0x668f4529, 0x63d0, 0x4bb5, { 0xb6, 0x5d, 0x6f, 0xbb, 0x9d, 0x36, 0xa4, 0x4a } }
19
20 /* detect direct boot */
21 bool is_direct_boot(EFI_HANDLE device) {
22 EFI_STATUS err;
23 VENDOR_DEVICE_PATH *dp; /* NB: Alignment of this structure might be quirky! */
24
25 err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp);
26 if (err != EFI_SUCCESS)
27 return false;
28
29 /* 'qemu -kernel systemd-bootx64.efi' */
30 if (dp->Header.Type == MEDIA_DEVICE_PATH &&
31 dp->Header.SubType == MEDIA_VENDOR_DP &&
32 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! */
33 return true;
34
35 /* loaded from firmware volume (sd-boot added to ovmf) */
36 if (dp->Header.Type == MEDIA_DEVICE_PATH &&
37 dp->Header.SubType == MEDIA_PIWG_FW_VOL_DP)
38 return true;
39
40 return false;
41 }
42
43 /*
44 * Try find ESP when not loaded from ESP
45 *
46 * Inspect all filesystems known to the firmware, try find the ESP. In case VMMBootOrderNNNN variables are
47 * present they are used to inspect the filesystems in the specified order. When nothing was found or the
48 * variables are not present the function will do one final search pass over all filesystems.
49 *
50 * Recent OVMF builds store the qemu boot order (as specified using the bootindex property on the qemu
51 * command line) in VMMBootOrderNNNN. The variables contain a device path.
52 *
53 * Example qemu command line:
54 * qemu -virtio-scsi-pci,addr=14.0 -device scsi-cd,scsi-id=4,bootindex=1
55 *
56 * Resulting variable:
57 * VMMBootOrder0000 = PciRoot(0x0)/Pci(0x14,0x0)/Scsi(0x4,0x0)
58 */
59 EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) {
60 _cleanup_free_ EFI_HANDLE *handles = NULL;
61 size_t n_handles;
62 EFI_STATUS err, dp_err;
63
64 assert(ret_vmm_dev);
65 assert(ret_vmm_dir);
66
67 /* Make sure all file systems have been initialized. Only do this in VMs as this is slow
68 * on some real firmwares. */
69 (void) reconnect_all_drivers();
70
71 /* find all file system handles */
72 err = BS->LocateHandleBuffer(
73 ByProtocol, MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), NULL, &n_handles, &handles);
74 if (err != EFI_SUCCESS)
75 return err;
76
77 for (size_t order = 0;; order++) {
78 _cleanup_free_ EFI_DEVICE_PATH *dp = NULL;
79
80 _cleanup_free_ char16_t *order_str = xasprintf("VMMBootOrder%04zx", order);
81 dp_err = efivar_get_raw(MAKE_GUID_PTR(VMM_BOOT_ORDER), order_str, (char **) &dp, NULL);
82
83 for (size_t i = 0; i < n_handles; i++) {
84 _cleanup_(file_closep) EFI_FILE *root_dir = NULL, *efi_dir = NULL;
85 EFI_DEVICE_PATH *fs;
86
87 err = BS->HandleProtocol(
88 handles[i], MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &fs);
89 if (err != EFI_SUCCESS)
90 return err;
91
92 /* check against VMMBootOrderNNNN (if set) */
93 if (dp_err == EFI_SUCCESS && !device_path_startswith(fs, dp))
94 continue;
95
96 err = open_volume(handles[i], &root_dir);
97 if (err != EFI_SUCCESS)
98 continue;
99
100 /* simple ESP check */
101 err = root_dir->Open(root_dir, &efi_dir, (char16_t*) u"\\EFI",
102 EFI_FILE_MODE_READ,
103 EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY);
104 if (err != EFI_SUCCESS)
105 continue;
106
107 *ret_vmm_dev = handles[i];
108 *ret_vmm_dir = TAKE_PTR(root_dir);
109 return EFI_SUCCESS;
110 }
111
112 if (dp_err != EFI_SUCCESS)
113 return EFI_NOT_FOUND;
114 }
115 assert_not_reached();
116 }
117
118 static bool cpuid_in_hypervisor(void) {
119 #if defined(__i386__) || defined(__x86_64__)
120 unsigned eax, ebx, ecx, edx;
121
122 /* This is a dumbed down version of src/basic/virt.c's detect_vm() that safely works in the UEFI
123 * environment. */
124
125 if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
126 return false;
127
128 if (FLAGS_SET(ecx, 0x80000000U))
129 return true;
130 #endif
131
132 return false;
133 }
134
135 #define SMBIOS_TABLE_GUID \
136 GUID_DEF(0xeb9d2d31, 0x2d88, 0x11d3, 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d)
137 #define SMBIOS3_TABLE_GUID \
138 GUID_DEF(0xf2fd1544, 0x9794, 0x4a2c, 0x99, 0x2e, 0xe5, 0xbb, 0xcf, 0x20, 0xe3, 0x94)
139
140 typedef struct {
141 uint8_t anchor_string[4];
142 uint8_t entry_point_structure_checksum;
143 uint8_t entry_point_length;
144 uint8_t major_version;
145 uint8_t minor_version;
146 uint16_t max_structure_size;
147 uint8_t entry_point_revision;
148 uint8_t formatted_area[5];
149 uint8_t intermediate_anchor_string[5];
150 uint8_t intermediate_checksum;
151 uint16_t table_length;
152 uint32_t table_address;
153 uint16_t number_of_smbios_structures;
154 uint8_t smbios_bcd_revision;
155 } _packed_ SmbiosEntryPoint;
156
157 typedef struct {
158 uint8_t anchor_string[5];
159 uint8_t entry_point_structure_checksum;
160 uint8_t entry_point_length;
161 uint8_t major_version;
162 uint8_t minor_version;
163 uint8_t docrev;
164 uint8_t entry_point_revision;
165 uint8_t reserved;
166 uint32_t table_maximum_size;
167 uint64_t table_address;
168 } _packed_ Smbios3EntryPoint;
169
170 typedef struct {
171 uint8_t type;
172 uint8_t length;
173 uint8_t handle[2];
174 } _packed_ SmbiosHeader;
175
176 typedef struct {
177 SmbiosHeader header;
178 uint8_t vendor;
179 uint8_t bios_version;
180 uint16_t bios_segment;
181 uint8_t bios_release_date;
182 uint8_t bios_size;
183 uint64_t bios_characteristics;
184 uint8_t bios_characteristics_ext[2];
185 } _packed_ SmbiosTableType0;
186
187 static void *find_smbios_configuration_table(uint64_t *ret_size) {
188 assert(ret_size);
189
190 Smbios3EntryPoint *entry3 = find_configuration_table(MAKE_GUID_PTR(SMBIOS3_TABLE));
191 if (entry3 && memcmp(entry3->anchor_string, "_SM3_", 5) == 0 &&
192 entry3->entry_point_length <= sizeof(*entry3)) {
193 *ret_size = entry3->table_maximum_size;
194 return PHYSICAL_ADDRESS_TO_POINTER(entry3->table_address);
195 }
196
197 SmbiosEntryPoint *entry = find_configuration_table(MAKE_GUID_PTR(SMBIOS_TABLE));
198 if (entry && memcmp(entry->anchor_string, "_SM_", 4) == 0 &&
199 entry->entry_point_length <= sizeof(*entry)) {
200 *ret_size = entry->table_length;
201 return PHYSICAL_ADDRESS_TO_POINTER(entry->table_address);
202 }
203
204 return NULL;
205 }
206
207 static SmbiosHeader *get_smbios_table(uint8_t type) {
208 uint64_t size = 0;
209 uint8_t *p = find_smbios_configuration_table(&size);
210 if (!p)
211 return false;
212
213 for (;;) {
214 if (size < sizeof(SmbiosHeader))
215 return NULL;
216
217 SmbiosHeader *header = (SmbiosHeader *) p;
218
219 /* End of table. */
220 if (header->type == 127)
221 return NULL;
222
223 if (size < header->length)
224 return NULL;
225
226 if (header->type == type)
227 return header; /* Yay! */
228
229 /* Skip over formatted area. */
230 size -= header->length;
231 p += header->length;
232
233 /* Skip over string table. */
234 for (;;) {
235 while (size > 0 && *p != '\0') {
236 p++;
237 size--;
238 }
239 if (size == 0)
240 return NULL;
241 p++;
242 size--;
243
244 /* Double NUL terminates string table. */
245 if (*p == '\0') {
246 if (size == 0)
247 return NULL;
248 p++;
249 break;
250 }
251 }
252 }
253
254 return NULL;
255 }
256
257 static bool smbios_in_hypervisor(void) {
258 /* Look up BIOS Information (Type 0). */
259 SmbiosTableType0 *type0 = (SmbiosTableType0 *) get_smbios_table(0);
260 if (!type0 || type0->header.length < sizeof(SmbiosTableType0))
261 return false;
262
263 /* Bit 4 of 2nd BIOS characteristics extension bytes indicates virtualization. */
264 return FLAGS_SET(type0->bios_characteristics_ext[1], 1 << 4);
265 }
266
267 bool in_hypervisor(void) {
268 static int cache = -1;
269 if (cache >= 0)
270 return cache;
271
272 cache = cpuid_in_hypervisor() || smbios_in_hypervisor();
273 return cache;
274 }