netboot.c 8.63 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/*
 * 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 "shim.h"
37

38
#include <string.h>
39

40
#define ntohs(x) __builtin_bswap16(x)	/* supported both by GCC and clang */
41 42 43 44
#define htons(x) ntohs(x)

static EFI_PXE_BASE_CODE *pxe;
static EFI_IP_ADDRESS tftp_addr;
45
static CHAR8 *full_path;
46 47 48 49 50 51 52 53 54 55 56 57 58


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
 */
59
BOOLEAN findNetboot(EFI_HANDLE device)
60
{
61
	EFI_STATUS efi_status;
62

63 64 65
	efi_status = gBS->HandleProtocol(device, &PxeBaseCodeProtocol,
					 (VOID **) &pxe);
	if (EFI_ERROR(efi_status)) {
66
		pxe = NULL;
67 68 69
		return FALSE;
	}

70 71
	if (!pxe || !pxe->Mode) {
		pxe = NULL;
72 73 74
		return FALSE;
	}

75 76 77
	if (!pxe->Mode->Started || !pxe->Mode->DhcpAckReceived) {
		pxe = NULL;
		return FALSE;
78 79
	}

80 81 82 83 84 85
	/*
	 * 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;
86 87
}

88
static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
89
{
90 91 92 93
	void *optr = NULL, *end = NULL;
	EFI_DHCP6_PACKET_OPTION *option = NULL;
	CHAR8 *url = NULL;
	UINT32 urllen = 0;
94 95

	optr = pkt->DhcpOptions;
96
	end = optr + sizeof(pkt->DhcpOptions);
97

98
	for (;;) {
99 100 101
		option = (EFI_DHCP6_PACKET_OPTION *)optr;

		if (ntohs(option->OpCode) == 0)
102
			break;
103 104 105 106

		if (ntohs(option->OpCode) == 59) {
			/* This is the bootfile url option */
			urllen = ntohs(option->Length);
107 108 109
			if ((void *)(option->Data + urllen) > end)
				break;
			url = AllocateZeroPool(urllen + 1);
110
			if (!url)
111
				break;
112 113 114 115
			memcpy(url, option->Data, urllen);
			return url;
		}
		optr += 4 + ntohs(option->Length);
116 117
		if (optr + sizeof(EFI_DHCP6_PACKET_OPTION) > end)
			break;
118 119 120 121 122
	}

	return NULL;
}

123
static CHAR16 str2ns(CHAR8 *str)
124
{
125 126
        CHAR16 ret = 0;
        CHAR8 v;
127 128 129 130 131 132 133 134 135 136 137 138 139 140
        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);
}

141
static CHAR8 *str2ip6(CHAR8 *str)
142
{
143 144 145 146 147
	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];
148

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
	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;
185 186
}

187
static BOOLEAN extract_tftp_info(CHAR8 *url)
188
{
189
	CHAR8 *start, *end;
190
	CHAR8 ip6str[40];
191
	CHAR8 ip6inv[16];
192
	CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
193

194 195 196
	// to check against str2ip6() errors
	memset(ip6inv, 0, sizeof(ip6inv));

197
	if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
198
		console_print(L"URLS MUST START WITH tftp://\n");
199 200
		return FALSE;
	}
201
	start = url + 7;
202
	if (*start != '[') {
203
		console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
204 205 206 207 208 209 210
		return FALSE;
	}

	start++;
	end = start;
	while ((*end != '\0') && (*end != ']')) {
		end++;
211
		if (end - start >= (int)sizeof(ip6str)) {
212
			console_print(L"TFTP URL includes malformed IPv6 address\n");
213 214
			return FALSE;
		}
215
	}
216
	if (*end == '\0') {
217
		console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
218 219
		return FALSE;
	}
220
	memset(ip6str, 0, sizeof(ip6str));
Steve Langasek's avatar
Steve Langasek committed
221
	memcpy(ip6str, start, end - start);
222 223
	end++;
	memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
224 225
	if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0)
		return FALSE;
226
	full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
227 228
	if (!full_path)
		return FALSE;
229 230
	memcpy(full_path, end, strlen(end));
	end = (CHAR8 *)strrchr((char *)full_path, '/');
231
	if (!end)
232 233 234
		end = (CHAR8 *)full_path;
	memcpy(end, template, strlen(template));
	end[strlen(template)] = '\0';
235 236 237 238 239 240 241

	return TRUE;
}

static EFI_STATUS parseDhcp6()
{
	EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
242
	CHAR8 *bootfile_url;
243 244 245 246

	bootfile_url = get_v6_bootfile_url(packet);
	if (!bootfile_url)
		return EFI_NOT_FOUND;
247 248 249 250 251
	if (extract_tftp_info(bootfile_url) == FALSE) {
		FreePool(bootfile_url);
		return EFI_NOT_FOUND;
	}
	FreePool(bootfile_url);
252 253 254 255 256
	return EFI_SUCCESS;
}

static EFI_STATUS parseDhcp4()
{
257
	CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
258
	INTN template_len = strlen(template) + 1;
259 260 261 262 263 264 265 266 267 268
	EFI_PXE_BASE_CODE_DHCPV4_PACKET* pkt_v4 = (EFI_PXE_BASE_CODE_DHCPV4_PACKET *)&pxe->Mode->DhcpAck.Dhcpv4;

	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;
	}
269

270
	INTN dir_len = strnlena(pkt_v4->BootpBootFile, 127);
271
	INTN i;
272
	UINT8 *dir = pkt_v4->BootpBootFile;
273 274 275 276 277 278 279 280

	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);
281

282
	if (!full_path)
283 284
		return EFI_OUT_OF_RESOURCES;

285 286 287 288 289
	if (dir_len > 0) {
		strncpya(full_path, dir, dir_len);
		if (full_path[dir_len-1] == '/' && template[0] == '/')
			full_path[dir_len-1] = '\0';
	}
290 291
	if (dir_len == 0 && dir[0] != '/' && template[0] == '/')
		template++;
292
	strcata(full_path, template);
293
	memcpy(&tftp_addr.v4, pkt_v4->BootpSiAddr, 4);
294 295 296 297 298 299 300

	return EFI_SUCCESS;
}

EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
{

301
	EFI_STATUS efi_status;
302 303 304 305 306 307 308 309 310 311 312

	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){
313
		efi_status = parseDhcp6();
314
	} else
315 316
		efi_status = parseDhcp4();
	return efi_status;
317 318
}

319
EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
320
{
321
	EFI_STATUS efi_status;
322 323 324 325 326
	EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
	BOOLEAN overwrite = FALSE;
	BOOLEAN nobuffer = FALSE;
	UINTN blksz = 512;

327
	console_print(L"Fetching Netboot Image\n");
328 329 330
	if (*buffer == NULL) {
		*buffer = AllocatePool(4096 * 1024);
		if (!*buffer)
331
			return EFI_OUT_OF_RESOURCES;
332 333 334 335
		*bufsiz = 4096 * 1024;
	}

try_again:
336 337 338
	efi_status = pxe->Mtftp(pxe, read, *buffer, overwrite, bufsiz, &blksz,
			      &tftp_addr, full_path, NULL, nobuffer);
	if (efi_status == EFI_BUFFER_TOO_SMALL) {
339 340 341 342 343 344 345 346 347
		/* try again, doubling buf size */
		*bufsiz *= 2;
		FreePool(*buffer);
		*buffer = AllocatePool(*bufsiz);
		if (!*buffer)
			return EFI_OUT_OF_RESOURCES;
		goto try_again;
	}

348
	if (EFI_ERROR(efi_status) && *buffer) {
Steve Langasek's avatar
Steve Langasek committed
349 350
		FreePool(*buffer);
	}
351
	return efi_status;
352
}