From 83c71c06efa3fea9b77c85322957f2ec80805904 Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Thu, 22 Jan 2015 00:03:42 +0100 Subject: find Linux kernels with an embedded os-release file Look for EFI executables at /EFI/Linux/*.efi, and add them to the menu if the PE file contains an .osrel section which carries an os-release file with the expected information. --- Makefile.am | 4 +- configure.ac | 2 +- src/efi/graphics.c | 2 +- src/efi/gummiboot.c | 156 ++++++++++++++++++++++++++++++++++-------- src/efi/pefile.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++ src/efi/pefile.h | 22 ++++++ src/efi/util.c | 22 ++++-- src/efi/util.h | 2 +- test/test-create-disk.sh | 4 ++ 9 files changed, 349 insertions(+), 39 deletions(-) create mode 100644 src/efi/pefile.c create mode 100644 src/efi/pefile.h diff --git a/Makefile.am b/Makefile.am index bf6ef17..c4cda7f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -79,12 +79,14 @@ efi_sources = \ src/efi/util.c \ src/efi/console.c \ src/efi/graphics.c \ + src/efi/pefile.c \ src/efi/gummiboot.c efi_headers = \ src/efi/util.h \ src/efi/console.h \ - src/efi/graphics.h + src/efi/graphics.h \ + src/efi/pefile.h efi_cppflags = \ $(EFI_CPPFLAGS) \ diff --git a/configure.ac b/configure.ac index c3f57ad..13306f2 100644 --- a/configure.ac +++ b/configure.ac @@ -68,7 +68,7 @@ AC_SUBST([MACHINE_TYPE_NAME]) AS_IF([test x"$cross_compiling" = "xyes"], [], [ AC_PATH_PROG([QEMU], [qemu-system-x86_64]) AC_CHECK_FILE([/usr/share/qemu/bios-ovmf.bin], [QEMU_BIOS=/usr/share/qemu/bios-ovmf.bin]) - AC_CHECK_FILE([/usr/share/qemu-ovmf/bios], [QEMU_BIOS=/usr/share/qemu-ovmf/bios/bios.bin]) + AC_CHECK_FILE([/usr/share/qemu-ovmf/bios.bin], [QEMU_BIOS=/usr/share/qemu-ovmf/bios.bin]) AC_SUBST([QEMU_BIOS]) ]) diff --git a/src/efi/graphics.c b/src/efi/graphics.c index 81b57e0..0810538 100644 --- a/src/efi/graphics.c +++ b/src/efi/graphics.c @@ -340,7 +340,7 @@ EFI_STATUS graphics_splash(EFI_FILE *root_dir, CHAR16 *path, if (EFI_ERROR(err)) return err; - len = file_read(root_dir, path, &content); + len = file_read(root_dir, path, 0, 0, &content); if (len < 0) return EFI_LOAD_ERROR; diff --git a/src/efi/gummiboot.c b/src/efi/gummiboot.c index 7ab291c..f9da90d 100644 --- a/src/efi/gummiboot.c +++ b/src/efi/gummiboot.c @@ -31,6 +31,7 @@ #include "util.h" #include "console.h" #include "graphics.h" +#include "pefile.h" #ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL @@ -991,7 +992,7 @@ static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2) return StrCmp(os1, os2); } -static CHAR8 *line_get_key_value(CHAR8 *content, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) { +static CHAR8 *line_get_key_value(CHAR8 *content, CHAR8 *sep, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) { CHAR8 *line; UINTN linelen; CHAR8 *value; @@ -1024,7 +1025,7 @@ skip: } /* remove trailing whitespace */ - while (linelen > 0 && strchra((CHAR8 *)" \t", line[linelen-1])) + while (linelen > 0 && strchra(sep, line[linelen-1])) linelen--; line[linelen] = '\0'; @@ -1033,15 +1034,21 @@ skip: /* split key/value */ value = line; - while (*value && !strchra((CHAR8 *)" \t", *value)) + while (*value && !strchra(sep, *value)) value++; if (*value == '\0') goto skip; *value = '\0'; value++; - while (*value && strchra((CHAR8 *)" \t", *value)) + while (*value && strchra(sep, *value)) value++; + /* unquote */ + if (value[0] == '\"' && line[linelen-1] == '\"') { + value++; + line[linelen-1] = '\0'; + } + *key_ret = line; *value_ret = value; return line; @@ -1053,7 +1060,7 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) { CHAR8 *key, *value; line = content; - while ((line = line_get_key_value(content, &pos, &key, &value))) { + while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { if (strcmpa((CHAR8 *)"timeout", key) == 0) { CHAR16 *s; @@ -1119,7 +1126,7 @@ static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR1 entry = AllocateZeroPool(sizeof(ConfigEntry)); line = content; - while ((line = line_get_key_value(content, &pos, &key, &value))) { + while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { if (strcmpa((CHAR8 *)"title", key) == 0) { FreePool(entry->title); entry->title = stra_to_str(value); @@ -1290,7 +1297,7 @@ static VOID config_load(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, UINTN len; UINTN i; - len = file_read(root_dir, L"\\loader\\loader.conf", &content); + len = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content); if (len > 0) config_defaults_load_from_file(config, content); FreePool(content); @@ -1327,7 +1334,7 @@ static VOID config_load(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, if (StriCmp(f->FileName + len - 5, L".conf") != 0) continue; - len = file_read(entries_dir, f->FileName, &content); + len = file_read(entries_dir, f->FileName, 0, 0, &content); if (len > 0) config_entry_add_from_file(config, device, f->FileName, content, loaded_image_path); FreePool(content); @@ -1562,11 +1569,26 @@ static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS ( return TRUE; } -static BOOLEAN config_entry_add_loader(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path, - CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { +static ConfigEntry *config_entry_add_loader(Config *config, EFI_HANDLE *device, CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { + ConfigEntry *entry; + + entry = AllocateZeroPool(sizeof(ConfigEntry)); + entry->title = StrDuplicate(title); + entry->device = device; + entry->loader = StrDuplicate(loader); + entry->file = StrDuplicate(file); + StrLwr(entry->file); + entry->key = key; + config_add_entry(config, entry); + + return entry; +} + +static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path, + CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { EFI_FILE_HANDLE handle; - EFI_STATUS err; ConfigEntry *entry; + EFI_STATUS err; /* do not add an entry for ourselves */ if (loaded_image_path && StriCmp(loader, loaded_image_path) == 0) @@ -1578,29 +1600,16 @@ static BOOLEAN config_entry_add_loader(Config *config, EFI_HANDLE *device, EFI_F return FALSE; uefi_call_wrapper(handle->Close, 1, handle); - entry = AllocateZeroPool(sizeof(ConfigEntry)); - entry->title = StrDuplicate(title); - entry->device = device; - entry->loader = StrDuplicate(loader); - entry->file = StrDuplicate(file); - StrLwr(entry->file); - entry->key = key; - config_add_entry(config, entry); + entry = config_entry_add_loader(config, device, file, key, title, loader); + if (!entry) + return FALSE; - /* do not boot right away into aut-detected entries */ + /* do not boot right away into auto-detected entries */ entry->no_autoselect = TRUE; /* do not show a splash; they do not need one, or they draw their own */ entry->splash = StrDuplicate(L""); - return TRUE; -} - -static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path, - CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { - if (!config_entry_add_loader(config, device, root_dir, loaded_image_path, file, key, title, loader)) - return FALSE; - /* export identifiers of automatically added entries */ if (config->entries_auto) { CHAR16 *s; @@ -1641,11 +1650,98 @@ static VOID config_entry_add_osx(Config *config) { } } -static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, const ConfigEntry *entry) { +static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_image, EFI_FILE *root_dir) { + EFI_FILE_HANDLE linux_dir; EFI_STATUS err; + + err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &linux_dir, L"\\EFI\\Linux", EFI_FILE_MODE_READ, 0ULL); + if (!EFI_ERROR(err)) { + for (;;) { + CHAR16 buf[256]; + UINTN bufsize; + EFI_FILE_INFO *f; + CHAR8 *sections[2] = { (UINT8 *)".osrel", NULL }; + UINTN offs[1] = {}; + UINTN szs[1] = {}; + UINTN addrs[1] = {}; + CHAR8 *content = NULL; + UINTN len; + CHAR8 *line; + UINTN pos = 0; + CHAR8 *key, *value; + CHAR16 *os_name = NULL; + CHAR16 *os_id = NULL; + CHAR16 *os_version = NULL; + + bufsize = sizeof(buf); + err = uefi_call_wrapper(linux_dir->Read, 3, linux_dir, &bufsize, buf); + if (bufsize == 0 || EFI_ERROR(err)) + break; + + f = (EFI_FILE_INFO *) buf; + if (f->FileName[0] == '.') + continue; + if (f->Attribute & EFI_FILE_DIRECTORY) + continue; + len = StrLen(f->FileName); + if (len < 5) + continue; + if (StriCmp(f->FileName + len - 4, L".efi") != 0) + continue; + + /* look for an .osrel section in the .efi binary */ + err = pefile_locate_sections(linux_dir, f->FileName, sections, addrs, offs, szs); + if (EFI_ERROR(err)) + continue; + + len = file_read(linux_dir, f->FileName, offs[0], szs[0], &content); + if (len <= 0) + continue; + + /* read properties from the embedded os-release file */ + line = content; + while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) { + if (strcmpa((CHAR8 *)"PRETTY_NAME", key) == 0) { + os_name = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"ID", key) == 0) { + os_id = stra_to_str(value); + continue; + } + + if (strcmpa((CHAR8 *)"VERSION_ID", key) == 0) { + os_version = stra_to_str(value); + continue; + } + } + + if (os_name && os_id && os_version) { + CHAR16 *conf; + CHAR16 *path; + + conf = PoolPrint(L"%s-%s", os_id, os_version); + path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName); + config_entry_add_loader(config, loaded_image->DeviceHandle, conf, 'l', os_name, path); + FreePool(conf); + FreePool(path); + FreePool(os_name); + FreePool(os_id); + FreePool(os_version); + } + + FreePool(content); + } + uefi_call_wrapper(linux_dir->Close, 1, linux_dir); + } +} + +static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, const ConfigEntry *entry) { EFI_HANDLE image; EFI_DEVICE_PATH *path; CHAR16 *options; + EFI_STATUS err; path = FileDevicePath(entry->device, entry->loader); if (!path) { @@ -1796,6 +1892,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { return EFI_LOAD_ERROR; } + /* the filesystem path to this image, to prevent adding ourselves to the menu */ loaded_image_path = DevicePathToStr(loaded_image->FilePath); efivar_set(L"LoaderImageIdentifier", loaded_image_path, FALSE); @@ -1814,6 +1911,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { } /* if we find some well-known loaders, add them to the end of the list */ + config_entry_add_linux(&config, loaded_image, root_dir); config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, L"auto-windows", 'w', L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi"); config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path, diff --git a/src/efi/pefile.c b/src/efi/pefile.c new file mode 100644 index 0000000..6ac28da --- /dev/null +++ b/src/efi/pefile.c @@ -0,0 +1,174 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * Copyright (C) 2012-2013 Kay Sievers + * Copyright (C) 2012 Harald Hoyer + */ + +#include +#include + +#include "util.h" +#include "pefile.h" + +struct DosFileHeader { + UINT8 Magic[2]; + UINT16 LastSize; + UINT16 nBlocks; + UINT16 nReloc; + UINT16 HdrSize; + UINT16 MinAlloc; + UINT16 MaxAlloc; + UINT16 ss; + UINT16 sp; + UINT16 Checksum; + UINT16 ip; + UINT16 cs; + UINT16 RelocPos; + UINT16 nOverlay; + UINT16 reserved[4]; + UINT16 OEMId; + UINT16 OEMInfo; + UINT16 reserved2[10]; + UINT32 ExeHeader; +} __attribute__((packed)); + +#define PE_HEADER_MACHINE_I386 0x014c +#define PE_HEADER_MACHINE_X64 0x8664 +struct PeFileHeader { + UINT16 Machine; + UINT16 NumberOfSections; + UINT32 TimeDateStamp; + UINT32 PointerToSymbolTable; + UINT32 NumberOfSymbols; + UINT16 SizeOfOptionalHeader; + UINT16 Characteristics; +} __attribute__((packed)); + +struct PeSectionHeader { + UINT8 Name[8]; + UINT32 VirtualSize; + UINT32 VirtualAddress; + UINT32 SizeOfRawData; + UINT32 PointerToRawData; + UINT32 PointerToRelocations; + UINT32 PointerToLinenumbers; + UINT16 NumberOfRelocations; + UINT16 NumberOfLinenumbers; + UINT32 Characteristics; +} __attribute__((packed)); + + +EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes) { + EFI_FILE_HANDLE handle; + struct DosFileHeader dos; + uint8_t magic[4]; + struct PeFileHeader pe; + UINTN len; + UINTN i; + EFI_STATUS err; + + err = uefi_call_wrapper(dir->Open, 5, dir, &handle, path, EFI_FILE_MODE_READ, 0ULL); + if (EFI_ERROR(err)) + return err; + + /* MS-DOS stub */ + len = sizeof(dos); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, &dos); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(dos)) { + err = EFI_LOAD_ERROR; + goto out; + } + + if (CompareMem(dos.Magic, "MZ", 2) != 0) { + err = EFI_LOAD_ERROR; + goto out; + } + + err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader); + if (EFI_ERROR(err)) + goto out; + + /* PE header */ + len = sizeof(magic); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, &magic); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(magic)) { + err = EFI_LOAD_ERROR; + goto out; + } + + if (CompareMem(magic, "PE\0\0", 2) != 0) { + err = EFI_LOAD_ERROR; + goto out; + } + + len = sizeof(pe); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, &pe); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(pe)) { + err = EFI_LOAD_ERROR; + goto out; + } + + /* PE32+ Subsystem type */ + if (pe.Machine != PE_HEADER_MACHINE_X64 && + pe.Machine != PE_HEADER_MACHINE_I386) { + err = EFI_LOAD_ERROR; + goto out; + } + + if (pe.NumberOfSections > 96) { + err = EFI_LOAD_ERROR; + goto out; + } + + /* the sections start directly after the headers */ + err = uefi_call_wrapper(handle->SetPosition, 2, handle, dos.ExeHeader + sizeof(magic) + sizeof(pe) + pe.SizeOfOptionalHeader); + if (EFI_ERROR(err)) + goto out; + + for (i = 0; i < pe.NumberOfSections; i++) { + struct PeSectionHeader sect; + UINTN j; + + len = sizeof(sect); + err = uefi_call_wrapper(handle->Read, 3, handle, &len, §); + if (EFI_ERROR(err)) + goto out; + if (len != sizeof(sect)) { + err = EFI_LOAD_ERROR; + goto out; + } + + for (j = 0; sections[j]; j++) { + if (strcmpa(sections[j], sect.Name) != 0) + continue; + + if (addrs) + addrs[j] = (UINTN)sect.VirtualAddress; + if (offsets) + offsets[j] = (UINTN)sect.PointerToRawData; + if (sizes) + sizes[j] = (UINTN)sect.VirtualSize; + } + } + +out: + uefi_call_wrapper(handle->Close, 1, handle); + return err; +} diff --git a/src/efi/pefile.h b/src/efi/pefile.h new file mode 100644 index 0000000..3adf1b0 --- /dev/null +++ b/src/efi/pefile.h @@ -0,0 +1,22 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * Copyright (C) 2015 Kay Sievers + */ + +#ifndef __GUMMIBOOT_PEFILE_H +#define __GUMMIBOOT_PEFILE_H + +EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, + CHAR8 **sections, UINTN *addrs, UINTN *offsets, UINTN *sizes); +#endif diff --git a/src/efi/util.c b/src/efi/util.c index 7cb8e0f..191c071 100644 --- a/src/efi/util.c +++ b/src/efi/util.c @@ -280,9 +280,8 @@ CHAR8 *strchra(CHAR8 *s, CHAR8 c) { return NULL; } -INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content) { +INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content) { EFI_FILE_HANDLE handle; - EFI_FILE_INFO *info; CHAR8 *buf; UINTN buflen; EFI_STATUS err; @@ -292,10 +291,22 @@ INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content) { if (EFI_ERROR(err)) return err; - info = LibFileInfo(handle); - buflen = info->FileSize+1; - buf = AllocatePool(buflen); + if (size == 0) { + EFI_FILE_INFO *info; + + info = LibFileInfo(handle); + buflen = info->FileSize+1; + FreePool(info); + } else + buflen = size; + if (off > 0) { + err = uefi_call_wrapper(handle->SetPosition, 2, handle, off); + if (EFI_ERROR(err)) + return err; + } + + buf = AllocatePool(buflen); err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf); if (!EFI_ERROR(err)) { buf[buflen] = '\0'; @@ -306,7 +317,6 @@ INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content) { FreePool(buf); } - FreePool(info); uefi_call_wrapper(handle->Close, 1, handle); return len; } diff --git a/src/efi/util.h b/src/efi/util.h index ce767bb..91757c4 100644 --- a/src/efi/util.h +++ b/src/efi/util.h @@ -40,5 +40,5 @@ CHAR8 *strchra(CHAR8 *s, CHAR8 c); CHAR16 *stra_to_path(CHAR8 *stra); CHAR16 *stra_to_str(CHAR8 *stra); -INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content); +INTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content); #endif diff --git a/test/test-create-disk.sh b/test/test-create-disk.sh index ead5129..3982223 100755 --- a/test/test-create-disk.sh +++ b/test/test-create-disk.sh @@ -18,6 +18,10 @@ cp test/splash.bmp mnt/EFI/gummiboot/ [ -e /boot/shellx64.efi ] && cp /boot/shellx64.efi mnt/ +mkdir mnt/EFI/Linux +objcopy --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \ + gummibootx64.efi mnt/EFI/Linux/Test-Linux.efi + # install entries mkdir -p mnt/loader/entries echo -e "timeout 3\nsplash /EFI/gummiboot/splash.bmp\n" > mnt/loader/loader.conf -- cgit v1.2.3