mirror of
https://git.proxmox.com/git/efi-boot-shim
synced 2025-05-02 17:38:43 +00:00

The maximum length of a string representation of an ipv6 address is 39 characters (8 groups of 4 hex chars, with 7 colons in between). So don't allocate more room than this - and more importantly, don't blindly accept strings from the server that are longer than our buffer...
361 lines
8.6 KiB
C
361 lines
8.6 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"
|
|
|
|
|
|
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 UINT8 *full_path;
|
|
|
|
|
|
typedef struct {
|
|
UINT16 OpCode;
|
|
UINT16 Length;
|
|
UINT8 Data[1];
|
|
} EFI_DHCP6_PACKET_OPTION;
|
|
|
|
/*
|
|
* 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 image_handle)
|
|
{
|
|
UINTN bs = sizeof(EFI_HANDLE);
|
|
EFI_GUID pxe_base_code_protocol = EFI_PXE_BASE_CODE_PROTOCOL;
|
|
EFI_HANDLE *hbuf;
|
|
BOOLEAN rc = FALSE;
|
|
void *buffer = AllocatePool(bs);
|
|
UINTN errcnt = 0;
|
|
UINTN i;
|
|
EFI_STATUS status;
|
|
|
|
if (!buffer)
|
|
return FALSE;
|
|
|
|
try_again:
|
|
status = uefi_call_wrapper(BS->LocateHandle,5, ByProtocol,
|
|
&pxe_base_code_protocol, NULL, &bs,
|
|
buffer);
|
|
|
|
if (status == EFI_BUFFER_TOO_SMALL) {
|
|
errcnt++;
|
|
FreePool(buffer);
|
|
if (errcnt > 1)
|
|
return FALSE;
|
|
buffer = AllocatePool(bs);
|
|
if (!buffer)
|
|
return FALSE;
|
|
goto try_again;
|
|
}
|
|
|
|
if (status == EFI_NOT_FOUND) {
|
|
FreePool(buffer);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* We have a list of pxe supporting protocols, lets see if any are
|
|
* active
|
|
*/
|
|
hbuf = buffer;
|
|
pxe = NULL;
|
|
for (i=0; i < (bs / sizeof(EFI_HANDLE)); i++) {
|
|
status = uefi_call_wrapper(BS->OpenProtocol, 6, hbuf[i],
|
|
&pxe_base_code_protocol,
|
|
(void **)&pxe, image_handle, NULL,
|
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
|
|
|
if (status != EFI_SUCCESS) {
|
|
pxe = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (!pxe || !pxe->Mode) {
|
|
pxe = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (pxe->Mode->Started && pxe->Mode->DhcpAckReceived) {
|
|
/*
|
|
* 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
|
|
*/
|
|
rc = TRUE;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
FreePool(buffer);
|
|
return rc;
|
|
}
|
|
|
|
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 UINT16 str2ns(UINT8 *str)
|
|
{
|
|
UINT16 ret = 0;
|
|
UINT8 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 UINT8 *str2ip6(char *str)
|
|
{
|
|
UINT8 i, j, p;
|
|
size_t len;
|
|
UINT8 *a, *b, t;
|
|
static UINT16 ip[8];
|
|
|
|
for(i=0; i < 8; i++) {
|
|
ip[i] = 0;
|
|
}
|
|
len = strlen((UINT8 *)str);
|
|
a = b = (UINT8 *)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 = (UINT8 *)(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 (UINT8 *)ip;
|
|
}
|
|
|
|
static BOOLEAN extract_tftp_info(CHAR8 *url)
|
|
{
|
|
CHAR8 *start, *end;
|
|
char ip6str[40];
|
|
CHAR8 *template = (CHAR8 *)"/grubx64.efi";
|
|
|
|
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 *)"/grubx64.efi";
|
|
full_path = AllocateZeroPool(strlen(template)+1);
|
|
|
|
if (!full_path)
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
|
|
|
|
memcpy(full_path, template, strlen(template));
|
|
|
|
/* Note we don't capture the filename option here because we know its shim.efi
|
|
* We instead assume the filename at the end of the path is going to be grubx64.efi
|
|
*/
|
|
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;
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|