// 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. * * Possibly related, but some boards have additional data before the * size which is garbage (it's a weird path to the directory * containing the loaders). Known boards that do this: Kontron VX3040 * (AMI), ASUS B85M-E, and at least one "older Dell laptop". */ if (((CHAR16 *)li->LoadOptions)[0] == 0) 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