]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/part-discovery.c
sd-event: reenable epoll_pwait2()
[thirdparty/systemd.git] / src / boot / efi / part-discovery.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <efi.h>
4 #include <efigpt.h>
5 #include <efilib.h>
6
7 #include "part-discovery.h"
8 #include "util.h"
9
10 union GptHeaderBuffer {
11 EFI_PARTITION_TABLE_HEADER gpt_header;
12 uint8_t space[CONST_ALIGN_TO(sizeof(EFI_PARTITION_TABLE_HEADER), 512)];
13 };
14
15 static EFI_DEVICE_PATH *path_replace_hd(
16 const EFI_DEVICE_PATH *path,
17 const EFI_DEVICE_PATH *node,
18 const HARDDRIVE_DEVICE_PATH *new_node) {
19
20 /* Create a new device path as a copy of path, while chopping off the remainder starting at the given
21 * node. If new_node is provided, it is appended at the end of the new path. */
22
23 assert(path);
24 assert(node);
25
26 size_t len = (uint8_t *) node - (uint8_t *) path, new_node_len = 0;
27 if (new_node)
28 new_node_len = DevicePathNodeLength(&new_node->Header);
29
30 EFI_DEVICE_PATH *ret = xmalloc(len + new_node_len + END_DEVICE_PATH_LENGTH);
31 EFI_DEVICE_PATH *end = mempcpy(ret, path, len);
32
33 if (new_node)
34 end = mempcpy(end, new_node, new_node_len);
35
36 SetDevicePathEndNode(end);
37 return ret;
38 }
39
40 static bool verify_gpt(union GptHeaderBuffer *gpt_header_buffer, EFI_LBA lba_expected) {
41 EFI_PARTITION_TABLE_HEADER *h;
42 uint32_t crc32, crc32_saved;
43 EFI_STATUS err;
44
45 assert(gpt_header_buffer);
46
47 h = &gpt_header_buffer->gpt_header;
48
49 /* Some superficial validation of the GPT header */
50 if (memcmp(&h->Header.Signature, "EFI PART", sizeof(h->Header.Signature)) != 0)
51 return false;
52
53 if (h->Header.HeaderSize < 92 || h->Header.HeaderSize > 512)
54 return false;
55
56 if (h->Header.Revision != 0x00010000U)
57 return false;
58
59 /* Calculate CRC check */
60 crc32_saved = h->Header.CRC32;
61 h->Header.CRC32 = 0;
62 err = BS->CalculateCrc32(gpt_header_buffer, h->Header.HeaderSize, &crc32);
63 h->Header.CRC32 = crc32_saved;
64 if (err != EFI_SUCCESS || crc32 != crc32_saved)
65 return false;
66
67 if (h->MyLBA != lba_expected)
68 return false;
69
70 if ((h->SizeOfPartitionEntry % sizeof(EFI_PARTITION_ENTRY)) != 0)
71 return false;
72
73 if (h->NumberOfPartitionEntries <= 0 || h->NumberOfPartitionEntries > 1024)
74 return false;
75
76 /* overflow check */
77 if (h->SizeOfPartitionEntry > UINTN_MAX / h->NumberOfPartitionEntries)
78 return false;
79
80 return true;
81 }
82
83 static EFI_STATUS try_gpt(
84 const EFI_GUID *type,
85 EFI_BLOCK_IO_PROTOCOL *block_io,
86 EFI_LBA lba,
87 EFI_LBA *ret_backup_lba, /* May be changed even on error! */
88 HARDDRIVE_DEVICE_PATH *ret_hd) {
89
90 _cleanup_free_ EFI_PARTITION_ENTRY *entries = NULL;
91 union GptHeaderBuffer gpt;
92 EFI_STATUS err;
93 uint32_t crc32;
94 UINTN size;
95
96 assert(block_io);
97 assert(ret_hd);
98
99 /* Read the GPT header */
100 err = block_io->ReadBlocks(
101 block_io,
102 block_io->Media->MediaId,
103 lba,
104 sizeof(gpt), &gpt);
105 if (err != EFI_SUCCESS)
106 return err;
107
108 /* Indicate the location of backup LBA even if the rest of the header is corrupt. */
109 if (ret_backup_lba)
110 *ret_backup_lba = gpt.gpt_header.AlternateLBA;
111
112 if (!verify_gpt(&gpt, lba))
113 return EFI_NOT_FOUND;
114
115 /* Now load the GPT entry table */
116 size = ALIGN_TO((UINTN) gpt.gpt_header.SizeOfPartitionEntry * (UINTN) gpt.gpt_header.NumberOfPartitionEntries, 512);
117 entries = xmalloc(size);
118
119 err = block_io->ReadBlocks(
120 block_io,
121 block_io->Media->MediaId,
122 gpt.gpt_header.PartitionEntryLBA,
123 size, entries);
124 if (err != EFI_SUCCESS)
125 return err;
126
127 /* Calculate CRC of entries array, too */
128 err = BS->CalculateCrc32(entries, size, &crc32);
129 if (err != EFI_SUCCESS || crc32 != gpt.gpt_header.PartitionEntryArrayCRC32)
130 return EFI_CRC_ERROR;
131
132 /* Now we can finally look for xbootloader partitions. */
133 for (UINTN i = 0; i < gpt.gpt_header.NumberOfPartitionEntries; i++) {
134 EFI_PARTITION_ENTRY *entry =
135 (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt.gpt_header.SizeOfPartitionEntry * i);
136
137 if (memcmp(&entry->PartitionTypeGUID, type, sizeof(entry->PartitionTypeGUID)) != 0)
138 continue;
139
140 if (entry->EndingLBA < entry->StartingLBA) /* Bogus? */
141 continue;
142
143 *ret_hd = (HARDDRIVE_DEVICE_PATH) {
144 .Header = {
145 .Type = MEDIA_DEVICE_PATH,
146 .SubType = MEDIA_HARDDRIVE_DP,
147 },
148 .PartitionNumber = i + 1,
149 .PartitionStart = entry->StartingLBA,
150 .PartitionSize = entry->EndingLBA - entry->StartingLBA + 1,
151 .MBRType = MBR_TYPE_EFI_PARTITION_TABLE_HEADER,
152 .SignatureType = SIGNATURE_TYPE_GUID,
153 };
154 memcpy(ret_hd->Signature, &entry->UniquePartitionGUID, sizeof(ret_hd->Signature));
155
156 /* HARDDRIVE_DEVICE_PATH has padding, which at least OVMF does not like. */
157 SetDevicePathNodeLength(
158 &ret_hd->Header,
159 offsetof(HARDDRIVE_DEVICE_PATH, SignatureType) + sizeof(ret_hd->SignatureType));
160
161 return EFI_SUCCESS;
162 }
163
164 /* This GPT was fully valid, but we didn't find what we are looking for. This
165 * means there's no reason to check the second copy of the GPT header */
166 return EFI_NOT_FOUND;
167 }
168
169 static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVICE_PATH **ret_device_path) {
170 EFI_STATUS err;
171
172 assert(device);
173 assert(ret_device_path);
174
175 EFI_DEVICE_PATH *partition_path;
176 err = BS->HandleProtocol(device, &DevicePathProtocol, (void **) &partition_path);
177 if (err != EFI_SUCCESS)
178 return err;
179
180 /* Find the (last) partition node itself. */
181 EFI_DEVICE_PATH *part_node = NULL;
182 for (EFI_DEVICE_PATH *node = partition_path; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) {
183 if (DevicePathType(node) != MEDIA_DEVICE_PATH)
184 continue;
185
186 if (DevicePathSubType(node) != MEDIA_HARDDRIVE_DP)
187 continue;
188
189 part_node = node;
190 }
191
192 if (!part_node)
193 return EFI_NOT_FOUND;
194
195 /* Chop off the partition part, leaving us with the full path to the disk itself. */
196 _cleanup_free_ EFI_DEVICE_PATH *disk_path = NULL;
197 EFI_DEVICE_PATH *p = disk_path = path_replace_hd(partition_path, part_node, NULL);
198
199 EFI_HANDLE disk_handle;
200 EFI_BLOCK_IO_PROTOCOL *block_io;
201 err = BS->LocateDevicePath(&BlockIoProtocol, &p, &disk_handle);
202 if (err != EFI_SUCCESS)
203 return err;
204
205 err = BS->HandleProtocol(disk_handle, &BlockIoProtocol, (void **)&block_io);
206 if (err != EFI_SUCCESS)
207 return err;
208
209 /* Filter out some block devices early. (We only care about block devices that aren't
210 * partitions themselves — we look for GPT partition tables to parse after all —, and only
211 * those which contain a medium and have at least 2 blocks.) */
212 if (block_io->Media->LogicalPartition ||
213 !block_io->Media->MediaPresent ||
214 block_io->Media->LastBlock <= 1)
215 return EFI_NOT_FOUND;
216
217 /* Try several copies of the GPT header, in case one is corrupted */
218 EFI_LBA backup_lba = 0;
219 for (UINTN nr = 0; nr < 3; nr++) {
220 EFI_LBA lba;
221
222 /* Read the first copy at LBA 1 and then try the backup GPT header pointed
223 * to by the first header if that one was corrupted. As a last resort,
224 * try the very last LBA of this block device. */
225 if (nr == 0)
226 lba = 1;
227 else if (nr == 1 && backup_lba != 0)
228 lba = backup_lba;
229 else if (nr == 2 && backup_lba != block_io->Media->LastBlock)
230 lba = block_io->Media->LastBlock;
231 else
232 continue;
233
234 HARDDRIVE_DEVICE_PATH hd;
235 err = try_gpt(type, block_io, lba,
236 nr == 0 ? &backup_lba : NULL, /* Only get backup LBA location from first GPT header. */
237 &hd);
238 if (err != EFI_SUCCESS) {
239 /* GPT was valid but no XBOOT loader partition found. */
240 if (err == EFI_NOT_FOUND)
241 break;
242 /* Bad GPT, try next one. */
243 continue;
244 }
245
246 /* Patch in the data we found */
247 *ret_device_path = path_replace_hd(partition_path, part_node, &hd);
248 return EFI_SUCCESS;
249 }
250
251 /* No xbootloader partition found */
252 return EFI_NOT_FOUND;
253 }
254
255 EFI_STATUS partition_open(const EFI_GUID *type, EFI_HANDLE *device, EFI_HANDLE *ret_device,
256 EFI_FILE **ret_root_dir) {
257 _cleanup_free_ EFI_DEVICE_PATH *partition_path = NULL;
258 EFI_HANDLE new_device;
259 EFI_FILE *root_dir;
260 EFI_STATUS err;
261
262 assert(type);
263 assert(device);
264 assert(ret_root_dir);
265
266 err = find_device(type, device, &partition_path);
267 if (err != EFI_SUCCESS)
268 return err;
269
270 EFI_DEVICE_PATH *dp = partition_path;
271 err = BS->LocateDevicePath(&BlockIoProtocol, &dp, &new_device);
272 if (err != EFI_SUCCESS)
273 return err;
274
275 err = open_volume(new_device, &root_dir);
276 if (err != EFI_SUCCESS)
277 return err;
278
279 if (ret_device)
280 *ret_device = new_device;
281 *ret_root_dir = root_dir;
282 return EFI_SUCCESS;
283 }