diff options
Diffstat (limited to 'load-options.c')
| -rw-r--r-- | load-options.c | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/load-options.c b/load-options.c new file mode 100644 index 00000000..c6bb7427 --- /dev/null +++ b/load-options.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * load-options.c - all the stuff we need to parse the load options + */ + +#include "shim.h" + +CHAR16 *second_stage; +void *load_options; +UINT32 load_options_size; + +/* + * Generate the path of an executable given shim's path and the name + * of the executable + */ +EFI_STATUS +generate_path_from_image_path(EFI_LOADED_IMAGE *li, + CHAR16 *ImagePath, + CHAR16 **PathName) +{ + EFI_DEVICE_PATH *devpath; + unsigned int i; + int j, last = -1; + unsigned int pathlen = 0; + EFI_STATUS efi_status = EFI_SUCCESS; + CHAR16 *bootpath; + + /* + * Suuuuper lazy technique here, but check and see if this is a full + * path to something on the ESP. Backwards compatibility demands + * that we don't just use \\, because we (not particularly brightly) + * used to require that the relative file path started with that. + * + * If it is a full path, don't try to merge it with the directory + * from our Loaded Image handle. + */ + if (StrSize(ImagePath) > 5 && StrnCmp(ImagePath, L"\\EFI\\", 5) == 0) { + *PathName = StrDuplicate(ImagePath); + if (!*PathName) { + perror(L"Failed to allocate path buffer\n"); + return EFI_OUT_OF_RESOURCES; + } + return EFI_SUCCESS; + } + + devpath = li->FilePath; + + bootpath = DevicePathToStr(devpath); + + pathlen = StrLen(bootpath); + + /* + * DevicePathToStr() concatenates two nodes with '/'. + * Convert '/' to '\\'. + */ + for (i = 0; i < pathlen; i++) { + if (bootpath[i] == '/') + bootpath[i] = '\\'; + } + + for (i=pathlen; i>0; i--) { + if (bootpath[i] == '\\' && bootpath[i-1] == '\\') + bootpath[i] = '/'; + else if (last == -1 && bootpath[i] == '\\') + last = i; + } + + if (last == -1 && bootpath[0] == '\\') + last = 0; + bootpath[last+1] = '\0'; + + if (last > 0) { + for (i = 0, j = 0; bootpath[i] != '\0'; i++) { + if (bootpath[i] != '/') { + bootpath[j] = bootpath[i]; + j++; + } + } + bootpath[j] = '\0'; + } + + for (i = 0, last = 0; i < StrLen(ImagePath); i++) + if (ImagePath[i] == '\\') + last = i + 1; + + ImagePath = ImagePath + last; + *PathName = AllocatePool(StrSize(bootpath) + StrSize(ImagePath)); + + if (!*PathName) { + perror(L"Failed to allocate path buffer\n"); + efi_status = EFI_OUT_OF_RESOURCES; + goto error; + } + + *PathName[0] = '\0'; + if (StrnCaseCmp(bootpath, ImagePath, StrLen(bootpath))) + StrCat(*PathName, bootpath); + StrCat(*PathName, ImagePath); + +error: + FreePool(bootpath); + + return efi_status; +} + +/* + * Extract the OptionalData and OptionalData fields from an + * EFI_LOAD_OPTION. + */ +static inline EFI_STATUS +get_load_option_optional_data(VOID *data, UINT32 data_size, + VOID **od, UINT32 *ods) +{ + /* + * If it's not at least Attributes + FilePathListLength + + * Description=L"" + 0x7fff0400 (EndEntrireDevicePath), it can't + * be valid. + */ + if (data_size < (sizeof(UINT32) + sizeof(UINT16) + 2 + 4)) + return EFI_INVALID_PARAMETER; + + UINT8 *start = (UINT8 *)data; + UINT8 *cur = start + sizeof(UINT32); + UINT16 fplistlen = *(UINT16 *)cur; + /* + * If there's not enough space for the file path list and the + * smallest possible description (L""), it's not valid. + */ + if (fplistlen > data_size - (sizeof(UINT32) + 2 + 4)) + return EFI_INVALID_PARAMETER; + + cur += sizeof(UINT16); + UINT32 limit = data_size - (cur - start) - fplistlen; + UINT32 i; + for (i = 0; i < limit ; i++) { + /* If the description isn't valid UCS2-LE, it's not valid. */ + if (i % 2 != 0) { + if (cur[i] != 0) + return EFI_INVALID_PARAMETER; + } else if (cur[i] == 0) { + /* we've found the end */ + i++; + if (i >= limit || cur[i] != 0) + return EFI_INVALID_PARAMETER; + break; + } + } + i++; + if (i > limit) + return EFI_INVALID_PARAMETER; + + /* + * If i is limit, we know the rest of this is the FilePathList and + * there's no optional data. So just bail now. + */ + if (i == limit) { + *od = NULL; + *ods = 0; + return EFI_SUCCESS; + } + + cur += i; + limit -= i; + limit += fplistlen; + i = 0; + while (limit - i >= 4) { + struct { + UINT8 type; + UINT8 subtype; + UINT16 len; + } dp = { + .type = cur[i], + .subtype = cur[i+1], + /* + * it's a little endian UINT16, but we're not + * guaranteed alignment is sane, so we can't just + * typecast it directly. + */ + .len = (cur[i+3] << 8) | cur[i+2], + }; + + /* + * We haven't found an EndEntire, so this has to be a valid + * EFI_DEVICE_PATH in order for the data to be valid. That + * means it has to fit, and it can't be smaller than 4 bytes. + */ + if (dp.len < 4 || dp.len > limit) + return EFI_INVALID_PARAMETER; + + /* + * see if this is an EndEntire node... + */ + if (dp.type == 0x7f && dp.subtype == 0xff) { + /* + * if we've found the EndEntire node, it must be 4 + * bytes + */ + if (dp.len != 4) + return EFI_INVALID_PARAMETER; + + i += dp.len; + break; + } + + /* + * It's just some random DP node; skip it. + */ + i += dp.len; + } + if (i != fplistlen) + return EFI_INVALID_PARAMETER; + + /* + * if there's any space left, it's "optional data" + */ + *od = cur + i; + *ods = limit - i; + return EFI_SUCCESS; +} + +static int +is_our_path(EFI_LOADED_IMAGE *li, CHAR16 *path) +{ + CHAR16 *dppath = NULL; + CHAR16 *PathName = NULL; + EFI_STATUS efi_status; + int ret = 1; + + dppath = DevicePathToStr(li->FilePath); + if (!dppath) + return 0; + + efi_status = generate_path_from_image_path(li, path, &PathName); + if (EFI_ERROR(efi_status)) { + perror(L"Unable to generate path %s: %r\n", path, + efi_status); + goto done; + } + + dprint(L"dppath: %s\n", dppath); + dprint(L"path: %s\n", path); + if (StrnCaseCmp(dppath, PathName, StrLen(dppath))) + ret = 0; + +done: + FreePool(dppath); + FreePool(PathName); + return ret; +} + +/* + * Split the supplied load options in to a NULL terminated + * string representing the path of the second stage loader, + * and return a pointer to the remaining load options data + * and its remaining size. + * + * This expects the supplied load options to begin with a + * string that is either NULL terminated or terminated with + * a space and some optional data. It will return NULL if + * the supplied load options contains no spaces or NULL + * terminators. + */ +static CHAR16 * +split_load_options(VOID *in, UINT32 in_size, + VOID **remaining, + UINT32 *remaining_size) { + UINTN i; + CHAR16 *arg0 = NULL; + CHAR16 *start = (CHAR16 *)in; + + /* Skip spaces */ + for (i = 0; i < in_size / sizeof(CHAR16); i++) { + if (*start != L' ') + break; + + start++; + } + + in_size -= ((VOID *)start - in); + + /* + * Ensure that the first argument is NULL terminated by + * replacing L' ' with L'\0'. + */ + for (i = 0; i < in_size / sizeof(CHAR16); i++) { + if (start[i] == L' ' || start[i] == L'\0') { + start[i] = L'\0'; + arg0 = (CHAR16 *)start; + break; + } + } + + if (arg0) { + UINTN skip = i + 1; + *remaining_size = in_size - (skip * sizeof(CHAR16)); + *remaining = *remaining_size > 0 ? start + skip : NULL; + } + + return arg0; +} + +/* + * Check the load options to specify the second stage loader + */ +EFI_STATUS +parse_load_options(EFI_LOADED_IMAGE *li) +{ + EFI_STATUS efi_status; + VOID *remaining = NULL; + UINT32 remaining_size; + CHAR16 *loader_str = NULL; + + dprint(L"full load options:\n"); + dhexdumpat(li->LoadOptions, li->LoadOptionsSize, 0); + + /* + * Sanity check since we make several assumptions about the length + * Some firmware feeds the following load option when booting from + * an USB device: + * + * 0x46 0x4a 0x00 |FJ.| + * + * The string is meaningless for shim and so just ignore it. + */ + if (li->LoadOptionsSize % 2 != 0) + return EFI_SUCCESS; + + /* So, load options are a giant pain in the ass. If we're invoked + * from the EFI shell, we get something like this: + +00000000 5c 00 45 00 36 00 49 00 5c 00 66 00 65 00 64 00 |\.E.F.I.\.f.e.d.| +00000010 6f 00 72 00 61 00 5c 00 73 00 68 00 69 00 6d 00 |o.r.a.\.s.h.i.m.| +00000020 78 00 36 00 34 00 2e 00 64 00 66 00 69 00 20 00 |x.6.4...e.f.i. .| +00000030 5c 00 45 00 46 00 49 00 5c 00 66 00 65 00 64 00 |\.E.F.I.\.f.e.d.| +00000040 6f 00 72 00 61 00 5c 00 66 00 77 00 75 00 70 00 |o.r.a.\.f.w.u.p.| +00000050 64 00 61 00 74 00 65 00 2e 00 65 00 66 00 20 00 |d.a.t.e.e.f.i. .| +00000060 00 00 66 00 73 00 30 00 3a 00 5c 00 00 00 |..f.s.0.:.\...| + + * + * which is just some paths rammed together separated by a UCS-2 NUL. + * But if we're invoked from BDS, we get something more like: + * + +00000000 01 00 00 00 62 00 4c 00 69 00 6e 00 75 00 78 00 |....b.L.i.n.u.x.| +00000010 20 00 46 00 69 00 72 00 6d 00 77 00 61 00 72 00 | .F.i.r.m.w.a.r.| +00000020 65 00 20 00 55 00 70 00 64 00 61 00 74 00 65 00 |e. .U.p.d.a.t.e.| +00000030 72 00 00 00 40 01 2a 00 01 00 00 00 00 08 00 00 |r.....*.........| +00000040 00 00 00 00 00 40 06 00 00 00 00 00 1a 9e 55 bf |.....@........U.| +00000050 04 57 f2 4f b4 4a ed 26 4a 40 6a 94 02 02 04 04 |.W.O.:.&J@j.....| +00000060 34 00 5c 00 45 00 46 00 49 00 5c 00 66 00 65 00 |4.\.E.F.I.f.e.d.| +00000070 64 00 6f 00 72 00 61 00 5c 00 73 00 68 00 69 00 |o.r.a.\.s.h.i.m.| +00000080 6d 00 78 00 36 00 34 00 2e 00 65 00 66 00 69 00 |x.6.4...e.f.i...| +00000090 00 00 7f ff 40 00 20 00 5c 00 66 00 77 00 75 00 |...... .\.f.w.u.| +000000a0 70 00 78 00 36 00 34 00 2e 00 65 00 66 00 69 00 |p.x.6.4...e.f.i.| +000000b0 00 00 |..| + + * + * which is clearly an EFI_LOAD_OPTION filled in halfway reasonably. + * In short, the UEFI shell is still a useless piece of junk. + * + * But then on some versions of BDS, we get: + +00000000 5c 00 66 00 77 00 75 00 70 00 78 00 36 00 34 00 |\.f.w.u.p.x.6.4.| +00000010 2e 00 65 00 66 00 69 00 00 00 |..e.f.i...| +0000001a + + * which as you can see is one perfectly normal UCS2-EL string + * containing the load option from the Boot#### variable. + * + * We also sometimes find a guid or partial guid at the end, because + * BDS will add that, but we ignore that here. + */ + + /* + * Maybe there just aren't any options... + */ + if (li->LoadOptionsSize == 0) + return EFI_SUCCESS; + + /* + * In either case, we've got to have at least a UCS2 NUL... + */ + if (li->LoadOptionsSize < 2) + return EFI_BAD_BUFFER_SIZE; + + /* + * Some awesome versions of BDS will add entries for Linux. On top + * of that, some versions of BDS will "tag" any Boot#### entries they + * create by putting a GUID at the very end of the optional data in + * the EFI_LOAD_OPTIONS, thus screwing things up for everybody who + * tries to actually *use* the optional data for anything. Why they + * did this instead of adding a flag to the spec to /say/ it's + * created by BDS, I do not know. For shame. + * + * Anyway, just nerf that out from the start. It's always just + * garbage at the end. + */ + if (li->LoadOptionsSize > 16) { + if (CompareGuid((EFI_GUID *)(li->LoadOptions + + (li->LoadOptionsSize - 16)), + &BDS_GUID) == 0) + li->LoadOptionsSize -= 16; + } + + /* + * Apparently sometimes we get L"\0\0"? Which isn't useful at all. + */ + if (is_all_nuls(li->LoadOptions, li->LoadOptionsSize)) + return EFI_SUCCESS; + + /* + * See if this is an EFI_LOAD_OPTION and extract the optional + * data if it is. This will return an error if it is not a valid + * EFI_LOAD_OPTION. + */ + efi_status = get_load_option_optional_data(li->LoadOptions, + li->LoadOptionsSize, + &li->LoadOptions, + &li->LoadOptionsSize); + if (EFI_ERROR(efi_status)) { + /* + * it's not an EFI_LOAD_OPTION, so it's probably just a string + * or list of strings. + * + * UEFI shell copies the whole line of the command into + * LoadOptions. We ignore the first string, i.e. the name of this + * program in this case. + */ + loader_str = split_load_options(li->LoadOptions, + li->LoadOptionsSize, + &remaining, + &remaining_size); + + if (loader_str && is_our_path(li, loader_str)) { + li->LoadOptions = remaining; + li->LoadOptionsSize = remaining_size; + } + } + + loader_str = split_load_options(li->LoadOptions, li->LoadOptionsSize, + &remaining, &remaining_size); + + /* + * Set up the name of the alternative loader and the LoadOptions for + * the loader + */ + if (loader_str) { + second_stage = loader_str; + load_options = remaining; + load_options_size = remaining_size; + } + + return EFI_SUCCESS; +} + +// vim:fenc=utf-8:tw=75:noet |
