Skip to content
Browse files

Add draft version of Neil's netboot code

  • Loading branch information...
1 parent 1b3512a commit d8e330b95368ce43da47e114eb1d699eedb18e57 Matthew Garrett committed Oct 12, 2012
Showing with 400 additions and 8 deletions.
  1. +2 −2 Makefile
  2. +365 −0 netboot.c
  3. +9 −0 netboot.h
  4. +24 −6 shim.c
View
4 Makefile
@@ -29,8 +29,8 @@ LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared -Bsymbolic -L$(EFI_PATH
VERSION = 0.1
TARGET = shim.efi MokManager.efi
-OBJS = shim.o cert.o
-SOURCES = shim.c shim.h signature.h PeImage.h
+OBJS = shim.o netboot.o cert.o
+SOURCES = shim.c shim.h netboot.c signature.h PeImage.h
MOK_OBJS = MokManager.o
MOK_SOURCES = MokManager.c shim.h
View
365 netboot.c
@@ -0,0 +1,365 @@
+/*
+ * 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 char *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;
+ 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;
+ }
+
+ /*
+ * 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->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 char *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
+{
+ void *optr;
+ EFI_DHCP6_PACKET_OPTION *option;
+ char *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 = AllocatePool(urllen+2);
+ if (!url)
+ return NULL;
+ memset(url, 0, urllen+2);
+ 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(char *url)
+{
+ char *start, *end;
+ char ip6str[128];
+ char *template = "/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 == '\0') {
+ Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
+ return FALSE;
+ }
+ *end = '\0';
+ memset(ip6str, 0, 128);
+ memcpy(ip6str, start, strlen((UINT8 *)start));
+ *end = ']';
+ end++;
+ memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
+ full_path = AllocatePool(strlen((UINT8 *)end)+strlen((UINT8 *)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, '/');
+ if (!end)
+ end = full_path;
+ memcpy(end, template, strlen((UINT8 *)template));
+
+ return TRUE;
+}
+
+static EFI_STATUS parseDhcp6()
+{
+ EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
+ char *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;
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS parseDhcp4()
+{
+ char *template = "/grubx64.efi";
+ char *tmp = AllocatePool(16);
+
+
+ if (!tmp)
+ return EFI_OUT_OF_RESOURCES;
+
+
+ memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
+
+ memcpy(tmp, template, 12);
+ tmp[13] = '\0';
+ full_path = tmp;
+
+ /* 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, UINTN *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;
+
+}
View
9 netboot.h
@@ -0,0 +1,9 @@
+#ifndef _NETBOOT_H_
+#define _NETBOOT_H_
+
+extern BOOLEAN findNetboot(EFI_HANDLE image_handle);
+
+extern EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle);
+
+extern EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINTN *bufsiz);
+#endif
View
30 shim.c
@@ -39,6 +39,7 @@
#include "PeImage.h"
#include "shim.h"
#include "signature.h"
+#include "netboot.h"
#define SECOND_STAGE L"\\grub.efi"
#define MOK_MANAGER L"\\MokManager.efi"
@@ -680,7 +681,6 @@ static EFI_STATUS read_header(void *data, unsigned int datasize,
Print(L"Empty security header\n");
return EFI_INVALID_PARAMETER;
}
-
return EFI_SUCCESS;
}
@@ -942,7 +942,9 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath)
EFI_STATUS efi_status;
EFI_LOADED_IMAGE *li, li_bak;
EFI_DEVICE_PATH *path;
- CHAR16 *PathName;
+ CHAR16 *PathName = NULL;
+ void *sourcebuffer = NULL;
+ UINTN sourcesize = 0;
void *data = NULL;
int datasize;
@@ -961,11 +963,27 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath)
goto done;
}
- efi_status = load_image(li, &data, &datasize, PathName);
+ if (findNetboot(image_handle)) {
+ efi_status = parseNetbootinfo(image_handle);
+ if (efi_status != EFI_SUCCESS) {
+ Print(L"Netboot parsing failed: %d\n", efi_status);
+ return EFI_PROTOCOL_ERROR;
+ }
+ efi_status = FetchNetbootimage(image_handle, &sourcebuffer,
+ &sourcesize);
+ if (efi_status != EFI_SUCCESS) {
+ Print(L"Unable to fetch TFTP image\n");
+ return efi_status;
+ }
+ data = sourcebuffer;
+ datasize = sourcesize;
+ } else {
+ efi_status = load_image(li, &data, &datasize, PathName);
- if (efi_status != EFI_SUCCESS) {
- Print(L"Failed to load image\n");
- goto done;
+ if (efi_status != EFI_SUCCESS) {
+ Print(L"Failed to load image\n");
+ goto done;
+ }
}
CopyMem(&li_bak, li, sizeof(li_bak));

0 comments on commit d8e330b

Please sign in to comment.
Something went wrong with that request. Please try again.