efi-boot-shim/netboot.c
Gary Ching-Pang Lin f500a8742c Fetch the netboot image from the same device
The previous strategy is to locate the first available PXE_BASE_CODE
protocol and to fetch the second stage image from it, and this may
cause shim to fetch the wrong second stage image, i.e. grub.efi.

Consider the machine with the following boot order:
1. PXE Boot
2. Hard Drive

Assume that the EFI image, e.g. bootx64.efi, in the PXE server is
broken, then "PXE Boot" will fail and fallback to "Hard Drive". While
shim.efi in "Hard Drive" is loaded, it will find the PXE protocol is
available and fetch grub.efi from the PXE server, not grub.efi in the
disk.

This commit checks the DeviceHandle from Loaded Image. If the device
supports PXE, then shim fetches grub.efi with the PXE protocol. Otherwise,
shim loads grub.efi from the disk.

Signed-off-by: Gary Ching-Pang Lin <glin@suse.com>
2014-06-25 09:53:23 -04:00

351 lines
8.4 KiB
C

/*
* netboot - trivial UEFI first-stage bootloader netboot support
*
* Copyright 2012 Red Hat, Inc <mjg@redhat.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Significant portions of this code are derived from Tianocore
* (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
* Corporation.
*/
#include <efi.h>
#include <efilib.h>
#include <string.h>
#include "shim.h"
#include "netboot.h"
#include "str.h"
static inline unsigned short int __swap16(unsigned short int x)
{
__asm__("xchgb %b0,%h0"
: "=q" (x)
: "0" (x));
return x;
}
#define ntohs(x) __swap16(x)
#define htons(x) ntohs(x)
static EFI_PXE_BASE_CODE *pxe;
static EFI_IP_ADDRESS tftp_addr;
static CHAR8 *full_path;
typedef struct {
UINT16 OpCode;
UINT16 Length;
UINT8 Data[1];
} EFI_DHCP6_PACKET_OPTION;
static CHAR8 *
translate_slashes(char *str)
{
int i;
int j;
if (str == NULL)
return (CHAR8 *)str;
for (i = 0, j = 0; str[i] != '\0'; i++, j++) {
if (str[i] == '\\') {
str[j] = '/';
if (str[i+1] == '\\')
i++;
}
}
return (CHAR8 *)str;
}
/*
* usingNetboot
* Returns TRUE if we identify a protocol that is enabled and Providing us with
* the needed information to fetch a grubx64.efi image
*/
BOOLEAN findNetboot(EFI_HANDLE device)
{
EFI_STATUS status;
status = uefi_call_wrapper(BS->HandleProtocol, 3, device,
&PxeBaseCodeProtocol, (VOID **)&pxe);
if (status != EFI_SUCCESS) {
pxe = NULL;
return FALSE;
}
if (!pxe || !pxe->Mode) {
pxe = NULL;
return FALSE;
}
if (!pxe->Mode->Started || !pxe->Mode->DhcpAckReceived) {
pxe = NULL;
return FALSE;
}
/*
* We've located a pxe protocol handle thats been started and has
* received an ACK, meaning its something we'll be able to get
* tftp server info out of
*/
return TRUE;
}
static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
{
void *optr;
EFI_DHCP6_PACKET_OPTION *option;
CHAR8 *url;
UINT32 urllen;
optr = pkt->DhcpOptions;
for(;;) {
option = (EFI_DHCP6_PACKET_OPTION *)optr;
if (ntohs(option->OpCode) == 0)
return NULL;
if (ntohs(option->OpCode) == 59) {
/* This is the bootfile url option */
urllen = ntohs(option->Length);
url = AllocateZeroPool(urllen+1);
if (!url)
return NULL;
memcpy(url, option->Data, urllen);
return url;
}
optr += 4 + ntohs(option->Length);
}
return NULL;
}
static CHAR16 str2ns(CHAR8 *str)
{
CHAR16 ret = 0;
CHAR8 v;
for(;*str;str++) {
if ('0' <= *str && *str <= '9')
v = *str - '0';
else if ('A' <= *str && *str <= 'F')
v = *str - 'A' + 10;
else if ('a' <= *str && *str <= 'f')
v = *str - 'a' + 10;
else
v = 0;
ret = (ret << 4) + v;
}
return htons(ret);
}
static CHAR8 *str2ip6(CHAR8 *str)
{
UINT8 i, j, p;
size_t len;
CHAR8 *a, *b, t;
static UINT16 ip[8];
for(i=0; i < 8; i++) {
ip[i] = 0;
}
len = strlen(str);
a = b = str;
for(i=p=0; i < len; i++, b++) {
if (*b != ':')
continue;
*b = '\0';
ip[p++] = str2ns(a);
*b = ':';
a = b + 1;
if ( *(b+1) == ':' )
break;
}
a = b = (str + len);
for(j=len, p=7; j > i; j--, a--) {
if (*a != ':')
continue;
t = *b;
*b = '\0';
ip[p--] = str2ns(a+1);
*b = t;
b = a;
}
return (CHAR8 *)ip;
}
static BOOLEAN extract_tftp_info(CHAR8 *url)
{
CHAR8 *start, *end;
CHAR8 ip6str[40];
CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
Print(L"URLS MUST START WITH tftp://\n");
return FALSE;
}
start = url + 7;
if (*start != '[') {
Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
return FALSE;
}
start++;
end = start;
while ((*end != '\0') && (*end != ']')) {
end++;
if (end - start > 39) {
Print(L"TFTP URL includes malformed IPv6 address\n");
return FALSE;
}
}
if (end == '\0') {
Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
return FALSE;
}
memset(ip6str, 0, 40);
memcpy(ip6str, start, end - start);
end++;
memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
if (!full_path)
return FALSE;
memcpy(full_path, end, strlen(end));
end = (CHAR8 *)strrchr((char *)full_path, '/');
if (!end)
end = (CHAR8 *)full_path;
memcpy(end, template, strlen(template));
end[strlen(template)] = '\0';
return TRUE;
}
static EFI_STATUS parseDhcp6()
{
EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
CHAR8 *bootfile_url;
bootfile_url = get_v6_bootfile_url(packet);
if (!bootfile_url)
return EFI_NOT_FOUND;
if (extract_tftp_info(bootfile_url) == FALSE) {
FreePool(bootfile_url);
return EFI_NOT_FOUND;
}
FreePool(bootfile_url);
return EFI_SUCCESS;
}
static EFI_STATUS parseDhcp4()
{
CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
INTN template_len = strlen(template) + 1;
INTN dir_len = strnlena(pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile, 127);
INTN i;
UINT8 *dir = pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile;
for (i = dir_len; i >= 0; i--) {
if (dir[i] == '/')
break;
}
dir_len = (i >= 0) ? i + 1 : 0;
full_path = AllocateZeroPool(dir_len + template_len);
if (!full_path)
return EFI_OUT_OF_RESOURCES;
if (dir_len > 0) {
strncpya(full_path, dir, dir_len);
if (full_path[dir_len-1] == '/' && template[0] == '/')
full_path[dir_len-1] = '\0';
}
if (dir_len == 0 && dir[0] != '/' && template[0] == '/')
template++;
strcata(full_path, template);
memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
return EFI_SUCCESS;
}
EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
{
EFI_STATUS rc;
if (!pxe)
return EFI_NOT_READY;
memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
/*
* If we've discovered an active pxe protocol figure out
* if its ipv4 or ipv6
*/
if (pxe->Mode->UsingIpv6){
rc = parseDhcp6();
} else
rc = parseDhcp4();
return rc;
}
EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
{
EFI_STATUS rc;
EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
BOOLEAN overwrite = FALSE;
BOOLEAN nobuffer = FALSE;
UINTN blksz = 512;
Print(L"Fetching Netboot Image\n");
if (*buffer == NULL) {
*buffer = AllocatePool(4096 * 1024);
if (!*buffer)
return EFI_OUT_OF_RESOURCES;
*bufsiz = 4096 * 1024;
}
try_again:
rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite,
bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer);
if (rc == EFI_BUFFER_TOO_SMALL) {
/* try again, doubling buf size */
*bufsiz *= 2;
FreePool(*buffer);
*buffer = AllocatePool(*bufsiz);
if (!*buffer)
return EFI_OUT_OF_RESOURCES;
goto try_again;
}
if (rc != EFI_SUCCESS && *buffer) {
FreePool(*buffer);
}
return rc;
}