]> git.proxmox.com Git - efi-boot-shim.git/blobdiff - netboot.c
Switch to new upstream (15.7)
[efi-boot-shim.git] / netboot.c
index cc43b7293eedc91064da0d6b12480097eef9832d..cf5882c1e798aef22614204e5b02b40e9f3176dd 100644 (file)
--- a/netboot.c
+++ b/netboot.c
@@ -1,70 +1,26 @@
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+
 /*
  * 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.
+ * 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 <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 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 char *full_path;
+static CHAR8 *full_path;
 
 
-/*
- * Not in the EFI header set yet, so I have to declare it here
- */
-typedef struct {
-       UINT32 MessageType:8;
-       UINT32 TransactionId:24;
-       UINT8 DhcpOptions[1024];
-} EFI_PXE_BASE_CODE_DHCPV6_PACKET;
-
 typedef struct {
        UINT16 OpCode;
        UINT16 Length;
@@ -76,110 +32,74 @@ typedef struct {
  * 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)
+BOOLEAN findNetboot(EFI_HANDLE device)
 {
-       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;
+       EFI_STATUS efi_status;
 
-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;
+       efi_status = BS->HandleProtocol(device, &PxeBaseCodeProtocol,
+                                       (VOID **) &pxe);
+       if (EFI_ERROR(efi_status)) {
+               pxe = NULL;
+               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,
-                                          &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 || !pxe->Mode) {
+               pxe = NULL;
+               return FALSE;
+       }
 
-               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;
-               }
-                       
+       if (!pxe->Mode->Started || !pxe->Mode->DhcpAckReceived) {
+               pxe = NULL;
+               return FALSE;
        }
 
-       FreePool(buffer);
-       return rc;
+       /*
+        * 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 char *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
+static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
 {
-       void *optr;
-       EFI_DHCP6_PACKET_OPTION *option;
-       char *url;
-       UINT32 urllen;
+       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(;;) {
+       for (;;) {
                option = (EFI_DHCP6_PACKET_OPTION *)optr;
 
                if (ntohs(option->OpCode) == 0)
-                       return NULL;
+                       break;
 
                if (ntohs(option->OpCode) == 59) {
                        /* This is the bootfile url option */
                        urllen = ntohs(option->Length);
-                       url = AllocatePool(urllen+2);
+                       if ((void *)(option->Data + urllen) > end)
+                               break;
+                       url = AllocateZeroPool(urllen + 1);
                        if (!url)
-                               return NULL;
-                       memset(url, 0, urllen+2);
+                               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 UINT16 str2ns(UINT8 *str)
+static CHAR16 str2ns(CHAR8 *str)
 {
-        UINT16 ret = 0;
-        UINT8 v;
+        CHAR16 ret = 0;
+        CHAR8 v;
         for(;*str;str++) {
                 if ('0' <= *str && *str <= '9')
                         v = *str - '0';
@@ -194,54 +114,71 @@ static UINT16 str2ns(UINT8 *str)
         return htons(ret);
 }
 
-static UINT8 *str2ip6(char *str)
+static CHAR8 *str2ip6(CHAR8 *str)
 {
-        UINT8 i, j, p;
-       size_t len;
-        UINT8 *a, *b, t;
-        static UINT16 ip[8];
+       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];
 
-        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;
+       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(char *url)
+static BOOLEAN extract_tftp_info(CHAR8 *url)
 {
-       char *start, *end;
-       char ip6str[128];
-       char *template = "/grubx64.efi";
+       CHAR8 *start, *end;
+       CHAR8 ip6str[40];
+       CHAR8 ip6inv[16];
+       CHAR8 template[sizeof DEFAULT_LOADER_CHAR];
+
+       translate_slashes(template, DEFAULT_LOADER_CHAR);
 
-       if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
-               Print(L"URLS MUST START WITH tftp://\n");
+       // to check against str2ip6() errors
+       memset(ip6inv, 0, sizeof(ip6inv));
+
+       if (strncmp((const char *)url, (const char *)"tftp://", 7)) {
+               console_print(L"URLS MUST START WITH tftp://\n");
                return FALSE;
        }
        start = url + 7;
        if (*start != '[') {
-               Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
+               console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
                return FALSE;
        }
 
@@ -249,26 +186,30 @@ static BOOLEAN extract_tftp_info(char *url)
        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') {
-               Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
+       if (*end == '\0') {
+               console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
                return FALSE;
        }
-       *end = '\0';
-       memset(ip6str, 0, 128);
-       memcpy(ip6str, start, strlen((UINT8 *)start));
-       *end = ']';
+       memset(ip6str, 0, sizeof(ip6str));
+       memcpy(ip6str, start, end - start);
        end++;
        memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
-       full_path = AllocatePool(strlen((UINT8 *)end)+strlen((UINT8 *)template)+1);
+       if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0)
+               return FALSE;
+       full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
        if (!full_path)
                return FALSE;
-       memset(full_path, 0, strlen((UINT8 *)end)+strlen((UINT8 *)template));
-       memcpy(full_path, end, strlen((UINT8 *)end));
-       end = strrchr(full_path, '/');
+       memcpy(full_path, end, strlen(end));
+       end = (CHAR8 *)strrchr((char *)full_path, '/');
        if (!end)
-               end = full_path;
-       memcpy(end, template, strlen((UINT8 *)template));
+               end = (CHAR8 *)full_path;
+       memcpy(end, template, strlen(template));
+       end[strlen(template)] = '\0';
 
        return TRUE;
 }
@@ -276,43 +217,79 @@ static BOOLEAN extract_tftp_info(char *url)
 static EFI_STATUS parseDhcp6()
 {
        EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
-       char *bootfile_url;
-
+       CHAR8 *bootfile_url;
 
        bootfile_url = get_v6_bootfile_url(packet);
-       if (extract_tftp_info(bootfile_url) == FALSE)
-               return EFI_NOT_FOUND;
        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()
 {
-       char *template = "/grubx64.efi";
-       char *tmp = AllocatePool(16);
+       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;
+       }
 
-       if (!tmp)
-               return EFI_OUT_OF_RESOURCES;
+       INTN dir_len = strnlen((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;
 
-       memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
+       full_path = AllocateZeroPool(dir_len + template_len);
 
-       memcpy(tmp, template, 12);
-       tmp[13] = '\0';
-       full_path = tmp;
+       if (!full_path)
+               return EFI_OUT_OF_RESOURCES;
+
+       if (dir_len > 0) {
+               strncpy(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++;
+       strcat(full_path, template + template_ofs);
+       memcpy(&tftp_addr.v4, pkt_v4->BootpSiAddr, 4);
 
-       /* 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 parseNetbootinfo(EFI_HANDLE image_handle UNUSED)
 {
 
-       EFI_STATUS rc;
+       EFI_STATUS efi_status;
 
        if (!pxe)
                return EFI_NOT_READY;
@@ -324,33 +301,32 @@ EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
         * if its ipv4 or ipv6
         */
        if (pxe->Mode->UsingIpv6){
-               rc = parseDhcp6();
+               efi_status = parseDhcp6();
        } else
-               rc = parseDhcp4();
-       return rc;
+               efi_status = parseDhcp4();
+       return efi_status;
 }
 
-EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINTN *bufsiz)
+EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle UNUSED, VOID **buffer, UINT64 *bufsiz)
 {
-       EFI_STATUS rc;
+       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;
 
-       Print(L"Fetching Netboot Image\n");
+       console_print(L"Fetching Netboot Image\n");
        if (*buffer == NULL) {
                *buffer = AllocatePool(4096 * 1024);
                if (!*buffer)
-                       return EFI_OUT_OF_RESOURCES; 
+                       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) {
+       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);
@@ -360,6 +336,8 @@ try_again:
                goto try_again;
        }
 
-       return rc;
-
+       if (EFI_ERROR(efi_status) && *buffer) {
+               FreePool(*buffer);
+       }
+       return efi_status;
 }