diff options
Diffstat (limited to 'netboot.c')
-rw-r--r-- | netboot.c | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/netboot.c b/netboot.c new file mode 100644 index 00000000..1cc1a2b8 --- /dev/null +++ b/netboot.c @@ -0,0 +1,346 @@ +/* + * 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" + +#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 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 = 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 = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR); + + // to check against str2ip6() errors + memset(ip6inv, 0, sizeof(ip6inv)); + + 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 >= (int)sizeof(ip6str)) { + 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, 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 = (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; +} |