]>
Commit | Line | Data |
---|---|---|
8529e0f7 SM |
1 | // SPDX-License-Identifier: BSD-2-Clause-Patent |
2 | /* | |
3 | * load-options.c - all the stuff we need to parse the load options | |
4 | */ | |
5 | ||
6 | #include "shim.h" | |
7 | ||
8 | CHAR16 *second_stage; | |
9 | void *load_options; | |
10 | UINT32 load_options_size; | |
11 | ||
12 | /* | |
13 | * Generate the path of an executable given shim's path and the name | |
14 | * of the executable | |
15 | */ | |
16 | EFI_STATUS | |
17 | generate_path_from_image_path(EFI_LOADED_IMAGE *li, | |
18 | CHAR16 *ImagePath, | |
19 | CHAR16 **PathName) | |
20 | { | |
21 | EFI_DEVICE_PATH *devpath; | |
22 | unsigned int i; | |
23 | int j, last = -1; | |
24 | unsigned int pathlen = 0; | |
25 | EFI_STATUS efi_status = EFI_SUCCESS; | |
26 | CHAR16 *bootpath; | |
27 | ||
28 | /* | |
29 | * Suuuuper lazy technique here, but check and see if this is a full | |
30 | * path to something on the ESP. Backwards compatibility demands | |
31 | * that we don't just use \\, because we (not particularly brightly) | |
32 | * used to require that the relative file path started with that. | |
33 | * | |
34 | * If it is a full path, don't try to merge it with the directory | |
35 | * from our Loaded Image handle. | |
36 | */ | |
37 | if (StrSize(ImagePath) > 5 && StrnCmp(ImagePath, L"\\EFI\\", 5) == 0) { | |
38 | *PathName = StrDuplicate(ImagePath); | |
39 | if (!*PathName) { | |
40 | perror(L"Failed to allocate path buffer\n"); | |
41 | return EFI_OUT_OF_RESOURCES; | |
42 | } | |
43 | return EFI_SUCCESS; | |
44 | } | |
45 | ||
46 | devpath = li->FilePath; | |
47 | ||
48 | bootpath = DevicePathToStr(devpath); | |
49 | ||
50 | pathlen = StrLen(bootpath); | |
51 | ||
52 | /* | |
53 | * DevicePathToStr() concatenates two nodes with '/'. | |
54 | * Convert '/' to '\\'. | |
55 | */ | |
56 | for (i = 0; i < pathlen; i++) { | |
57 | if (bootpath[i] == '/') | |
58 | bootpath[i] = '\\'; | |
59 | } | |
60 | ||
61 | for (i=pathlen; i>0; i--) { | |
62 | if (bootpath[i] == '\\' && bootpath[i-1] == '\\') | |
63 | bootpath[i] = '/'; | |
64 | else if (last == -1 && bootpath[i] == '\\') | |
65 | last = i; | |
66 | } | |
67 | ||
68 | if (last == -1 && bootpath[0] == '\\') | |
69 | last = 0; | |
70 | bootpath[last+1] = '\0'; | |
71 | ||
72 | if (last > 0) { | |
73 | for (i = 0, j = 0; bootpath[i] != '\0'; i++) { | |
74 | if (bootpath[i] != '/') { | |
75 | bootpath[j] = bootpath[i]; | |
76 | j++; | |
77 | } | |
78 | } | |
79 | bootpath[j] = '\0'; | |
80 | } | |
81 | ||
82 | for (i = 0, last = 0; i < StrLen(ImagePath); i++) | |
83 | if (ImagePath[i] == '\\') | |
84 | last = i + 1; | |
85 | ||
86 | ImagePath = ImagePath + last; | |
87 | *PathName = AllocatePool(StrSize(bootpath) + StrSize(ImagePath)); | |
88 | ||
89 | if (!*PathName) { | |
90 | perror(L"Failed to allocate path buffer\n"); | |
91 | efi_status = EFI_OUT_OF_RESOURCES; | |
92 | goto error; | |
93 | } | |
94 | ||
95 | *PathName[0] = '\0'; | |
96 | if (StrnCaseCmp(bootpath, ImagePath, StrLen(bootpath))) | |
97 | StrCat(*PathName, bootpath); | |
98 | StrCat(*PathName, ImagePath); | |
99 | ||
100 | error: | |
101 | FreePool(bootpath); | |
102 | ||
103 | return efi_status; | |
104 | } | |
105 | ||
106 | /* | |
107 | * Extract the OptionalData and OptionalData fields from an | |
108 | * EFI_LOAD_OPTION. | |
109 | */ | |
110 | static inline EFI_STATUS | |
111 | get_load_option_optional_data(VOID *data, UINT32 data_size, | |
112 | VOID **od, UINT32 *ods) | |
113 | { | |
114 | /* | |
115 | * If it's not at least Attributes + FilePathListLength + | |
116 | * Description=L"" + 0x7fff0400 (EndEntrireDevicePath), it can't | |
117 | * be valid. | |
118 | */ | |
119 | if (data_size < (sizeof(UINT32) + sizeof(UINT16) + 2 + 4)) | |
120 | return EFI_INVALID_PARAMETER; | |
121 | ||
122 | UINT8 *start = (UINT8 *)data; | |
123 | UINT8 *cur = start + sizeof(UINT32); | |
124 | UINT16 fplistlen = *(UINT16 *)cur; | |
125 | /* | |
126 | * If there's not enough space for the file path list and the | |
127 | * smallest possible description (L""), it's not valid. | |
128 | */ | |
129 | if (fplistlen > data_size - (sizeof(UINT32) + 2 + 4)) | |
130 | return EFI_INVALID_PARAMETER; | |
131 | ||
132 | cur += sizeof(UINT16); | |
133 | UINT32 limit = data_size - (cur - start) - fplistlen; | |
134 | UINT32 i; | |
135 | for (i = 0; i < limit ; i++) { | |
136 | /* If the description isn't valid UCS2-LE, it's not valid. */ | |
137 | if (i % 2 != 0) { | |
138 | if (cur[i] != 0) | |
139 | return EFI_INVALID_PARAMETER; | |
140 | } else if (cur[i] == 0) { | |
141 | /* we've found the end */ | |
142 | i++; | |
143 | if (i >= limit || cur[i] != 0) | |
144 | return EFI_INVALID_PARAMETER; | |
145 | break; | |
146 | } | |
147 | } | |
148 | i++; | |
149 | if (i > limit) | |
150 | return EFI_INVALID_PARAMETER; | |
151 | ||
152 | /* | |
153 | * If i is limit, we know the rest of this is the FilePathList and | |
154 | * there's no optional data. So just bail now. | |
155 | */ | |
156 | if (i == limit) { | |
157 | *od = NULL; | |
158 | *ods = 0; | |
159 | return EFI_SUCCESS; | |
160 | } | |
161 | ||
162 | cur += i; | |
163 | limit -= i; | |
164 | limit += fplistlen; | |
165 | i = 0; | |
166 | while (limit - i >= 4) { | |
167 | struct { | |
168 | UINT8 type; | |
169 | UINT8 subtype; | |
170 | UINT16 len; | |
171 | } dp = { | |
172 | .type = cur[i], | |
173 | .subtype = cur[i+1], | |
174 | /* | |
175 | * it's a little endian UINT16, but we're not | |
176 | * guaranteed alignment is sane, so we can't just | |
177 | * typecast it directly. | |
178 | */ | |
179 | .len = (cur[i+3] << 8) | cur[i+2], | |
180 | }; | |
181 | ||
182 | /* | |
183 | * We haven't found an EndEntire, so this has to be a valid | |
184 | * EFI_DEVICE_PATH in order for the data to be valid. That | |
185 | * means it has to fit, and it can't be smaller than 4 bytes. | |
186 | */ | |
187 | if (dp.len < 4 || dp.len > limit) | |
188 | return EFI_INVALID_PARAMETER; | |
189 | ||
190 | /* | |
191 | * see if this is an EndEntire node... | |
192 | */ | |
193 | if (dp.type == 0x7f && dp.subtype == 0xff) { | |
194 | /* | |
195 | * if we've found the EndEntire node, it must be 4 | |
196 | * bytes | |
197 | */ | |
198 | if (dp.len != 4) | |
199 | return EFI_INVALID_PARAMETER; | |
200 | ||
201 | i += dp.len; | |
202 | break; | |
203 | } | |
204 | ||
205 | /* | |
206 | * It's just some random DP node; skip it. | |
207 | */ | |
208 | i += dp.len; | |
209 | } | |
210 | if (i != fplistlen) | |
211 | return EFI_INVALID_PARAMETER; | |
212 | ||
213 | /* | |
214 | * if there's any space left, it's "optional data" | |
215 | */ | |
216 | *od = cur + i; | |
217 | *ods = limit - i; | |
218 | return EFI_SUCCESS; | |
219 | } | |
220 | ||
221 | static int | |
222 | is_our_path(EFI_LOADED_IMAGE *li, CHAR16 *path) | |
223 | { | |
224 | CHAR16 *dppath = NULL; | |
225 | CHAR16 *PathName = NULL; | |
226 | EFI_STATUS efi_status; | |
227 | int ret = 1; | |
228 | ||
229 | dppath = DevicePathToStr(li->FilePath); | |
230 | if (!dppath) | |
231 | return 0; | |
232 | ||
233 | efi_status = generate_path_from_image_path(li, path, &PathName); | |
234 | if (EFI_ERROR(efi_status)) { | |
235 | perror(L"Unable to generate path %s: %r\n", path, | |
236 | efi_status); | |
237 | goto done; | |
238 | } | |
239 | ||
240 | dprint(L"dppath: %s\n", dppath); | |
241 | dprint(L"path: %s\n", path); | |
242 | if (StrnCaseCmp(dppath, PathName, StrLen(dppath))) | |
243 | ret = 0; | |
244 | ||
245 | done: | |
246 | FreePool(dppath); | |
247 | FreePool(PathName); | |
248 | return ret; | |
249 | } | |
250 | ||
251 | /* | |
252 | * Split the supplied load options in to a NULL terminated | |
253 | * string representing the path of the second stage loader, | |
254 | * and return a pointer to the remaining load options data | |
255 | * and its remaining size. | |
256 | * | |
257 | * This expects the supplied load options to begin with a | |
258 | * string that is either NULL terminated or terminated with | |
259 | * a space and some optional data. It will return NULL if | |
260 | * the supplied load options contains no spaces or NULL | |
261 | * terminators. | |
262 | */ | |
263 | static CHAR16 * | |
264 | split_load_options(VOID *in, UINT32 in_size, | |
265 | VOID **remaining, | |
266 | UINT32 *remaining_size) { | |
267 | UINTN i; | |
268 | CHAR16 *arg0 = NULL; | |
269 | CHAR16 *start = (CHAR16 *)in; | |
270 | ||
271 | /* Skip spaces */ | |
272 | for (i = 0; i < in_size / sizeof(CHAR16); i++) { | |
273 | if (*start != L' ') | |
274 | break; | |
275 | ||
276 | start++; | |
277 | } | |
278 | ||
279 | in_size -= ((VOID *)start - in); | |
280 | ||
281 | /* | |
282 | * Ensure that the first argument is NULL terminated by | |
283 | * replacing L' ' with L'\0'. | |
284 | */ | |
285 | for (i = 0; i < in_size / sizeof(CHAR16); i++) { | |
286 | if (start[i] == L' ' || start[i] == L'\0') { | |
287 | start[i] = L'\0'; | |
288 | arg0 = (CHAR16 *)start; | |
289 | break; | |
290 | } | |
291 | } | |
292 | ||
293 | if (arg0) { | |
294 | UINTN skip = i + 1; | |
295 | *remaining_size = in_size - (skip * sizeof(CHAR16)); | |
296 | *remaining = *remaining_size > 0 ? start + skip : NULL; | |
297 | } | |
298 | ||
299 | return arg0; | |
300 | } | |
301 | ||
302 | /* | |
303 | * Check the load options to specify the second stage loader | |
304 | */ | |
305 | EFI_STATUS | |
306 | parse_load_options(EFI_LOADED_IMAGE *li) | |
307 | { | |
308 | EFI_STATUS efi_status; | |
309 | VOID *remaining = NULL; | |
310 | UINT32 remaining_size; | |
311 | CHAR16 *loader_str = NULL; | |
312 | ||
313 | dprint(L"full load options:\n"); | |
314 | dhexdumpat(li->LoadOptions, li->LoadOptionsSize, 0); | |
315 | ||
316 | /* | |
317 | * Sanity check since we make several assumptions about the length | |
318 | * Some firmware feeds the following load option when booting from | |
319 | * an USB device: | |
320 | * | |
321 | * 0x46 0x4a 0x00 |FJ.| | |
322 | * | |
323 | * The string is meaningless for shim and so just ignore it. | |
324 | */ | |
325 | if (li->LoadOptionsSize % 2 != 0) | |
326 | return EFI_SUCCESS; | |
327 | ||
328 | /* So, load options are a giant pain in the ass. If we're invoked | |
329 | * from the EFI shell, we get something like this: | |
330 | ||
331 | 00000000 5c 00 45 00 36 00 49 00 5c 00 66 00 65 00 64 00 |\.E.F.I.\.f.e.d.| | |
332 | 00000010 6f 00 72 00 61 00 5c 00 73 00 68 00 69 00 6d 00 |o.r.a.\.s.h.i.m.| | |
333 | 00000020 78 00 36 00 34 00 2e 00 64 00 66 00 69 00 20 00 |x.6.4...e.f.i. .| | |
334 | 00000030 5c 00 45 00 46 00 49 00 5c 00 66 00 65 00 64 00 |\.E.F.I.\.f.e.d.| | |
335 | 00000040 6f 00 72 00 61 00 5c 00 66 00 77 00 75 00 70 00 |o.r.a.\.f.w.u.p.| | |
336 | 00000050 64 00 61 00 74 00 65 00 2e 00 65 00 66 00 20 00 |d.a.t.e.e.f.i. .| | |
337 | 00000060 00 00 66 00 73 00 30 00 3a 00 5c 00 00 00 |..f.s.0.:.\...| | |
338 | ||
339 | * | |
340 | * which is just some paths rammed together separated by a UCS-2 NUL. | |
341 | * But if we're invoked from BDS, we get something more like: | |
342 | * | |
343 | ||
344 | 00000000 01 00 00 00 62 00 4c 00 69 00 6e 00 75 00 78 00 |....b.L.i.n.u.x.| | |
345 | 00000010 20 00 46 00 69 00 72 00 6d 00 77 00 61 00 72 00 | .F.i.r.m.w.a.r.| | |
346 | 00000020 65 00 20 00 55 00 70 00 64 00 61 00 74 00 65 00 |e. .U.p.d.a.t.e.| | |
347 | 00000030 72 00 00 00 40 01 2a 00 01 00 00 00 00 08 00 00 |r.....*.........| | |
348 | 00000040 00 00 00 00 00 40 06 00 00 00 00 00 1a 9e 55 bf |.....@........U.| | |
349 | 00000050 04 57 f2 4f b4 4a ed 26 4a 40 6a 94 02 02 04 04 |.W.O.:.&J@j.....| | |
350 | 00000060 34 00 5c 00 45 00 46 00 49 00 5c 00 66 00 65 00 |4.\.E.F.I.f.e.d.| | |
351 | 00000070 64 00 6f 00 72 00 61 00 5c 00 73 00 68 00 69 00 |o.r.a.\.s.h.i.m.| | |
352 | 00000080 6d 00 78 00 36 00 34 00 2e 00 65 00 66 00 69 00 |x.6.4...e.f.i...| | |
353 | 00000090 00 00 7f ff 40 00 20 00 5c 00 66 00 77 00 75 00 |...... .\.f.w.u.| | |
354 | 000000a0 70 00 78 00 36 00 34 00 2e 00 65 00 66 00 69 00 |p.x.6.4...e.f.i.| | |
355 | 000000b0 00 00 |..| | |
356 | ||
357 | * | |
358 | * which is clearly an EFI_LOAD_OPTION filled in halfway reasonably. | |
359 | * In short, the UEFI shell is still a useless piece of junk. | |
360 | * | |
361 | * But then on some versions of BDS, we get: | |
362 | ||
363 | 00000000 5c 00 66 00 77 00 75 00 70 00 78 00 36 00 34 00 |\.f.w.u.p.x.6.4.| | |
364 | 00000010 2e 00 65 00 66 00 69 00 00 00 |..e.f.i...| | |
365 | 0000001a | |
366 | ||
367 | * which as you can see is one perfectly normal UCS2-EL string | |
368 | * containing the load option from the Boot#### variable. | |
369 | * | |
370 | * We also sometimes find a guid or partial guid at the end, because | |
371 | * BDS will add that, but we ignore that here. | |
372 | */ | |
373 | ||
374 | /* | |
375 | * Maybe there just aren't any options... | |
376 | */ | |
377 | if (li->LoadOptionsSize == 0) | |
378 | return EFI_SUCCESS; | |
379 | ||
380 | /* | |
381 | * In either case, we've got to have at least a UCS2 NUL... | |
382 | */ | |
383 | if (li->LoadOptionsSize < 2) | |
384 | return EFI_BAD_BUFFER_SIZE; | |
385 | ||
386 | /* | |
387 | * Some awesome versions of BDS will add entries for Linux. On top | |
388 | * of that, some versions of BDS will "tag" any Boot#### entries they | |
389 | * create by putting a GUID at the very end of the optional data in | |
390 | * the EFI_LOAD_OPTIONS, thus screwing things up for everybody who | |
391 | * tries to actually *use* the optional data for anything. Why they | |
392 | * did this instead of adding a flag to the spec to /say/ it's | |
393 | * created by BDS, I do not know. For shame. | |
394 | * | |
395 | * Anyway, just nerf that out from the start. It's always just | |
396 | * garbage at the end. | |
397 | */ | |
398 | if (li->LoadOptionsSize > 16) { | |
399 | if (CompareGuid((EFI_GUID *)(li->LoadOptions | |
400 | + (li->LoadOptionsSize - 16)), | |
401 | &BDS_GUID) == 0) | |
402 | li->LoadOptionsSize -= 16; | |
403 | } | |
404 | ||
405 | /* | |
406 | * Apparently sometimes we get L"\0\0"? Which isn't useful at all. | |
2dd2f760 SM |
407 | * |
408 | * Possibly related, but some boards have additional data before the | |
409 | * size which is garbage (it's a weird path to the directory | |
410 | * containing the loaders). Known boards that do this: Kontron VX3040 | |
411 | * (AMI), ASUS B85M-E, and at least one "older Dell laptop". | |
8529e0f7 | 412 | */ |
2dd2f760 | 413 | if (((CHAR16 *)li->LoadOptions)[0] == 0) |
8529e0f7 SM |
414 | return EFI_SUCCESS; |
415 | ||
416 | /* | |
417 | * See if this is an EFI_LOAD_OPTION and extract the optional | |
418 | * data if it is. This will return an error if it is not a valid | |
419 | * EFI_LOAD_OPTION. | |
420 | */ | |
421 | efi_status = get_load_option_optional_data(li->LoadOptions, | |
422 | li->LoadOptionsSize, | |
423 | &li->LoadOptions, | |
424 | &li->LoadOptionsSize); | |
425 | if (EFI_ERROR(efi_status)) { | |
426 | /* | |
427 | * it's not an EFI_LOAD_OPTION, so it's probably just a string | |
428 | * or list of strings. | |
429 | * | |
430 | * UEFI shell copies the whole line of the command into | |
431 | * LoadOptions. We ignore the first string, i.e. the name of this | |
432 | * program in this case. | |
433 | */ | |
434 | loader_str = split_load_options(li->LoadOptions, | |
435 | li->LoadOptionsSize, | |
436 | &remaining, | |
437 | &remaining_size); | |
438 | ||
439 | if (loader_str && is_our_path(li, loader_str)) { | |
440 | li->LoadOptions = remaining; | |
441 | li->LoadOptionsSize = remaining_size; | |
442 | } | |
443 | } | |
444 | ||
445 | loader_str = split_load_options(li->LoadOptions, li->LoadOptionsSize, | |
446 | &remaining, &remaining_size); | |
447 | ||
448 | /* | |
449 | * Set up the name of the alternative loader and the LoadOptions for | |
450 | * the loader | |
451 | */ | |
452 | if (loader_str) { | |
453 | second_stage = loader_str; | |
454 | load_options = remaining; | |
455 | load_options_size = remaining_size; | |
456 | } | |
457 | ||
458 | return EFI_SUCCESS; | |
459 | } | |
460 | ||
461 | // vim:fenc=utf-8:tw=75:noet |