/* SPDX-License-Identifier: LGPL-2.1+ */
#include <efi.h>
+#include <efigpt.h>
#include <efilib.h>
#include "console.h"
+#include "crc32.h"
#include "disk.h"
#include "graphics.h"
#include "linux.h"
+#include "loader-features.h"
#include "measure.h"
#include "pe.h"
+#include "random-seed.h"
#include "shim.h"
#include "util.h"
};
typedef struct {
- CHAR16 *id; /* The identifier for this entry (note that this id is not necessarily unique though!) */
+ CHAR16 *id; /* The unique identifier for this entry */
CHAR16 *title_show;
CHAR16 *title;
CHAR16 *version;
BOOLEAN force_menu;
UINTN console_mode;
enum console_mode_change_type console_mode_change;
+ RandomSeedMode random_seed_mode;
} Config;
static VOID cursor_left(UINTN *cursor, UINTN *first) {
case KEYPRESS(0, 0, CHAR_LINEFEED):
case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN):
- if (StrCmp(line, line_in) != 0) {
- *line_out = line;
- line = NULL;
- }
+ if (StrCmp(line, line_in) != 0)
+ *line_out = TAKE_PTR(line);
enter = TRUE;
exit = TRUE;
break;
Print(L"editor: %s\n", yes_no(config->editor));
Print(L"auto-entries: %s\n", yes_no(config->auto_entries));
Print(L"auto-firmware: %s\n", yes_no(config->auto_firmware));
+
+ switch (config->random_seed_mode) {
+ case RANDOM_SEED_OFF:
+ Print(L"random-seed-mode: off\n");
+ break;
+ case RANDOM_SEED_WITH_SYSTEM_TOKEN:
+ Print(L"random-seed-node: with-system-token\n");
+ break;
+ case RANDOM_SEED_ALWAYS:
+ Print(L"random-seed-node: always\n");
+ break;
+ default:
+ ;
+ }
+
Print(L"\n");
Print(L"config entry count: %d\n", config->entry_count);
BOOLEAN exit = FALSE;
BOOLEAN run = TRUE;
BOOLEAN wait = FALSE;
- BOOLEAN cleared_screen = FALSE;
graphics_mode(FALSE);
uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE);
if (config->console_mode_change != CONSOLE_MODE_KEEP) {
err = console_set_mode(&config->console_mode, config->console_mode_change);
- if (!EFI_ERROR(err))
- cleared_screen = TRUE;
- }
-
- if (!cleared_screen)
+ if (EFI_ERROR(err)) {
+ uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
+ Print(L"Error switching console mode to %ld: %r.\r", (UINT64)config->console_mode, err);
+ }
+ } else
uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
- if (config->console_mode_change != CONSOLE_MODE_KEEP && EFI_ERROR(err))
- Print(L"Error switching console mode to %ld: %r.\r", (UINT64)config->console_mode, err);
-
err = uefi_call_wrapper(ST->ConOut->QueryMode, 4, ST->ConOut, ST->ConOut->Mode->Mode, &x_max, &y_max);
if (EFI_ERROR(err)) {
x_max = 80;
value++;
/* unquote */
- if (value[0] == '\"' && line[linelen-1] == '\"') {
+ if (value[0] == '"' && line[linelen-1] == '"') {
value++;
line[linelen-1] = '\0';
}
if (EFI_ERROR(parse_boolean(value, &on)))
continue;
+
config->editor = on;
+ continue;
}
if (strcmpa((CHAR8 *)"auto-entries", key) == 0) {
if (EFI_ERROR(parse_boolean(value, &on)))
continue;
+
config->auto_entries = on;
+ continue;
}
if (strcmpa((CHAR8 *)"auto-firmware", key) == 0) {
if (EFI_ERROR(parse_boolean(value, &on)))
continue;
+
config->auto_firmware = on;
+ continue;
}
if (strcmpa((CHAR8 *)"console-mode", key) == 0) {
continue;
}
+
+ if (strcmpa((CHAR8*) "random-seed-mode", key) == 0) {
+ if (strcmpa((CHAR8*) "off", value) == 0)
+ config->random_seed_mode = RANDOM_SEED_OFF;
+ else if (strcmpa((CHAR8*) "with-system-token", value) == 0)
+ config->random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN;
+ else if (strcmpa((CHAR8*) "always", value) == 0)
+ config->random_seed_mode = RANDOM_SEED_ALWAYS;
+ else {
+ BOOLEAN on;
+
+ if (EFI_ERROR(parse_boolean(value, &on)))
+ continue;
+
+ config->random_seed_mode = on ? RANDOM_SEED_ALWAYS : RANDOM_SEED_OFF;
+ }
+ }
}
}
/* If the file we just renamed is the loader path, then let's update that. */
if (StrCmp(entry->loader, old_path) == 0) {
FreePool(entry->loader);
- entry->loader = new_path;
- new_path = NULL;
+ entry->loader = TAKE_PTR(new_path);
}
}
static VOID config_entry_add_from_file(
Config *config,
EFI_HANDLE *device,
+ EFI_FILE *root_dir,
CHAR16 *path,
CHAR16 *file,
CHAR8 *content,
CHAR8 *line;
UINTN pos = 0;
CHAR8 *key, *value;
- UINTN len;
+ EFI_STATUS err;
+ EFI_FILE_HANDLE handle;
_cleanup_freepool_ CHAR16 *initrd = NULL;
entry = AllocatePool(sizeof(ConfigEntry));
entry->loader = stra_to_path(value);
/* do not add an entry for ourselves */
- if (StriCmp(entry->loader, loaded_image_path) == 0) {
+ if (loaded_image_path && StriCmp(entry->loader, loaded_image_path) == 0) {
entry->type = LOADER_UNDEFINED;
break;
}
s = PoolPrint(L"%s %s", entry->options, new);
FreePool(entry->options);
entry->options = s;
- } else {
- entry->options = new;
- new = NULL;
- }
+ } else
+ entry->options = TAKE_PTR(new);
continue;
}
return;
}
+ /* check existence */
+ err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, entry->loader, EFI_FILE_MODE_READ, 0ULL);
+ if (EFI_ERROR(err)) {
+ config_entry_free(entry);
+ return;
+ }
+ uefi_call_wrapper(handle->Close, 1, handle);
+
/* add initrd= to options */
if (entry->type == LOADER_LINUX && initrd) {
if (entry->options) {
s = PoolPrint(L"%s %s", initrd, entry->options);
FreePool(entry->options);
entry->options = s;
- } else {
- entry->options = initrd;
- initrd = NULL;
- }
+ } else
+ entry->options = TAKE_PTR(initrd);
}
entry->device = device;
entry->id = StrDuplicate(file);
- len = StrLen(entry->id);
- /* remove ".conf" */
- if (len > 5)
- entry->id[len - 5] = '\0';
StrLwr(entry->id);
config_add_entry(config, entry);
.editor = TRUE,
.auto_entries = TRUE,
.auto_firmware = TRUE,
+ .random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN,
};
err = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content, NULL);
err = file_read(entries_dir, f->FileName, 0, 0, &content, NULL);
if (!EFI_ERROR(err))
- config_entry_add_from_file(config, device, L"\\loader\\entries", f->FileName, content, loaded_image_path);
+ config_entry_add_from_file(config, device, root_dir, L"\\loader\\entries", f->FileName, content, loaded_image_path);
}
uefi_call_wrapper(entries_dir->Close, 1, entries_dir);
}
CHAR16 *id,
CHAR16 key,
CHAR16 *title,
- CHAR16 *loader) {
+ CHAR16 *loader,
+ CHAR16 *version) {
ConfigEntry *entry;
*entry = (ConfigEntry) {
.type = type,
.title = StrDuplicate(title),
+ .version = StrDuplicate(version),
.device = device,
.loader = StrDuplicate(loader),
.id = StrDuplicate(id),
return FALSE;
uefi_call_wrapper(handle->Close, 1, handle);
- entry = config_entry_add_loader(config, device, LOADER_UNDEFINED, id, key, title, loader);
+ entry = config_entry_add_loader(config, device, LOADER_UNDEFINED, id, key, title, loader, NULL);
if (!entry)
return FALSE;
static VOID config_entry_add_linux(
Config *config,
- EFI_LOADED_IMAGE *loaded_image,
+ EFI_HANDLE *device,
EFI_FILE *root_dir) {
EFI_FILE_HANDLE linux_dir;
CHAR8 *line;
UINTN pos = 0;
CHAR8 *key, *value;
+ CHAR16 *os_name_pretty = NULL;
CHAR16 *os_name = NULL;
CHAR16 *os_id = NULL;
CHAR16 *os_version = NULL;
- CHAR16 *os_build = NULL;
+ CHAR16 *os_version_id = NULL;
+ CHAR16 *os_build_id = NULL;
err = uefi_call_wrapper(linux_dir->Read, 3, linux_dir, &bufsize, buf);
if (bufsize == 0 || EFI_ERROR(err))
continue;
if (StriCmp(f->FileName + len - 4, L".efi") != 0)
continue;
+ if (StrnCmp(f->FileName, L"auto-", 5) == 0)
+ continue;
/* look for .osrel and .cmdline sections in the .efi binary */
err = pe_file_locate_sections(linux_dir, f->FileName, sections, addrs, offs, szs);
/* read properties from the embedded os-release file */
while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) {
if (strcmpa((CHAR8 *)"PRETTY_NAME", key) == 0) {
+ FreePool(os_name_pretty);
+ os_name_pretty = stra_to_str(value);
+ continue;
+ }
+
+ if (strcmpa((CHAR8 *)"NAME", key) == 0) {
FreePool(os_name);
os_name = stra_to_str(value);
continue;
continue;
}
+ if (strcmpa((CHAR8 *)"VERSION_ID", key) == 0) {
+ FreePool(os_version_id);
+ os_version_id = stra_to_str(value);
+ continue;
+ }
+
if (strcmpa((CHAR8 *)"BUILD_ID", key) == 0) {
- FreePool(os_build);
- os_build = stra_to_str(value);
+ FreePool(os_build_id);
+ os_build_id = stra_to_str(value);
continue;
}
}
- if (os_name && os_id && (os_version || os_build)) {
- _cleanup_freepool_ CHAR16 *conf = NULL, *path = NULL;
+ if ((os_name_pretty || os_name) && os_id && (os_version || os_version_id || os_build_id)) {
+ _cleanup_freepool_ CHAR16 *path = NULL;
- conf = PoolPrint(L"%s-%s", os_id, os_version ? : os_build);
path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName);
- entry = config_entry_add_loader(config, loaded_image->DeviceHandle, LOADER_LINUX, conf, 'l', os_name, path);
+ entry = config_entry_add_loader(config, device, LOADER_LINUX, f->FileName, 'l',
+ os_name_pretty ? : (os_name ? : os_id), path,
+ os_version ? : (os_version_id ? : os_build_id));
FreePool(content);
content = NULL;
config_entry_parse_tries(entry, L"\\EFI\\Linux", f->FileName, L".efi");
}
+ FreePool(os_name_pretty);
FreePool(os_name);
FreePool(os_id);
FreePool(os_version);
- FreePool(os_build);
+ FreePool(os_version_id);
+ FreePool(os_build_id);
FreePool(content);
}
uefi_call_wrapper(linux_dir->Close, 1, linux_dir);
}
+/* Note that this is in GUID format, i.e. the first 32bit, and the following pair of 16bit are byteswapped. */
+static const UINT8 xbootldr_guid[16] = {
+ 0xff, 0xc2, 0x13, 0xbc, 0xe6, 0x59, 0x62, 0x42, 0xa3, 0x52, 0xb2, 0x75, 0xfd, 0x6f, 0x71, 0x72
+};
+
+EFI_DEVICE_PATH *path_parent(EFI_DEVICE_PATH *path, EFI_DEVICE_PATH *node) {
+ EFI_DEVICE_PATH *parent;
+ UINTN len;
+
+ len = (UINT8*) NextDevicePathNode(node) - (UINT8*) path;
+ parent = (EFI_DEVICE_PATH*) AllocatePool(len + sizeof(EFI_DEVICE_PATH));
+ CopyMem(parent, path, len);
+ CopyMem((UINT8*) parent + len, EndDevicePath, sizeof(EFI_DEVICE_PATH));
+
+ return parent;
+}
+
+static VOID config_load_xbootldr(
+ Config *config,
+ EFI_HANDLE *device) {
+
+ EFI_DEVICE_PATH *partition_path, *node, *disk_path, *copy;
+ UINT32 found_partition_number = (UINT32) -1;
+ UINT64 found_partition_start = (UINT64) -1;
+ UINT64 found_partition_size = (UINT64) -1;
+ UINT8 found_partition_signature[16] = {};
+ EFI_HANDLE new_device;
+ EFI_FILE *root_dir;
+ EFI_STATUS r;
+
+ partition_path = DevicePathFromHandle(device);
+ if (!partition_path)
+ return;
+
+ for (node = partition_path; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) {
+ EFI_HANDLE disk_handle;
+ EFI_BLOCK_IO *block_io;
+ EFI_DEVICE_PATH *p;
+ UINTN nr;
+
+ /* First, Let's look for the SCSI/SATA/USB/… device path node, i.e. one above the media
+ * devices */
+ if (DevicePathType(node) != MESSAGING_DEVICE_PATH)
+ continue;
+
+ /* Determine the device path one level up */
+ disk_path = path_parent(partition_path, node);
+ p = disk_path;
+ r = uefi_call_wrapper(BS->LocateDevicePath, 3, &BlockIoProtocol, &p, &disk_handle);
+ if (EFI_ERROR(r))
+ continue;
+
+ r = uefi_call_wrapper(BS->HandleProtocol, 3, disk_handle, &BlockIoProtocol, (VOID **)&block_io);
+ if (EFI_ERROR(r))
+ continue;
+
+ /* Filter out some block devices early. (We only care about block devices that aren't
+ * partitions themselves — we look for GPT partition tables to parse after all —, and only
+ * those which contain a medium and have at least 2 blocks.) */
+ if (block_io->Media->LogicalPartition ||
+ !block_io->Media->MediaPresent ||
+ block_io->Media->LastBlock <= 1)
+ continue;
+
+ /* Try both copies of the GPT header, in case one is corrupted */
+ for (nr = 0; nr < 2; nr++) {
+ _cleanup_freepool_ EFI_PARTITION_ENTRY* entries = NULL;
+ union {
+ EFI_PARTITION_TABLE_HEADER gpt_header;
+ uint8_t space[((sizeof(EFI_PARTITION_TABLE_HEADER) + 511) / 512) * 512];
+ } gpt_header_buffer;
+ const EFI_PARTITION_TABLE_HEADER *h = &gpt_header_buffer.gpt_header;
+ UINT64 where;
+ UINTN i, sz;
+ UINT32 c;
+
+ if (nr == 0)
+ /* Read the first copy at LBA 1 */
+ where = 1;
+ else
+ /* Read the second copy at the very last LBA of this block device */
+ where = block_io->Media->LastBlock;
+
+ /* Read the GPT header */
+ r = uefi_call_wrapper(block_io->ReadBlocks, 5,
+ block_io,
+ block_io->Media->MediaId,
+ where,
+ sizeof(gpt_header_buffer), &gpt_header_buffer);
+ if (EFI_ERROR(r))
+ continue;
+
+ /* Some superficial validation of the GPT header */
+ c = CompareMem(&h->Header.Signature, "EFI PART", sizeof(h->Header.Signature));
+ if (c != 0)
+ continue;
+
+ if (h->Header.HeaderSize < 92 ||
+ h->Header.HeaderSize > 512)
+ continue;
+
+ if (h->Header.Revision != 0x00010000U)
+ continue;
+
+ /* Calculate CRC check */
+ c = ~crc32_exclude_offset((UINT32) -1,
+ (const UINT8*) &gpt_header_buffer,
+ h->Header.HeaderSize,
+ OFFSETOF(EFI_PARTITION_TABLE_HEADER, Header.CRC32),
+ sizeof(h->Header.CRC32));
+ if (c != h->Header.CRC32)
+ continue;
+
+ if (h->MyLBA != where)
+ continue;
+
+ if (h->SizeOfPartitionEntry < sizeof(EFI_PARTITION_ENTRY))
+ continue;
+
+ if (h->NumberOfPartitionEntries <= 0 ||
+ h->NumberOfPartitionEntries > 1024)
+ continue;
+
+ if (h->SizeOfPartitionEntry > UINTN_MAX / h->NumberOfPartitionEntries) /* overflow check */
+ continue;
+
+ /* Now load the GPT entry table */
+ sz = ALIGN_TO((UINTN) h->SizeOfPartitionEntry * (UINTN) h->NumberOfPartitionEntries, 512);
+ entries = AllocatePool(sz);
+
+ r = uefi_call_wrapper(block_io->ReadBlocks, 5,
+ block_io,
+ block_io->Media->MediaId,
+ h->PartitionEntryLBA,
+ sz, entries);
+ if (EFI_ERROR(r))
+ continue;
+
+ /* Calculate CRC of entries array, too */
+ c = ~crc32((UINT32) -1, entries, sz);
+ if (c != h->PartitionEntryArrayCRC32)
+ continue;
+
+ for (i = 0; i < h->NumberOfPartitionEntries; i++) {
+ EFI_PARTITION_ENTRY *entry;
+
+ entry = (EFI_PARTITION_ENTRY*) ((UINT8*) entries + h->SizeOfPartitionEntry * i);
+
+ if (CompareMem(&entry->PartitionTypeGUID, xbootldr_guid, 16) == 0) {
+ UINT64 end;
+
+ /* Let's use memcpy(), in case the structs are not aligned (they really should be though) */
+ CopyMem(&found_partition_start, &entry->StartingLBA, sizeof(found_partition_start));
+ CopyMem(&end, &entry->EndingLBA, sizeof(end));
+
+ if (end < found_partition_start) /* Bogus? */
+ continue;
+
+ found_partition_size = end - found_partition_start + 1;
+ CopyMem(found_partition_signature, &entry->UniquePartitionGUID, sizeof(found_partition_signature));
+
+ found_partition_number = i + 1;
+ goto found;
+ }
+ }
+
+ break; /* This GPT was fully valid, but we didn't find what we are looking for. This
+ * means there's no reason to check the second copy of the GPT header */
+ }
+ }
+
+ return; /* Not found */
+
+found:
+ copy = DuplicateDevicePath(partition_path);
+
+ /* Patch in the data we found */
+ for (node = copy; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) {
+ HARDDRIVE_DEVICE_PATH *hd;
+
+ if (DevicePathType(node) != MEDIA_DEVICE_PATH)
+ continue;
+
+ if (DevicePathSubType(node) != MEDIA_HARDDRIVE_DP)
+ continue;
+
+ hd = (HARDDRIVE_DEVICE_PATH*) node;
+ hd->PartitionNumber = found_partition_number;
+ hd->PartitionStart = found_partition_start;
+ hd->PartitionSize = found_partition_size;
+ CopyMem(hd->Signature, found_partition_signature, sizeof(hd->Signature));
+ hd->MBRType = MBR_TYPE_EFI_PARTITION_TABLE_HEADER;
+ hd->SignatureType = SIGNATURE_TYPE_GUID;
+ }
+
+ r = uefi_call_wrapper(BS->LocateDevicePath, 3, &BlockIoProtocol, ©, &new_device);
+ if (EFI_ERROR(r))
+ return;
+
+ root_dir = LibOpenRoot(new_device);
+ if (!root_dir)
+ return;
+
+ config_entry_add_linux(config, new_device, root_dir);
+ config_load_entries(config, new_device, root_dir, NULL);
+}
+
static EFI_STATUS image_start(
EFI_HANDLE parent_image,
const Config *config,
EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
static const UINT64 loader_features =
- (1ULL << 0) | /* I honour the LoaderConfigTimeout variable */
- (1ULL << 1) | /* I honour the LoaderConfigTimeoutOneShot variable */
- (1ULL << 2) | /* I honour the LoaderEntryDefault variable */
- (1ULL << 3) | /* I honour the LoaderEntryOneShot variable */
- (1ULL << 4) | /* I support boot counting */
+ EFI_LOADER_FEATURE_CONFIG_TIMEOUT |
+ EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT |
+ EFI_LOADER_FEATURE_ENTRY_DEFAULT |
+ EFI_LOADER_FEATURE_ENTRY_ONESHOT |
+ EFI_LOADER_FEATURE_BOOT_COUNTING |
+ EFI_LOADER_FEATURE_XBOOTLDR |
+ EFI_LOADER_FEATURE_RANDOM_SEED |
0;
_cleanup_freepool_ CHAR16 *infostr = NULL, *typestr = NULL;
err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image,
image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (EFI_ERROR(err)) {
- Print(L"Error getting a LoadedImageProtocol handle: %r ", err);
+ Print(L"Error getting a LoadedImageProtocol handle: %r", err);
uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
return err;
}
root_dir = LibOpenRoot(loaded_image->DeviceHandle);
if (!root_dir) {
- Print(L"Unable to open root directory: %r ", err);
+ Print(L"Unable to open root directory.");
uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
return EFI_LOAD_ERROR;
}
config_load_defaults(&config, root_dir);
/* scan /EFI/Linux/ directory */
- config_entry_add_linux(&config, loaded_image, root_dir);
+ config_entry_add_linux(&config, loaded_image->DeviceHandle, root_dir);
/* scan /loader/entries/\*.conf files */
config_load_entries(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path);
+ /* Similar, but on any XBOOTLDR partition */
+ config_load_xbootldr(&config, loaded_image->DeviceHandle);
+
/* sort entries after version number */
config_sort_entries(&config);
UINT64 osind = (UINT64)*b;
if (osind & EFI_OS_INDICATIONS_BOOT_TO_FW_UI)
- config_entry_add_call(&config, L"auto-reboot-to-firmware-setup", L"Reboot Into Firmware Interface", reboot_into_firmware);
+ config_entry_add_call(&config,
+ L"auto-reboot-to-firmware-setup",
+ L"Reboot Into Firmware Interface",
+ reboot_into_firmware);
FreePool(b);
}
UINT64 key;
err = console_key_read(&key, FALSE);
+
+ if (err == EFI_NOT_READY) {
+ uefi_call_wrapper(BS->Stall, 1, 100 * 1000);
+ err = console_key_read(&key, FALSE);
+ }
+
if (!EFI_ERROR(err)) {
INT16 idx;
config_entry_bump_counters(entry, root_dir);
- /* export the selected boot entry to the system */
- efivar_set(L"LoaderEntrySelected", entry->id, FALSE);
+ /* Export the selected boot entry to the system */
+ (VOID) efivar_set(L"LoaderEntrySelected", entry->id, FALSE);
+
+ /* Optionally, read a random seed off the ESP and pass it to the OS */
+ (VOID) process_random_seed(root_dir, config.random_seed_mode);
uefi_call_wrapper(BS->SetWatchdogTimer, 4, 5 * 60, 0x10000, 0, NULL);
err = image_start(image, &config, entry);