loader.efi: reduce the size of the staging area if necessary

The loader assumes physical memory in [2MB, 2MB + EFI_STAGING_SIZE)
is Conventional Memory, but actually it may not, e.g. in the case
of Hyper-V Generation-2 VM (i.e. UEFI VM) running on Windows
Server 2012 R2 host, there is a BootServiceData memory block at
the address 47.449MB and the memory is not writable.

Without the patch, the loader will crash in efi_copy_finish():
see PR 211746.

The patch verifies the end of the staging area, and reduces its
size if necessary. This way, the loader will not try to write into
the BootServiceData memory any longer.

Thank Marcel Moolenaar for helping me on this issue!

The patch also allocates the staging area in the first 1GB memory.
See the comment in the patch for this.

PR:		211746
Reviewed by:	marcel, kib, sephe
Approved by:	sephe (mentor)
MFC after:	2 weeks
Sponsored by:	Microsoft
Differential Revision:	https://reviews.freebsd.org/D9686
This commit is contained in:
Dexuan Cui 2017-03-02 07:25:50 +00:00
parent e51c23d9fb
commit 985455c0cc

View File

@ -39,12 +39,71 @@ __FBSDID("$FreeBSD$");
#include "loader_efi.h"
#if defined(__i386__) || defined(__amd64__)
#define KERNEL_PHYSICAL_BASE (2*1024*1024)
static void
efi_verify_staging_size(unsigned long *nr_pages)
{
UINTN sz;
EFI_MEMORY_DESCRIPTOR *map, *p;
EFI_PHYSICAL_ADDRESS start, end;
UINTN key, dsz;
UINT32 dver;
EFI_STATUS status;
int i, ndesc;
unsigned long available_pages;
sz = 0;
status = BS->GetMemoryMap(&sz, 0, &key, &dsz, &dver);
if (status != EFI_BUFFER_TOO_SMALL) {
printf("Can't determine memory map size\n");
return;
}
map = malloc(sz);
status = BS->GetMemoryMap(&sz, map, &key, &dsz, &dver);
if (EFI_ERROR(status)) {
printf("Can't read memory map\n");
goto out;
}
ndesc = sz / dsz;
for (i = 0, p = map; i < ndesc;
i++, p = NextMemoryDescriptor(p, dsz)) {
start = p->PhysicalStart;
end = start + p->NumberOfPages * EFI_PAGE_SIZE;
if (KERNEL_PHYSICAL_BASE < start ||
KERNEL_PHYSICAL_BASE >= end)
continue;
if (p->Type != EfiConventionalMemory)
continue;
available_pages = p->NumberOfPages -
((KERNEL_PHYSICAL_BASE - start) >> EFI_PAGE_SHIFT);
if (*nr_pages > available_pages) {
printf("staging area size is reduced: %ld -> %ld!\n",
*nr_pages, available_pages);
*nr_pages = available_pages;
}
break;
}
out:
free(map);
}
#endif
#ifndef EFI_STAGING_SIZE
#define EFI_STAGING_SIZE 64
#endif
#define STAGE_PAGES EFI_SIZE_TO_PAGES((EFI_STAGING_SIZE) * 1024 * 1024)
EFI_PHYSICAL_ADDRESS staging, staging_end;
int stage_offset_set = 0;
ssize_t stage_offset;
@ -54,14 +113,32 @@ efi_copy_init(void)
{
EFI_STATUS status;
unsigned long nr_pages;
nr_pages = EFI_SIZE_TO_PAGES((EFI_STAGING_SIZE) * 1024 * 1024);
#if defined(__i386__) || defined(__amd64__)
/* We'll decrease nr_pages, if it's too big. */
efi_verify_staging_size(&nr_pages);
/*
* The staging area must reside in the the first 1GB physical
* memory: see elf64_exec() in
* boot/efi/loader/arch/amd64/elf64_freebsd.c.
*/
staging = 1024*1024*1024;
status = BS->AllocatePages(AllocateMaxAddress, EfiLoaderData,
nr_pages, &staging);
#else
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
STAGE_PAGES, &staging);
nr_pages, &staging);
#endif
if (EFI_ERROR(status)) {
printf("failed to allocate staging area: %lu\n",
EFI_ERROR_CODE(status));
return (status);
}
staging_end = staging + STAGE_PAGES * EFI_PAGE_SIZE;
staging_end = staging + nr_pages * EFI_PAGE_SIZE;
#if defined(__aarch64__) || defined(__arm__)
/*