blob: bab862662ab240559cbad8c8229e5dec7d0b8a97 [file] [log] [blame]
/* chainloader.c - boot another boot loader */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2002,2004,2006,2007,2008 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
/* TODO: support load options. */
#include <grub/loader.h>
#include <grub/file.h>
#include <grub/err.h>
#include <grub/device.h>
#include <grub/disk.h>
#include <grub/misc.h>
#include <grub/charset.h>
#include <grub/mm.h>
#include <grub/types.h>
#include <grub/dl.h>
#include <grub/efi/api.h>
#include <grub/efi/efi.h>
#include <grub/efi/disk.h>
#include <grub/efi/pe32.h>
#include <grub/efi/linux.h>
#include <grub/efi/sb.h>
#include <grub/command.h>
#include <grub/i18n.h>
#include <grub/net.h>
#if defined (__i386__) || defined (__x86_64__)
#include <grub/macho.h>
#include <grub/i386/macho.h>
#endif
GRUB_MOD_LICENSE ("GPLv3+");
static grub_dl_t my_mod;
static grub_efi_physical_address_t address;
static grub_efi_uintn_t pages;
static grub_ssize_t fsize;
static grub_efi_device_path_t *file_path;
static grub_efi_handle_t image_handle;
static grub_efi_char16_t *cmdline;
static grub_ssize_t cmdline_len;
static grub_efi_handle_t dev_handle;
static grub_efi_status_t (*entry_point) (grub_efi_handle_t image_handle, grub_efi_system_table_t *system_table);
static grub_err_t
grub_chainloader_unload (void)
{
grub_efi_boot_services_t *b;
b = grub_efi_system_table->boot_services;
efi_call_1 (b->unload_image, image_handle);
efi_call_2 (b->free_pages, address, pages);
grub_free (file_path);
grub_free (cmdline);
cmdline = 0;
file_path = 0;
dev_handle = 0;
grub_dl_unref (my_mod);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_chainloader_boot (void)
{
grub_efi_boot_services_t *b;
grub_efi_status_t status;
grub_efi_uintn_t exit_data_size;
grub_efi_char16_t *exit_data = NULL;
b = grub_efi_system_table->boot_services;
status = efi_call_3 (b->start_image, image_handle, &exit_data_size, &exit_data);
if (status != GRUB_EFI_SUCCESS)
{
if (exit_data)
{
char *buf;
buf = grub_malloc (exit_data_size * 4 + 1);
if (buf)
{
*grub_utf16_to_utf8 ((grub_uint8_t *) buf,
exit_data, exit_data_size) = 0;
grub_error (GRUB_ERR_BAD_OS, buf);
grub_free (buf);
}
}
else
grub_error (GRUB_ERR_BAD_OS, "unknown error");
}
if (exit_data)
efi_call_1 (b->free_pool, exit_data);
grub_loader_unset ();
return grub_errno;
}
static void
copy_file_path (grub_efi_file_path_device_path_t *fp,
const char *str, grub_efi_uint16_t len)
{
grub_efi_char16_t *p;
grub_efi_uint16_t size;
fp->header.type = GRUB_EFI_MEDIA_DEVICE_PATH_TYPE;
fp->header.subtype = GRUB_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE;
size = grub_utf8_to_utf16 (fp->path_name, len * GRUB_MAX_UTF16_PER_UTF8,
(const grub_uint8_t *) str, len, 0);
for (p = fp->path_name; p < fp->path_name + size; p++)
if (*p == '/')
*p = '\\';
/* File Path is NULL terminated */
fp->path_name[size++] = '\0';
fp->header.length = size * sizeof (grub_efi_char16_t) + sizeof (*fp);
}
static grub_efi_device_path_t *
make_file_path (grub_efi_device_path_t *dp, const char *filename)
{
char *dir_start;
char *dir_end;
grub_size_t size;
grub_efi_device_path_t *d;
dir_start = grub_strchr (filename, ')');
if (! dir_start)
dir_start = (char *) filename;
else
dir_start++;
dir_end = grub_strrchr (dir_start, '/');
if (! dir_end)
{
grub_error (GRUB_ERR_BAD_FILENAME, "invalid EFI file path");
return 0;
}
size = 0;
d = dp;
while (1)
{
size += GRUB_EFI_DEVICE_PATH_LENGTH (d);
if ((GRUB_EFI_END_ENTIRE_DEVICE_PATH (d)))
break;
d = GRUB_EFI_NEXT_DEVICE_PATH (d);
}
/* File Path is NULL terminated. Allocate space for 2 extra characters */
/* FIXME why we split path in two components? */
file_path = grub_malloc (size
+ ((grub_strlen (dir_start) + 2)
* GRUB_MAX_UTF16_PER_UTF8
* sizeof (grub_efi_char16_t))
+ sizeof (grub_efi_file_path_device_path_t) * 2);
if (! file_path)
return 0;
grub_memcpy (file_path, dp, size);
/* Fill the file path for the directory. */
d = (grub_efi_device_path_t *) ((char *) file_path
+ ((char *) d - (char *) dp));
copy_file_path ((grub_efi_file_path_device_path_t *) d,
dir_start, dir_end - dir_start);
/* Fill the file path for the file. */
d = GRUB_EFI_NEXT_DEVICE_PATH (d);
copy_file_path ((grub_efi_file_path_device_path_t *) d,
dir_end + 1, grub_strlen (dir_end + 1));
/* Fill the end of device path nodes. */
d = GRUB_EFI_NEXT_DEVICE_PATH (d);
d->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
d->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
d->length = sizeof (*d);
return file_path;
}
#define SHIM_LOCK_GUID \
{ 0x605dab50, 0xe046, 0x4300, { 0xab,0xb6,0x3d,0xd8,0x10,0xdd,0x8b,0x23 } }
typedef union
{
struct grub_pe32_header_32 pe32;
struct grub_pe32_header_64 pe32plus;
} grub_pe_header_t;
struct pe_coff_loader_image_context
{
grub_efi_uint64_t image_address;
grub_efi_uint64_t image_size;
grub_efi_uint64_t entry_point;
grub_efi_uintn_t size_of_headers;
grub_efi_uint16_t image_type;
grub_efi_uint16_t number_of_sections;
grub_efi_uint32_t section_alignment;
struct grub_pe32_section_table *first_section;
struct grub_pe32_data_directory *reloc_dir;
struct grub_pe32_data_directory *sec_dir;
grub_efi_uint64_t number_of_rva_and_sizes;
grub_pe_header_t *pe_hdr;
};
typedef struct pe_coff_loader_image_context pe_coff_loader_image_context_t;
struct grub_efi_shim_lock
{
grub_efi_status_t (*verify)(void *buffer,
grub_efi_uint32_t size);
grub_efi_status_t (*hash)(void *data,
grub_efi_int32_t datasize,
pe_coff_loader_image_context_t *context,
grub_efi_uint8_t *sha256hash,
grub_efi_uint8_t *sha1hash);
grub_efi_status_t (*context)(void *data,
grub_efi_uint32_t size,
pe_coff_loader_image_context_t *context);
};
typedef struct grub_efi_shim_lock grub_efi_shim_lock_t;
static grub_efi_boolean_t
read_header (void *data, grub_efi_uint32_t size,
pe_coff_loader_image_context_t *context)
{
grub_efi_guid_t guid = SHIM_LOCK_GUID;
grub_efi_shim_lock_t *shim_lock;
grub_efi_status_t status;
shim_lock = grub_efi_locate_protocol (&guid, NULL);
if (!shim_lock)
{
grub_dprintf ("chain", "no shim lock protocol");
return 0;
}
status = shim_lock->context (data, size, context);
if (status == GRUB_EFI_SUCCESS)
{
grub_dprintf ("chain", "context success\n");
return 1;
}
switch (status)
{
case GRUB_EFI_UNSUPPORTED:
grub_error (GRUB_ERR_BAD_ARGUMENT, "context error unsupported");
break;
case GRUB_EFI_INVALID_PARAMETER:
grub_error (GRUB_ERR_BAD_ARGUMENT, "context error invalid parameter");
break;
default:
grub_error (GRUB_ERR_BAD_ARGUMENT, "context error code");
break;
}
return -1;
}
static void*
image_address (void *image, grub_efi_uint64_t sz, grub_efi_uint64_t adr)
{
if (adr > sz)
return NULL;
return ((grub_uint8_t*)image + adr);
}
static int
image_is_64_bit (grub_pe_header_t *pe_hdr)
{
/* .Magic is the same offset in all cases */
if (pe_hdr->pe32plus.optional_header.magic == GRUB_PE32_PE64_MAGIC)
return 1;
return 0;
}
static const grub_uint16_t machine_type __attribute__((__unused__)) =
#if defined(__x86_64__)
GRUB_PE32_MACHINE_X86_64;
#elif defined(__aarch64__)
GRUB_PE32_MACHINE_ARM64;
#elif defined(__arm__)
GRUB_PE32_MACHINE_ARMTHUMB_MIXED;
#elif defined(__i386__) || defined(__i486__) || defined(__i686__)
GRUB_PE32_MACHINE_I386;
#elif defined(__ia64__)
GRUB_PE32_MACHINE_IA64;
#else
#error this architecture is not supported by grub2
#endif
static grub_efi_status_t
relocate_coff (pe_coff_loader_image_context_t *context,
struct grub_pe32_section_table *section,
void *orig, void *data)
{
struct grub_pe32_data_directory *reloc_base, *reloc_base_end;
grub_efi_uint64_t adjust;
struct grub_pe32_fixup_block *reloc, *reloc_end;
char *fixup, *fixup_base, *fixup_data = NULL;
grub_efi_uint16_t *fixup_16;
grub_efi_uint32_t *fixup_32;
grub_efi_uint64_t *fixup_64;
grub_efi_uint64_t size = context->image_size;
void *image_end = (char *)orig + size;
int n = 0;
if (image_is_64_bit (context->pe_hdr))
context->pe_hdr->pe32plus.optional_header.image_base =
(grub_uint64_t)(unsigned long)data;
else
context->pe_hdr->pe32.optional_header.image_base =
(grub_uint32_t)(unsigned long)data;
/* Alright, so here's how this works:
*
* context->reloc_dir gives us two things:
* - the VA the table of base relocation blocks are (maybe) to be
* mapped at (reloc_dir->rva)
* - the virtual size (reloc_dir->size)
*
* The .reloc section (section here) gives us some other things:
* - the name! kind of. (section->name)
* - the virtual size (section->virtual_size), which should be the same
* as RelocDir->Size
* - the virtual address (section->virtual_address)
* - the file section size (section->raw_data_size), which is
* a multiple of optional_header->file_alignment. Only useful for image
* validation, not really useful for iteration bounds.
* - the file address (section->raw_data_offset)
* - a bunch of stuff we don't use that's 0 in our binaries usually
* - Flags (section->characteristics)
*
* and then the thing that's actually at the file address is an array
* of struct grub_pe32_fixup_block structs with some values packed behind
* them. The block_size field of this structure includes the
* structure itself, and adding it to that structure's address will
* yield the next entry in the array.
*/
reloc_base = image_address (orig, size, section->raw_data_offset);
reloc_base_end = image_address (orig, size, section->raw_data_offset
+ section->virtual_size);
grub_dprintf ("chain", "relocate_coff(): reloc_base %p reloc_base_end %p\n",
reloc_base, reloc_base_end);
if (!reloc_base && !reloc_base_end)
return GRUB_EFI_SUCCESS;
if (!reloc_base || !reloc_base_end)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "Reloc table overflows binary");
return GRUB_EFI_UNSUPPORTED;
}
adjust = (grub_uint64_t)data - context->image_address;
if (adjust == 0)
return GRUB_EFI_SUCCESS;
while (reloc_base < reloc_base_end)
{
grub_uint16_t *entry;
reloc = (struct grub_pe32_fixup_block *)((char*)reloc_base);
if ((reloc_base->size == 0) ||
(reloc_base->size > context->reloc_dir->size))
{
grub_error (GRUB_ERR_BAD_ARGUMENT,
"Reloc %d block size %d is invalid\n", n,
reloc_base->size);
return GRUB_EFI_UNSUPPORTED;
}
entry = &reloc->entries[0];
reloc_end = (struct grub_pe32_fixup_block *)
((char *)reloc_base + reloc_base->size);
if ((void *)reloc_end < data || (void *)reloc_end > image_end)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "Reloc entry %d overflows binary",
n);
return GRUB_EFI_UNSUPPORTED;
}
fixup_base = image_address(data, size, reloc_base->rva);
if (!fixup_base)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "Reloc %d Invalid fixupbase", n);
return GRUB_EFI_UNSUPPORTED;
}
while ((void *)entry < (void *)reloc_end)
{
fixup = fixup_base + (*entry & 0xFFF);
switch ((*entry) >> 12)
{
case GRUB_PE32_REL_BASED_ABSOLUTE:
break;
case GRUB_PE32_REL_BASED_HIGH:
fixup_16 = (grub_uint16_t *)fixup;
*fixup_16 = (grub_uint16_t)
(*fixup_16 + ((grub_uint16_t)((grub_uint32_t)adjust >> 16)));
if (fixup_data != NULL)
{
*(grub_uint16_t *) fixup_data = *fixup_16;
fixup_data = fixup_data + sizeof (grub_uint16_t);
}
break;
case GRUB_PE32_REL_BASED_LOW:
fixup_16 = (grub_uint16_t *)fixup;
*fixup_16 = (grub_uint16_t) (*fixup_16 + (grub_uint16_t)adjust);
if (fixup_data != NULL)
{
*(grub_uint16_t *) fixup_data = *fixup_16;
fixup_data = fixup_data + sizeof (grub_uint16_t);
}
break;
case GRUB_PE32_REL_BASED_HIGHLOW:
fixup_32 = (grub_uint32_t *)fixup;
*fixup_32 = *fixup_32 + (grub_uint32_t)adjust;
if (fixup_data != NULL)
{
fixup_data = (char *)ALIGN_UP ((grub_addr_t)fixup_data, sizeof (grub_uint32_t));
*(grub_uint32_t *) fixup_data = *fixup_32;
fixup_data += sizeof (grub_uint32_t);
}
break;
case GRUB_PE32_REL_BASED_DIR64:
fixup_64 = (grub_uint64_t *)fixup;
*fixup_64 = *fixup_64 + (grub_uint64_t)adjust;
if (fixup_data != NULL)
{
fixup_data = (char *)ALIGN_UP ((grub_addr_t)fixup_data, sizeof (grub_uint64_t));
*(grub_uint64_t *) fixup_data = *fixup_64;
fixup_data += sizeof (grub_uint64_t);
}
break;
default:
grub_error (GRUB_ERR_BAD_ARGUMENT,
"Reloc %d unknown relocation type %d",
n, (*entry) >> 12);
return GRUB_EFI_UNSUPPORTED;
}
entry += 1;
}
reloc_base = (struct grub_pe32_data_directory *)reloc_end;
n++;
}
return GRUB_EFI_SUCCESS;
}
static grub_efi_device_path_t *
grub_efi_get_media_file_path (grub_efi_device_path_t *dp)
{
while (1)
{
grub_efi_uint8_t type = GRUB_EFI_DEVICE_PATH_TYPE (dp);
grub_efi_uint8_t subtype = GRUB_EFI_DEVICE_PATH_SUBTYPE (dp);
if (type == GRUB_EFI_END_DEVICE_PATH_TYPE)
break;
else if (type == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE
&& subtype == GRUB_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE)
return dp;
dp = GRUB_EFI_NEXT_DEVICE_PATH (dp);
}
return NULL;
}
static grub_efi_boolean_t
handle_image (void *data, grub_efi_uint32_t datasize)
{
grub_efi_boot_services_t *b;
grub_efi_loaded_image_t *li, li_bak;
grub_efi_status_t efi_status;
char *buffer = NULL;
char *buffer_aligned = NULL;
grub_efi_uint32_t i;
struct grub_pe32_section_table *section;
char *base, *end;
pe_coff_loader_image_context_t context;
grub_uint32_t section_alignment;
grub_uint32_t buffer_size;
int found_entry_point = 0;
int rc;
b = grub_efi_system_table->boot_services;
rc = read_header (data, datasize, &context);
if (rc < 0)
{
grub_dprintf ("chain", "Failed to read header\n");
goto error_exit;
}
else if (rc == 0)
{
grub_dprintf ("chain", "Secure Boot is not enabled\n");
return 0;
}
else
{
grub_dprintf ("chain", "Header read without error\n");
}
/*
* The spec says, uselessly, of SectionAlignment:
* =====
* The alignment (in bytes) of sections when they are loaded into
* memory. It must be greater than or equal to FileAlignment. The
* default is the page size for the architecture.
* =====
* Which doesn't tell you whose responsibility it is to enforce the
* "default", or when. It implies that the value in the field must
* be > FileAlignment (also poorly defined), but it appears visual
* studio will happily write 512 for FileAlignment (its default) and
* 0 for SectionAlignment, intending to imply PAGE_SIZE.
*
* We only support one page size, so if it's zero, nerf it to 4096.
*/
section_alignment = context.section_alignment;
if (section_alignment == 0)
section_alignment = 4096;
buffer_size = context.image_size + section_alignment;
grub_dprintf ("chain", "image size is %08lx, datasize is %08x\n",
context.image_size, datasize);
efi_status = efi_call_3 (b->allocate_pool, GRUB_EFI_LOADER_DATA,
buffer_size, &buffer);
if (efi_status != GRUB_EFI_SUCCESS)
{
grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
goto error_exit;
}
buffer_aligned = (char *)ALIGN_UP ((grub_addr_t)buffer, section_alignment);
if (!buffer_aligned)
{
grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
goto error_exit;
}
grub_memcpy (buffer_aligned, data, context.size_of_headers);
entry_point = image_address (buffer_aligned, context.image_size,
context.entry_point);
grub_dprintf ("chain", "entry_point: %p\n", entry_point);
if (!entry_point)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid entry point");
goto error_exit;
}
char *reloc_base, *reloc_base_end;
grub_dprintf ("chain", "reloc_dir: %p reloc_size: 0x%08x\n",
(void *)(unsigned long long)context.reloc_dir->rva,
context.reloc_dir->size);
reloc_base = image_address (buffer_aligned, context.image_size,
context.reloc_dir->rva);
/* RelocBaseEnd here is the address of the last byte of the table */
reloc_base_end = image_address (buffer_aligned, context.image_size,
context.reloc_dir->rva
+ context.reloc_dir->size - 1);
grub_dprintf ("chain", "reloc_base: %p reloc_base_end: %p\n",
reloc_base, reloc_base_end);
struct grub_pe32_section_table *reloc_section = NULL;
section = context.first_section;
for (i = 0; i < context.number_of_sections; i++, section++)
{
char name[9];
base = image_address (buffer_aligned, context.image_size,
section->virtual_address);
end = image_address (buffer_aligned, context.image_size,
section->virtual_address + section->virtual_size -1);
grub_strncpy(name, section->name, 9);
name[8] = '\0';
grub_dprintf ("chain", "Section %d \"%s\" at %p..%p\n", i,
name, base, end);
if (end < base)
{
grub_dprintf ("chain", " base is %p but end is %p... bad.\n",
base, end);
grub_error (GRUB_ERR_BAD_ARGUMENT,
"Image has invalid negative size");
goto error_exit;
}
if (section->virtual_address <= context.entry_point &&
(section->virtual_address + section->raw_data_size - 1)
> context.entry_point)
{
found_entry_point++;
grub_dprintf ("chain", " section contains entry point\n");
}
/* We do want to process .reloc, but it's often marked
* discardable, so we don't want to memcpy it. */
if (grub_memcmp (section->name, ".reloc\0\0", 8) == 0)
{
if (reloc_section)
{
grub_error (GRUB_ERR_BAD_ARGUMENT,
"Image has multiple relocation sections");
goto error_exit;
}
/* If it has nonzero sizes, and our bounds check
* made sense, and the VA and size match RelocDir's
* versions, then we believe in this section table. */
if (section->raw_data_size && section->virtual_size &&
base && end && reloc_base == base && reloc_base_end == end)
{
grub_dprintf ("chain", " section is relocation section\n");
reloc_section = section;
}
else
{
grub_dprintf ("chain", " section is not reloc section?\n");
grub_dprintf ("chain", " rds: 0x%08x, vs: %08x\n",
section->raw_data_size, section->virtual_size);
grub_dprintf ("chain", " base: %p end: %p\n", base, end);
grub_dprintf ("chain", " reloc_base: %p reloc_base_end: %p\n",
reloc_base, reloc_base_end);
}
}
grub_dprintf ("chain", " Section characteristics are %08x\n",
section->characteristics);
grub_dprintf ("chain", " Section virtual size: %08x\n",
section->virtual_size);
grub_dprintf ("chain", " Section raw_data size: %08x\n",
section->raw_data_size);
if (section->characteristics & GRUB_PE32_SCN_MEM_DISCARDABLE)
{
grub_dprintf ("chain", " Discarding section\n");
continue;
}
if (!base || !end)
{
grub_dprintf ("chain", " section is invalid\n");
grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid section size");
goto error_exit;
}
if (section->characteristics & GRUB_PE32_SCN_CNT_UNINITIALIZED_DATA)
{
if (section->raw_data_size != 0)
grub_dprintf ("chain", " UNINITIALIZED_DATA section has data?\n");
}
else if (section->virtual_address < context.size_of_headers ||
section->raw_data_offset < context.size_of_headers)
{
grub_error (GRUB_ERR_BAD_ARGUMENT,
"Section %d is inside image headers", i);
goto error_exit;
}
if (section->raw_data_size > 0)
{
grub_dprintf ("chain", " copying 0x%08x bytes to %p\n",
section->raw_data_size, base);
grub_memcpy (base,
(grub_efi_uint8_t*)data + section->raw_data_offset,
section->raw_data_size);
}
if (section->raw_data_size < section->virtual_size)
{
grub_dprintf ("chain", " padding with 0x%08x bytes at %p\n",
section->virtual_size - section->raw_data_size,
base + section->raw_data_size);
grub_memset (base + section->raw_data_size, 0,
section->virtual_size - section->raw_data_size);
}
grub_dprintf ("chain", " finished section %s\n", name);
}
/* 5 == EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC */
if (context.number_of_rva_and_sizes <= 5)
{
grub_dprintf ("chain", "image has no relocation entry\n");
goto error_exit;
}
if (context.reloc_dir->size && reloc_section)
{
/* run the relocation fixups */
efi_status = relocate_coff (&context, reloc_section, data,
buffer_aligned);
if (efi_status != GRUB_EFI_SUCCESS)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "relocation failed");
goto error_exit;
}
}
if (!found_entry_point)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "entry point is not within sections");
goto error_exit;
}
if (found_entry_point > 1)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "%d sections contain entry point",
found_entry_point);
goto error_exit;
}
li = grub_efi_get_loaded_image (grub_efi_image_handle);
if (!li)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "no loaded image available");
goto error_exit;
}
grub_memcpy (&li_bak, li, sizeof (grub_efi_loaded_image_t));
li->image_base = buffer_aligned;
li->image_size = context.image_size;
li->load_options = cmdline;
li->load_options_size = cmdline_len;
li->file_path = grub_efi_get_media_file_path (file_path);
li->device_handle = dev_handle;
if (!li->file_path)
{
grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no matching file path found");
goto error_exit;
}
grub_dprintf ("chain", "booting via entry point\n");
efi_status = efi_call_2 (entry_point, grub_efi_image_handle,
grub_efi_system_table);
grub_dprintf ("chain", "entry_point returned %ld\n", efi_status);
grub_memcpy (li, &li_bak, sizeof (grub_efi_loaded_image_t));
efi_status = efi_call_1 (b->free_pool, buffer);
return 1;
error_exit:
grub_dprintf ("chain", "error_exit: grub_errno: %d\n", grub_errno);
if (buffer)
efi_call_1 (b->free_pool, buffer);
return 0;
}
static grub_err_t
grub_secureboot_chainloader_unload (void)
{
grub_efi_boot_services_t *b;
b = grub_efi_system_table->boot_services;
efi_call_2 (b->free_pages, address, pages);
grub_free (file_path);
grub_free (cmdline);
cmdline = 0;
file_path = 0;
dev_handle = 0;
grub_dl_unref (my_mod);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_load_and_start_image(void *boot_image)
{
grub_efi_boot_services_t *b;
grub_efi_status_t status;
grub_efi_loaded_image_t *loaded_image;
b = grub_efi_system_table->boot_services;
status = efi_call_6 (b->load_image, 0, grub_efi_image_handle, file_path,
boot_image, fsize, &image_handle);
if (status != GRUB_EFI_SUCCESS)
{
if (status == GRUB_EFI_OUT_OF_RESOURCES)
grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of resources");
else
grub_error (GRUB_ERR_BAD_OS, "cannot load image");
return -1;
}
/* LoadImage does not set a device handler when the image is
loaded from memory, so it is necessary to set it explicitly here.
This is a mess. */
loaded_image = grub_efi_get_loaded_image (image_handle);
if (! loaded_image)
{
grub_error (GRUB_ERR_BAD_OS, "no loaded image available");
return -1;
}
loaded_image->device_handle = dev_handle;
if (cmdline)
{
loaded_image->load_options = cmdline;
loaded_image->load_options_size = cmdline_len;
}
return 0;
}
static grub_err_t
grub_secureboot_chainloader_boot (void)
{
int rc;
rc = handle_image ((void *)address, fsize);
if (rc == 0)
{
grub_load_and_start_image((void *)address);
}
grub_loader_unset ();
return grub_errno;
}
static grub_err_t
grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_file_t file = 0;
grub_efi_status_t status;
grub_efi_boot_services_t *b;
grub_device_t dev = 0;
grub_efi_device_path_t *dp = 0;
char *filename;
void *boot_image = 0;
int rc;
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
filename = argv[0];
grub_dl_ref (my_mod);
/* Initialize some global variables. */
address = 0;
image_handle = 0;
file_path = 0;
dev_handle = 0;
b = grub_efi_system_table->boot_services;
if (argc > 1)
{
int i;
grub_efi_char16_t *p16;
for (i = 1, cmdline_len = 0; i < argc; i++)
cmdline_len += grub_strlen (argv[i]) + 1;
cmdline_len *= sizeof (grub_efi_char16_t);
cmdline = p16 = grub_malloc (cmdline_len);
if (! cmdline)
goto fail;
for (i = 1; i < argc; i++)
{
char *p8;
p8 = argv[i];
while (*p8)
*(p16++) = *(p8++);
*(p16++) = ' ';
}
*(--p16) = 0;
}
file = grub_file_open (filename, GRUB_FILE_TYPE_EFI_CHAINLOADED_IMAGE);
if (! file)
goto fail;
/* Get the device path from filename. */
char *devname = grub_file_get_device_name (filename);
dev = grub_device_open (devname);
if (devname)
grub_free (devname);
if (! dev)
goto fail;
if (dev->disk)
dev_handle = grub_efidisk_get_device_handle (dev->disk);
else if (dev->net && dev->net->server)
{
grub_net_network_level_address_t addr;
struct grub_net_network_level_interface *inf;
grub_net_network_level_address_t gateway;
grub_err_t err;
err = grub_net_resolve_address (dev->net->server, &addr);
if (err)
goto fail;
err = grub_net_route_address (addr, &gateway, &inf);
if (err)
goto fail;
dev_handle = grub_efinet_get_device_handle (inf->card);
}
if (dev_handle)
dp = grub_efi_get_device_path (dev_handle);
if (! dp)
{
grub_error (GRUB_ERR_BAD_DEVICE, "not a valid root device");
goto fail;
}
file_path = make_file_path (dp, filename);
if (! file_path)
goto fail;
fsize = grub_file_size (file);
if (!fsize)
{
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
filename);
goto fail;
}
pages = (((grub_efi_uintn_t) fsize + ((1 << 12) - 1)) >> 12);
status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_ANY_PAGES,
GRUB_EFI_LOADER_CODE,
pages, &address);
if (status != GRUB_EFI_SUCCESS)
{
grub_dprintf ("chain", "Failed to allocate %u pages\n",
(unsigned int) pages);
grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
goto fail;
}
boot_image = (void *) ((grub_addr_t) address);
if (grub_file_read (file, boot_image, fsize) != fsize)
{
if (grub_errno == GRUB_ERR_NONE)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
filename);
goto fail;
}
#if defined (__i386__) || defined (__x86_64__)
if (fsize >= (grub_ssize_t) sizeof (struct grub_macho_fat_header))
{
struct grub_macho_fat_header *head = boot_image;
if (head->magic
== grub_cpu_to_le32_compile_time (GRUB_MACHO_FAT_EFI_MAGIC))
{
grub_uint32_t i;
struct grub_macho_fat_arch *archs
= (struct grub_macho_fat_arch *) (head + 1);
if (grub_efi_secure_boot())
{
grub_error (GRUB_ERR_BAD_OS,
"MACHO binaries are forbidden with Secure Boot");
goto fail;
}
for (i = 0; i < grub_cpu_to_le32 (head->nfat_arch); i++)
{
if (GRUB_MACHO_CPUTYPE_IS_HOST_CURRENT (archs[i].cputype))
break;
}
if (i == grub_cpu_to_le32 (head->nfat_arch))
{
grub_error (GRUB_ERR_BAD_OS, "no compatible arch found");
goto fail;
}
if (grub_cpu_to_le32 (archs[i].offset)
> ~grub_cpu_to_le32 (archs[i].size)
|| grub_cpu_to_le32 (archs[i].offset)
+ grub_cpu_to_le32 (archs[i].size)
> (grub_size_t) fsize)
{
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
filename);
goto fail;
}
boot_image = (char *) boot_image + grub_cpu_to_le32 (archs[i].offset);
fsize = grub_cpu_to_le32 (archs[i].size);
}
}
#endif
rc = grub_linuxefi_secure_validate((void *)address, fsize);
grub_dprintf ("chain", "linuxefi_secure_validate: %d\n", rc);
if (rc > 0)
{
grub_file_close (file);
grub_loader_set (grub_secureboot_chainloader_boot,
grub_secureboot_chainloader_unload, 0);
return 0;
}
else if (rc == 0)
{
grub_load_and_start_image(boot_image);
grub_file_close (file);
grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 0);
return 0;
}
// -1 fall-through to fail
grub_file_close (file);
grub_device_close (dev);
fail:
if (dev)
grub_device_close (dev);
if (file)
grub_file_close (file);
grub_free (file_path);
if (address)
efi_call_2 (b->free_pages, address, pages);
if (cmdline)
grub_free (cmdline);
grub_dl_unref (my_mod);
return grub_errno;
}
static grub_command_t cmd;
GRUB_MOD_INIT(chainloader)
{
cmd = grub_register_command ("chainloader", grub_cmd_chainloader,
0, N_("Load another boot loader."));
my_mod = mod;
}
GRUB_MOD_FINI(chainloader)
{
grub_unregister_command (cmd);
}