mirror of
https://git.proxmox.com/git/efi-boot-shim
synced 2025-06-13 18:40:21 +00:00

The license statements in our source files were getting to be a giant mess, and mostly they all just say the same thing. I've switched most of it to SPDX labels, but left copyright statements in place (where they were not obviously incorrect copy-paste jobs that I did...). If there's some change here you don't think is valid, let me know and we can fix it up together. Signed-off-by: Peter Jones <pjones@redhat.com>
346 lines
7.9 KiB
C
Executable File
346 lines
7.9 KiB
C
Executable File
// SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
/*
|
|
* netboot - trivial UEFI first-stage bootloader netboot support
|
|
*
|
|
* Copyright Red Hat, Inc
|
|
* Author: Matthew Garrett
|
|
*
|
|
* Significant portions of this code are derived from Tianocore
|
|
* (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
|
|
* Corporation.
|
|
*/
|
|
|
|
#include "shim.h"
|
|
|
|
#include <string.h>
|
|
|
|
#define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */
|
|
#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;
|
|
|
|
/*
|
|
* 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 efi_status;
|
|
|
|
efi_status = gBS->HandleProtocol(device, &PxeBaseCodeProtocol,
|
|
(VOID **) &pxe);
|
|
if (EFI_ERROR(efi_status)) {
|
|
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 = NULL, *end = NULL;
|
|
EFI_DHCP6_PACKET_OPTION *option = NULL;
|
|
CHAR8 *url = NULL;
|
|
UINT32 urllen = 0;
|
|
|
|
optr = pkt->DhcpOptions;
|
|
end = optr + sizeof(pkt->DhcpOptions);
|
|
|
|
for (;;) {
|
|
option = (EFI_DHCP6_PACKET_OPTION *)optr;
|
|
|
|
if (ntohs(option->OpCode) == 0)
|
|
break;
|
|
|
|
if (ntohs(option->OpCode) == 59) {
|
|
/* This is the bootfile url option */
|
|
urllen = ntohs(option->Length);
|
|
if ((void *)(option->Data + urllen) > end)
|
|
break;
|
|
url = AllocateZeroPool(urllen + 1);
|
|
if (!url)
|
|
break;
|
|
memcpy(url, option->Data, urllen);
|
|
return url;
|
|
}
|
|
optr += 4 + ntohs(option->Length);
|
|
if (optr + sizeof(EFI_DHCP6_PACKET_OPTION) > end)
|
|
break;
|
|
}
|
|
|
|
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 = 0, j = 0, p = 0;
|
|
size_t len = 0, dotcount = 0;
|
|
enum { MAX_IP6_DOTS = 7 };
|
|
CHAR8 *a = NULL, *b = NULL, t = 0;
|
|
static UINT16 ip[8];
|
|
|
|
memset(ip, 0, sizeof(ip));
|
|
|
|
/* Count amount of ':' to prevent overflows.
|
|
* max. count = 7. Returns an invalid ip6 that
|
|
* can be checked against
|
|
*/
|
|
for (a = str; *a != 0; ++a) {
|
|
if (*a == ':')
|
|
++dotcount;
|
|
}
|
|
if (dotcount > MAX_IP6_DOTS)
|
|
return (CHAR8 *)ip;
|
|
|
|
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 ip6inv[16];
|
|
CHAR8 template[sizeof DEFAULT_LOADER_CHAR];
|
|
|
|
translate_slashes(template, DEFAULT_LOADER_CHAR);
|
|
|
|
// to check against str2ip6() errors
|
|
memset(ip6inv, 0, sizeof(ip6inv));
|
|
|
|
if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
|
|
console_print(L"URLS MUST START WITH tftp://\n");
|
|
return FALSE;
|
|
}
|
|
start = url + 7;
|
|
if (*start != '[') {
|
|
console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
|
|
return FALSE;
|
|
}
|
|
|
|
start++;
|
|
end = start;
|
|
while ((*end != '\0') && (*end != ']')) {
|
|
end++;
|
|
if (end - start >= (int)sizeof(ip6str)) {
|
|
console_print(L"TFTP URL includes malformed IPv6 address\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (*end == '\0') {
|
|
console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
|
|
return FALSE;
|
|
}
|
|
memset(ip6str, 0, sizeof(ip6str));
|
|
memcpy(ip6str, start, end - start);
|
|
end++;
|
|
memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
|
|
if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0)
|
|
return FALSE;
|
|
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[sizeof DEFAULT_LOADER_CHAR];
|
|
INTN template_len;
|
|
UINTN template_ofs = 0;
|
|
EFI_PXE_BASE_CODE_DHCPV4_PACKET* pkt_v4 = (EFI_PXE_BASE_CODE_DHCPV4_PACKET *)&pxe->Mode->DhcpAck.Dhcpv4;
|
|
|
|
translate_slashes(template, DEFAULT_LOADER_CHAR);
|
|
template_len = strlen(template) + 1;
|
|
|
|
if(pxe->Mode->ProxyOfferReceived) {
|
|
/*
|
|
* Proxy should not have precedence. Check if DhcpAck
|
|
* contained boot info.
|
|
*/
|
|
if(pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile[0] == '\0')
|
|
pkt_v4 = &pxe->Mode->ProxyOffer.Dhcpv4;
|
|
}
|
|
|
|
if(pxe->Mode->PxeReplyReceived) {
|
|
/*
|
|
* If we have no bootinfo yet search for it in the PxeReply.
|
|
* Some mainboards run into this when the server uses boot menus
|
|
*/
|
|
if(pkt_v4->BootpBootFile[0] == '\0' && pxe->Mode->PxeReply.Dhcpv4.BootpBootFile[0] != '\0')
|
|
pkt_v4 = &pxe->Mode->PxeReply.Dhcpv4;
|
|
}
|
|
|
|
INTN dir_len = strnlena((CHAR8 *)pkt_v4->BootpBootFile, 127);
|
|
INTN i;
|
|
UINT8 *dir = pkt_v4->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, (CHAR8 *)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_ofs++;
|
|
strcata(full_path, template + template_ofs);
|
|
memcpy(&tftp_addr.v4, pkt_v4->BootpSiAddr, 4);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
|
|
{
|
|
|
|
EFI_STATUS efi_status;
|
|
|
|
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){
|
|
efi_status = parseDhcp6();
|
|
} else
|
|
efi_status = parseDhcp4();
|
|
return efi_status;
|
|
}
|
|
|
|
EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
|
|
BOOLEAN overwrite = FALSE;
|
|
BOOLEAN nobuffer = FALSE;
|
|
UINTN blksz = 512;
|
|
|
|
console_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:
|
|
efi_status = pxe->Mtftp(pxe, read, *buffer, overwrite, bufsiz, &blksz,
|
|
&tftp_addr, (UINT8 *)full_path, NULL, nobuffer);
|
|
if (efi_status == 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 (EFI_ERROR(efi_status) && *buffer) {
|
|
FreePool(*buffer);
|
|
}
|
|
return efi_status;
|
|
}
|