diff --git a/Makefile b/Makefile index 412eba5..ba62f51 100644 --- a/Makefile +++ b/Makefile @@ -28,12 +28,14 @@ LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared -Bsymbolic -L$(EFI_PATH VERSION = 0.2 -TARGET = shim.efi MokManager.efi.signed +TARGET = shim.efi MokManager.efi.signed fallback.efi.signed OBJS = shim.o netboot.o cert.o dbx.o KEYS = shim_cert.h ocsp.* ca.* shim.crt shim.csr shim.p12 shim.pem shim.key SOURCES = shim.c shim.h netboot.c signature.h PeImage.h MOK_OBJS = MokManager.o MOK_SOURCES = MokManager.c shim.h +FALLBACK_OBJS = fallback.o +FALLBACK_SRCS = fallback.c all: $(TARGET) @@ -65,6 +67,11 @@ dbx.o : dbx.S shim.so: $(OBJS) Cryptlib/libcryptlib.a Cryptlib/OpenSSL/libopenssl.a $(LD) -o $@ $(LDFLAGS) $^ $(EFI_LIBS) +fallback.o: $(FALLBACK_SRCS) + +fallback.so: $(FALLBACK_OBJS) + $(LD) -o $@ $(LDFLAGS) $^ $(EFI_LIBS) + MokManager.o: $(SOURCES) MokManager.so: $(MOK_OBJS) Cryptlib/libcryptlib.a Cryptlib/OpenSSL/libopenssl.a diff --git a/fallback.c b/fallback.c new file mode 100644 index 0000000..8f9bcbe --- /dev/null +++ b/fallback.c @@ -0,0 +1,619 @@ +/* + * Copyright 2012-2013 Red Hat, Inc. + * All rights reserved. + * + * See "COPYING" for license terms. + * + * Author(s): Peter Jones + */ + +#include +#include + +#include "ucs2.h" + +EFI_LOADED_IMAGE *this_image = NULL; + +static EFI_STATUS +get_file_size(EFI_FILE_HANDLE fh, UINT64 *retsize) +{ + EFI_STATUS rc; + void *buffer = NULL; + UINTN bs = 0; + EFI_GUID finfo = EFI_FILE_INFO_ID; + + /* The API here is "Call it once with bs=0, it fills in bs, + * then allocate a buffer and ask again to get it filled. */ + rc = uefi_call_wrapper(fh->GetInfo, 4, fh, &finfo, &bs, NULL); + if (rc == EFI_BUFFER_TOO_SMALL) { + buffer = AllocateZeroPool(bs); + if (!buffer) { + Print(L"Could not allocate memory\n"); + return EFI_OUT_OF_RESOURCES; + } + rc = uefi_call_wrapper(fh->GetInfo, 4, fh, &finfo, + &bs, buffer); + } + /* This checks *either* the error from the first GetInfo, if it isn't + * the EFI_BUFFER_TOO_SMALL we're expecting, or the second GetInfo call + * in *any* case. */ + if (EFI_ERROR(rc)) { + Print(L"Could not get file info: %d\n", rc); + if (buffer) + FreePool(buffer); + return rc; + } + EFI_FILE_INFO *fi = buffer; + *retsize = fi->FileSize; + FreePool(buffer); + return EFI_SUCCESS; +} + +EFI_STATUS +read_file(EFI_FILE_HANDLE fh, CHAR16 *fullpath, CHAR16 **buffer, UINT64 *bs) +{ + EFI_FILE_HANDLE fh2; + EFI_STATUS rc = uefi_call_wrapper(fh->Open, 5, fh, &fh2, fullpath, + EFI_FILE_READ_ONLY, 0); + if (EFI_ERROR(rc)) { + Print(L"Couldn't open \"%s\": %d\n", fullpath, rc); + return rc; + } + + UINT64 len = 0; + CHAR16 *b = NULL; + rc = get_file_size(fh2, &len); + if (EFI_ERROR(rc)) { + uefi_call_wrapper(fh2->Close, 1, fh2); + return rc; + } + + b = AllocateZeroPool(len + 2); + if (!buffer) { + Print(L"Could not allocate memory\n"); + uefi_call_wrapper(fh2->Close, 1, fh2); + return EFI_OUT_OF_RESOURCES; + } + + rc = uefi_call_wrapper(fh->Read, 3, fh, &len, b); + if (EFI_ERROR(rc)) { + FreePool(buffer); + uefi_call_wrapper(fh2->Close, 1, fh2); + Print(L"Could not read file: %d\n", rc); + return rc; + } + *buffer = b; + *bs = len; + uefi_call_wrapper(fh2->Close, 1, fh2); + return EFI_SUCCESS; +} + +EFI_STATUS +make_full_path(CHAR16 *dirname, CHAR16 *filename, CHAR16 **out, UINT64 *outlen) +{ + UINT64 len; + + len = StrLen(dirname) + StrLen(filename) + StrLen(L"\\EFI\\\\") + 2; + + CHAR16 *fullpath = AllocateZeroPool(len); + if (!fullpath) { + Print(L"Could not allocate memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + StrCat(fullpath, L"\\EFI\\"); + StrCat(fullpath, dirname); + StrCat(fullpath, L"\\"); + StrCat(fullpath, filename); + + *out = fullpath; + *outlen = len; + return EFI_SUCCESS; +} + +CHAR16 *bootorder = NULL; +int nbootorder = 0; + +EFI_STATUS +add_boot_option(EFI_DEVICE_PATH *dp, CHAR16 *filename, CHAR16 *label, CHAR16 *arguments) +{ + static int i = 0; + CHAR16 varname[] = L"Boot0000"; + CHAR16 hexmap[] = L"0123456789ABCDEF"; + EFI_GUID global = EFI_GLOBAL_VARIABLE; + EFI_STATUS rc; + + for(; i <= 0xffff; i++) { + varname[4] = hexmap[(i & 0xf000) >> 12]; + varname[5] = hexmap[(i & 0x0f00) >> 8]; + varname[6] = hexmap[(i & 0x00f0) >> 4]; + varname[7] = hexmap[(i & 0x000f) >> 0]; + + void *var = LibGetVariable(varname, &global); + if (!var) { + int size = sizeof(UINT32) + sizeof (UINT16) + + StrLen(label)*2 + 2 + DevicePathSize(dp) + + StrLen(arguments) * 2 + 2; + + CHAR8 *data = AllocateZeroPool(size); + CHAR8 *cursor = data; + *(UINT32 *)cursor = LOAD_OPTION_ACTIVE; + cursor += sizeof (UINT32); + *(UINT16 *)cursor = DevicePathSize(dp); + cursor += sizeof (UINT16); + StrCpy((CHAR16 *)cursor, label); + cursor += StrLen(label)*2 + 2; + CopyMem(cursor, dp, DevicePathSize(dp)); + cursor += DevicePathSize(dp); + StrCpy((CHAR16 *)cursor, arguments); + + Print(L"Creating boot entry \"%s\" with label \"%s\" " + L"for file \"%s\"\n", + varname, label, filename); + rc = uefi_call_wrapper(RT->SetVariable, 5, varname, + &global, EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + size, data); + + FreePool(data); + + if (EFI_ERROR(rc)) { + Print(L"Could not create variable: %d\n", rc); + return rc; + } + + CHAR16 *newbootorder = AllocateZeroPool(sizeof (CHAR16) + * (nbootorder + 1)); + if (!newbootorder) + return EFI_OUT_OF_RESOURCES; + + int j = 0; + if (nbootorder) { + for (j = 0; j < nbootorder; j++) + newbootorder[j] = bootorder[j]; + FreePool(bootorder); + } + newbootorder[j] = i & 0xffff; + bootorder = newbootorder; + nbootorder += 1; +#ifdef DEBUG_FALLBACK + Print(L"nbootorder: %d\nBootOrder: ", nbootorder); + for (j = 0 ; j < nbootorder ; j++) + Print(L"%04x ", bootorder[j]); + Print(L"\n"); +#endif + + return EFI_SUCCESS; + } + } + return EFI_OUT_OF_RESOURCES; +} + +EFI_STATUS +update_boot_order(void) +{ + CHAR16 *oldbootorder; + UINTN size; + EFI_GUID global = EFI_GLOBAL_VARIABLE; + CHAR16 *newbootorder = NULL; + + oldbootorder = LibGetVariableAndSize(L"BootOrder", &global, &size); + if (oldbootorder) { + int n = size / sizeof (CHAR16) + nbootorder; + + newbootorder = AllocateZeroPool(n * sizeof (CHAR16)); + CopyMem(newbootorder, bootorder, nbootorder * sizeof (CHAR16)); + CopyMem(newbootorder + nbootorder, oldbootorder, size); + size = n * sizeof (CHAR16); + } else { + size = nbootorder * sizeof(CHAR16); + newbootorder = AllocateZeroPool(size); + CopyMem(newbootorder, bootorder, size); + } + if (!newbootorder) + return EFI_OUT_OF_RESOURCES; + +#ifdef DEBUG_FALLBACK + Print(L"nbootorder: %d\nBootOrder: ", size / sizeof (CHAR16)); + int j; + for (j = 0 ; j < size / sizeof (CHAR16); j++) + Print(L"%04x ", newbootorder[j]); + Print(L"\n"); +#endif + + if (oldbootorder) { + LibDeleteVariable(L"BootOrder", &global); + FreePool(oldbootorder); + } + + EFI_STATUS rc; + rc = uefi_call_wrapper(RT->SetVariable, 5, L"BootOrder", &global, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + size, newbootorder); + FreePool(newbootorder); + return rc; +} + +EFI_STATUS +add_to_boot_list(EFI_FILE_HANDLE fh, CHAR16 *dirname, CHAR16 *filename, CHAR16 *label, CHAR16 *arguments) +{ + CHAR16 *fullpath = NULL; + UINT64 pathlen = 0; + EFI_STATUS rc = EFI_SUCCESS; + + rc = make_full_path(dirname, filename, &fullpath, &pathlen); + if (EFI_ERROR(rc)) + return rc; + + EFI_DEVICE_PATH *dph = NULL, *dpf = NULL, *dp = NULL; + + dph = DevicePathFromHandle(this_image->DeviceHandle); + if (!dph) { + rc = EFI_OUT_OF_RESOURCES; + goto err; + } + + dpf = FileDevicePath(fh, fullpath); + if (!dpf) { + rc = EFI_OUT_OF_RESOURCES; + goto err; + } + + dp = AppendDevicePath(dph, dpf); + if (!dp) { + rc = EFI_OUT_OF_RESOURCES; + goto err; + } + +#ifdef DEBUG_FALLBACK + UINTN s = DevicePathSize(dp); + int i; + UINT8 *dpv = (void *)dp; + for (i = 0; i < s; i++) { + if (i > 0 && i % 16 == 0) + Print(L"\n"); + Print(L"%02x ", dpv[i]); + } + Print(L"\n"); + + CHAR16 *dps = DevicePathToStr(dp); + Print(L"device path: \"%s\"\n", dps); +#endif + + add_boot_option(dp, fullpath, label, arguments); + FreePool(fullpath); + +err: + if (dph) + FreePool(dph); + if (dpf) + FreePool(dpf); + if (dp) + FreePool(dp); + if (fullpath) + FreePool(fullpath); + return rc; +} + +EFI_STATUS +populate_stanza(EFI_FILE_HANDLE fh, CHAR16 *dirname, CHAR16 *filename, CHAR16 *csv) +{ +#ifdef DEBUG_FALLBACK + Print(L"CSV data: \"%s\"\n", csv); +#endif + CHAR16 *file = csv; + + UINTN comma0 = StrCSpn(csv, L","); + if (comma0 == 0) + return EFI_INVALID_PARAMETER; + file[comma0] = L'\0'; +#ifdef DEBUG_FALLBACK + Print(L"filename: \"%s\"\n", file); +#endif + + CHAR16 *label = csv + comma0 + 1; + UINTN comma1 = StrCSpn(label, L","); + if (comma1 == 0) + return EFI_INVALID_PARAMETER; + label[comma1] = L'\0'; +#ifdef DEBUG_FALLBACK + Print(L"label: \"%s\"\n", label); +#endif + + CHAR16 *arguments = csv + comma0 +1 + comma1 +1; + UINTN comma2 = StrCSpn(arguments, L","); + arguments[comma2] = L'\0'; + /* This one is optional, so don't check if comma2 is 0 */ +#ifdef DEBUG_FALLBACK + Print(L"arguments: \"%s\"\n", arguments); +#endif + + add_to_boot_list(fh, dirname, file, label, arguments); + + return EFI_SUCCESS; +} + +EFI_STATUS +try_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname, CHAR16 *filename) +{ + CHAR16 *fullpath = NULL; + UINT64 pathlen = 0; + EFI_STATUS rc; + + rc = make_full_path(dirname, filename, &fullpath, &pathlen); + if (EFI_ERROR(rc)) + return rc; + +#ifdef DEBUG_FALLBACK + Print(L"Found file \"%s\"\n", fullpath); +#endif + + CHAR16 *buffer; + UINT64 bs; + rc = read_file(fh, fullpath, &buffer, &bs); + if (EFI_ERROR(rc)) { + Print(L"Could not read file \"%s\": %d\n", fullpath, rc); + FreePool(fullpath); + return rc; + } + FreePool(fullpath); + +#ifdef DEBUG_FALLBACK + Print(L"File looks like:\n%s\n", buffer); +#endif + + CHAR16 *start = buffer; + /* If I create boot.csv with the efi shell's "edit" command, + * it starts with 0xfeff. I assume there's some reason for this, + * but it doesn't matter much to me... + */ + if (*start == 0xfeff) + start++; + while (*start) { + while (*start == L'\r' || *start == L'\n') { + start++; + continue; + } + UINTN l = StrCSpn(start, L"\r\n"); + if (l == 0) { + if (start[l] == L'\0') + break; + start++; + continue; + } + CHAR16 c = start[l]; + start[l] = L'\0'; + + populate_stanza(fh, dirname, filename, start); + + start[l] = c; + start += l; + } + + FreePool(buffer); + return EFI_SUCCESS; +} + +EFI_STATUS +find_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname) +{ + EFI_STATUS rc; + void *buffer = NULL; + UINTN bs = 0; + EFI_GUID finfo = EFI_FILE_INFO_ID; + + /* The API here is "Call it once with bs=0, it fills in bs, + * then allocate a buffer and ask again to get it filled. */ + rc = uefi_call_wrapper(fh->GetInfo, 4, fh, &finfo, &bs, NULL); + if (rc == EFI_BUFFER_TOO_SMALL) { + buffer = AllocateZeroPool(bs); + if (!buffer) { + Print(L"Could not allocate memory\n"); + return EFI_OUT_OF_RESOURCES; + } + rc = uefi_call_wrapper(fh->GetInfo, 4, fh, &finfo, + &bs, buffer); + } + /* This checks *either* the error from the first GetInfo, if it isn't + * the EFI_BUFFER_TOO_SMALL we're expecting, or the second GetInfo call + * in *any* case. */ + if (EFI_ERROR(rc)) { + Print(L"Could not get info for \"%s\": %d\n", dirname, rc); + if (buffer) + FreePool(buffer); + return rc; + } + + EFI_FILE_INFO *fi = buffer; + if (!(fi->Attribute & EFI_FILE_DIRECTORY)) { + FreePool(buffer); + return EFI_SUCCESS; + } + FreePool(buffer); + + bs = 0; + do { + bs = 0; + rc = uefi_call_wrapper(fh->Read, 3, fh, &bs, NULL); + if (rc == EFI_BUFFER_TOO_SMALL) { + buffer = AllocateZeroPool(bs); + if (!buffer) { + Print(L"Could not allocate memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + rc = uefi_call_wrapper(fh->Read, 3, fh, &bs, buffer); + } + if (EFI_ERROR(rc)) { + Print(L"Could not read \\EFI\\%s\\: %d\n", dirname, rc); + FreePool(buffer); + return rc; + } + if (bs == 0) + break; + + fi = buffer; + + if (!StrCaseCmp(fi->FileName, L"boot.csv")) { + EFI_FILE_HANDLE fh2; + rc = uefi_call_wrapper(fh->Open, 5, fh, &fh2, + fi->FileName, + EFI_FILE_READ_ONLY, 0); + if (EFI_ERROR(rc) || fh2 == NULL) { + Print(L"Couldn't open \\EFI\\%s\\%s: %d\n", + dirname, fi->FileName, rc); + FreePool(buffer); + buffer = NULL; + continue; + } + rc = try_boot_csv(fh2, dirname, fi->FileName); + uefi_call_wrapper(fh2->Close, 1, fh2); + } + + FreePool(buffer); + buffer = NULL; + } while (bs != 0); + + rc = EFI_SUCCESS; + if (nbootorder > 0) + rc = update_boot_order(); + + return rc; +} + +EFI_STATUS +find_boot_options(EFI_HANDLE device) +{ + EFI_STATUS rc = EFI_SUCCESS; + + EFI_FILE_IO_INTERFACE *fio = NULL; + rc = uefi_call_wrapper(BS->HandleProtocol, 3, device, + &FileSystemProtocol, &fio); + if (EFI_ERROR(rc)) { + Print(L"Couldn't find file system: %d\n", rc); + return rc; + } + + /* EFI_FILE_HANDLE is a pointer to an EFI_FILE, and I have + * *no idea* what frees the memory allocated here. Hopefully + * Close() does. */ + EFI_FILE_HANDLE fh = NULL; + rc = uefi_call_wrapper(fio->OpenVolume, 2, fio, &fh); + if (EFI_ERROR(rc) || fh == NULL) { + Print(L"Couldn't open file system: %d\n", rc); + return rc; + } + + EFI_FILE_HANDLE fh2 = NULL; + rc = uefi_call_wrapper(fh->Open, 5, fh, &fh2, L"EFI", + EFI_FILE_READ_ONLY, 0); + if (EFI_ERROR(rc) || fh2 == NULL) { + Print(L"Couldn't open EFI: %d\n", rc); + uefi_call_wrapper(fh->Close, 1, fh); + return rc; + } + rc = uefi_call_wrapper(fh2->SetPosition, 2, fh2, 0); + if (EFI_ERROR(rc)) { + Print(L"Couldn't set file position: %d\n", rc); + uefi_call_wrapper(fh2->Close, 1, fh2); + uefi_call_wrapper(fh->Close, 1, fh); + return rc; + } + + void *buffer; + UINTN bs; + do { + bs = 0; + rc = uefi_call_wrapper(fh2->Read, 3, fh2, &bs, NULL); + if (rc == EFI_BUFFER_TOO_SMALL || + (rc == EFI_SUCCESS && bs != 0)) { + buffer = AllocateZeroPool(bs); + if (!buffer) { + Print(L"Could not allocate memory\n"); + /* sure, this might work, why not? */ + uefi_call_wrapper(fh2->Close, 1, fh2); + uefi_call_wrapper(fh->Close, 1, fh); + return EFI_OUT_OF_RESOURCES; + } + + rc = uefi_call_wrapper(fh2->Read, 3, fh2, &bs, buffer); + } + if (bs == 0) + break; + + if (EFI_ERROR(rc)) { + Print(L"Could not read \\EFI\\: %d\n", rc); + if (buffer) { + FreePool(buffer); + buffer = NULL; + } + uefi_call_wrapper(fh2->Close, 1, fh2); + uefi_call_wrapper(fh->Close, 1, fh); + return rc; + } + EFI_FILE_INFO *fi = buffer; + + if (!(fi->Attribute & EFI_FILE_DIRECTORY)) { + FreePool(buffer); + buffer = NULL; + continue; + } + if (!StrCmp(fi->FileName, L".") || + !StrCmp(fi->FileName, L"..") || + !StrCaseCmp(fi->FileName, L"BOOT")) { + FreePool(buffer); + buffer = NULL; + continue; + } +#ifdef DEBUG_FALLBACK + Print(L"Found directory named \"%s\"\n", fi->FileName); +#endif + + EFI_FILE_HANDLE fh3; + rc = uefi_call_wrapper(fh->Open, 5, fh2, &fh3, fi->FileName, + EFI_FILE_READ_ONLY, 0); + if (EFI_ERROR(rc)) { + Print(L"%d Couldn't open %s: %d\n", __LINE__, fi->FileName, rc); + FreePool(buffer); + buffer = NULL; + continue; + } + + rc = find_boot_csv(fh3, fi->FileName); + FreePool(buffer); + buffer = NULL; + if (rc == EFI_OUT_OF_RESOURCES) + break; + + } while (1); + + uefi_call_wrapper(fh2->Close, 1, fh2); + uefi_call_wrapper(fh->Close, 1, fh); + return EFI_SUCCESS; +} +EFI_STATUS +efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) +{ + EFI_STATUS rc; + + InitializeLib(image, systab); + + rc = uefi_call_wrapper(BS->HandleProtocol, 3, image, &LoadedImageProtocol, (void *)&this_image); + if (EFI_ERROR(rc)) { + Print(L"Error: could not find loaded image: %d\n", rc); + return rc; + } + + Print(L"System BootOrder not found. Initializing defaults.\n"); + + rc = find_boot_options(this_image->DeviceHandle); + if (EFI_ERROR(rc)) { + Print(L"Error: could not find boot options: %d\n", rc); + return rc; + } + + return EFI_SUCCESS; +}