]> git.proxmox.com Git - efi-boot-shim.git/blame - netboot.c
debian/rules, debian/shim.install: make sure the 'make install' step does what it...
[efi-boot-shim.git] / netboot.c
CommitLineData
1c595706
MG
1/*
2 * netboot - trivial UEFI first-stage bootloader netboot support
3 *
4 * Copyright 2012 Red Hat, Inc <mjg@redhat.com>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the
16 * distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 * Significant portions of this code are derived from Tianocore
32 * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
33 * Corporation.
34 */
35
36#include <efi.h>
37#include <efilib.h>
38#include <string.h>
39#include "shim.h"
40#include "netboot.h"
d9355ab6 41#include "str.h"
1c595706 42
f7a18215 43#define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */
1c595706
MG
44#define htons(x) ntohs(x)
45
46static EFI_PXE_BASE_CODE *pxe;
47static EFI_IP_ADDRESS tftp_addr;
cb89c25a 48static CHAR8 *full_path;
1c595706
MG
49
50
1c595706
MG
51typedef struct {
52 UINT16 OpCode;
53 UINT16 Length;
54 UINT8 Data[1];
55} EFI_DHCP6_PACKET_OPTION;
56
57/*
58 * usingNetboot
59 * Returns TRUE if we identify a protocol that is enabled and Providing us with
60 * the needed information to fetch a grubx64.efi image
61 */
da49ac6d 62BOOLEAN findNetboot(EFI_HANDLE device)
1c595706 63{
1c595706
MG
64 EFI_STATUS status;
65
da49ac6d
GCPL
66 status = uefi_call_wrapper(BS->HandleProtocol, 3, device,
67 &PxeBaseCodeProtocol, (VOID **)&pxe);
68 if (status != EFI_SUCCESS) {
69 pxe = NULL;
1c595706 70 return FALSE;
1c595706
MG
71 }
72
da49ac6d
GCPL
73 if (!pxe || !pxe->Mode) {
74 pxe = NULL;
e4d55afe
MG
75 return FALSE;
76 }
77
da49ac6d
GCPL
78 if (!pxe->Mode->Started || !pxe->Mode->DhcpAckReceived) {
79 pxe = NULL;
80 return FALSE;
1c595706
MG
81 }
82
da49ac6d
GCPL
83 /*
84 * We've located a pxe protocol handle thats been started and has
85 * received an ACK, meaning its something we'll be able to get
86 * tftp server info out of
87 */
88 return TRUE;
1c595706
MG
89}
90
af049ff4 91static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
1c595706 92{
f6bff34f
SK
93 void *optr = NULL, *end = NULL;
94 EFI_DHCP6_PACKET_OPTION *option = NULL;
95 CHAR8 *url = NULL;
96 UINT32 urllen = 0;
1c595706
MG
97
98 optr = pkt->DhcpOptions;
f6bff34f 99 end = optr + sizeof(pkt->DhcpOptions);
1c595706 100
f6bff34f 101 for (;;) {
1c595706
MG
102 option = (EFI_DHCP6_PACKET_OPTION *)optr;
103
104 if (ntohs(option->OpCode) == 0)
f6bff34f 105 break;
1c595706
MG
106
107 if (ntohs(option->OpCode) == 59) {
108 /* This is the bootfile url option */
109 urllen = ntohs(option->Length);
f6bff34f
SK
110 if ((void *)(option->Data + urllen) > end)
111 break;
112 url = AllocateZeroPool(urllen + 1);
1c595706 113 if (!url)
f6bff34f 114 break;
1c595706
MG
115 memcpy(url, option->Data, urllen);
116 return url;
117 }
118 optr += 4 + ntohs(option->Length);
f6bff34f
SK
119 if (optr + sizeof(EFI_DHCP6_PACKET_OPTION) > end)
120 break;
1c595706
MG
121 }
122
123 return NULL;
124}
125
cb89c25a 126static CHAR16 str2ns(CHAR8 *str)
1c595706 127{
cb89c25a
PJ
128 CHAR16 ret = 0;
129 CHAR8 v;
1c595706
MG
130 for(;*str;str++) {
131 if ('0' <= *str && *str <= '9')
132 v = *str - '0';
133 else if ('A' <= *str && *str <= 'F')
134 v = *str - 'A' + 10;
135 else if ('a' <= *str && *str <= 'f')
136 v = *str - 'a' + 10;
137 else
138 v = 0;
139 ret = (ret << 4) + v;
140 }
141 return htons(ret);
142}
143
cb89c25a 144static CHAR8 *str2ip6(CHAR8 *str)
1c595706 145{
f6bff34f
SK
146 UINT8 i = 0, j = 0, p = 0;
147 size_t len = 0, dotcount = 0;
148 enum { MAX_IP6_DOTS = 7 };
149 CHAR8 *a = NULL, *b = NULL, t = 0;
150 static UINT16 ip[8];
1c595706 151
f6bff34f
SK
152 memset(ip, 0, sizeof(ip));
153
154 /* Count amount of ':' to prevent overflows.
155 * max. count = 7. Returns an invalid ip6 that
156 * can be checked against
157 */
158 for (a = str; *a != 0; ++a) {
159 if (*a == ':')
160 ++dotcount;
161 }
162 if (dotcount > MAX_IP6_DOTS)
163 return (CHAR8 *)ip;
164
165 len = strlen(str);
166 a = b = str;
167 for (i = p = 0; i < len; i++, b++) {
168 if (*b != ':')
169 continue;
170 *b = '\0';
171 ip[p++] = str2ns(a);
172 *b = ':';
173 a = b + 1;
174 if (b[1] == ':' )
175 break;
176 }
177 a = b = (str + len);
178 for (j = len, p = 7; j > i; j--, a--) {
179 if (*a != ':')
180 continue;
181 t = *b;
182 *b = '\0';
183 ip[p--] = str2ns(a+1);
184 *b = t;
185 b = a;
186 }
187 return (CHAR8 *)ip;
1c595706
MG
188}
189
af049ff4 190static BOOLEAN extract_tftp_info(CHAR8 *url)
1c595706 191{
e2979f2c 192 CHAR8 *start, *end;
cb89c25a 193 CHAR8 ip6str[40];
f6bff34f 194 CHAR8 ip6inv[16];
e053c227 195 CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
1c595706 196
f6bff34f
SK
197 // to check against str2ip6() errors
198 memset(ip6inv, 0, sizeof(ip6inv));
199
1c595706
MG
200 if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
201 Print(L"URLS MUST START WITH tftp://\n");
202 return FALSE;
203 }
af049ff4 204 start = url + 7;
1c595706
MG
205 if (*start != '[') {
206 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
207 return FALSE;
208 }
209
210 start++;
211 end = start;
212 while ((*end != '\0') && (*end != ']')) {
213 end++;
f6bff34f 214 if (end - start >= (int)sizeof(ip6str)) {
69a54db4
SL
215 Print(L"TFTP URL includes malformed IPv6 address\n");
216 return FALSE;
217 }
1c595706 218 }
159609ee 219 if (*end == '\0') {
1c595706
MG
220 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
221 return FALSE;
222 }
f6bff34f 223 memset(ip6str, 0, sizeof(ip6str));
3816832b 224 memcpy(ip6str, start, end - start);
1c595706
MG
225 end++;
226 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
f6bff34f
SK
227 if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0)
228 return FALSE;
e2979f2c 229 full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
1c595706
MG
230 if (!full_path)
231 return FALSE;
e2979f2c
SL
232 memcpy(full_path, end, strlen(end));
233 end = (CHAR8 *)strrchr((char *)full_path, '/');
1c595706 234 if (!end)
e2979f2c
SL
235 end = (CHAR8 *)full_path;
236 memcpy(end, template, strlen(template));
237 end[strlen(template)] = '\0';
1c595706
MG
238
239 return TRUE;
240}
241
242static EFI_STATUS parseDhcp6()
243{
244 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
af049ff4 245 CHAR8 *bootfile_url;
1c595706
MG
246
247 bootfile_url = get_v6_bootfile_url(packet);
1c595706
MG
248 if (!bootfile_url)
249 return EFI_NOT_FOUND;
6eaa1a9c
SL
250 if (extract_tftp_info(bootfile_url) == FALSE) {
251 FreePool(bootfile_url);
252 return EFI_NOT_FOUND;
253 }
254 FreePool(bootfile_url);
1c595706
MG
255 return EFI_SUCCESS;
256}
257
258static EFI_STATUS parseDhcp4()
259{
d9355ab6 260 CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
e724cfb1 261 INTN template_len = strlen(template) + 1;
d9355ab6 262
e724cfb1
PJ
263 INTN dir_len = strnlena(pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile, 127);
264 INTN i;
d9355ab6
PJ
265 UINT8 *dir = pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile;
266
267 for (i = dir_len; i >= 0; i--) {
268 if (dir[i] == '/')
269 break;
270 }
271 dir_len = (i >= 0) ? i + 1 : 0;
272
273 full_path = AllocateZeroPool(dir_len + template_len);
1c595706 274
e2979f2c 275 if (!full_path)
1c595706
MG
276 return EFI_OUT_OF_RESOURCES;
277
d9355ab6
PJ
278 if (dir_len > 0) {
279 strncpya(full_path, dir, dir_len);
280 if (full_path[dir_len-1] == '/' && template[0] == '/')
281 full_path[dir_len-1] = '\0';
282 }
e724cfb1
PJ
283 if (dir_len == 0 && dir[0] != '/' && template[0] == '/')
284 template++;
d9355ab6 285 strcata(full_path, template);
1c595706
MG
286 memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
287
1c595706
MG
288 return EFI_SUCCESS;
289}
290
291EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
292{
293
294 EFI_STATUS rc;
295
296 if (!pxe)
297 return EFI_NOT_READY;
298
299 memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
300
301 /*
302 * If we've discovered an active pxe protocol figure out
303 * if its ipv4 or ipv6
304 */
305 if (pxe->Mode->UsingIpv6){
306 rc = parseDhcp6();
307 } else
308 rc = parseDhcp4();
309 return rc;
310}
311
fbc486b5 312EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
1c595706
MG
313{
314 EFI_STATUS rc;
315 EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
316 BOOLEAN overwrite = FALSE;
317 BOOLEAN nobuffer = FALSE;
318 UINTN blksz = 512;
319
320 Print(L"Fetching Netboot Image\n");
321 if (*buffer == NULL) {
322 *buffer = AllocatePool(4096 * 1024);
323 if (!*buffer)
324 return EFI_OUT_OF_RESOURCES;
325 *bufsiz = 4096 * 1024;
326 }
327
328try_again:
329 rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite,
fbc486b5 330 bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer);
1c595706
MG
331
332 if (rc == EFI_BUFFER_TOO_SMALL) {
333 /* try again, doubling buf size */
334 *bufsiz *= 2;
335 FreePool(*buffer);
336 *buffer = AllocatePool(*bufsiz);
337 if (!*buffer)
338 return EFI_OUT_OF_RESOURCES;
339 goto try_again;
340 }
341
5ccacd3a
SL
342 if (rc != EFI_SUCCESS && *buffer) {
343 FreePool(*buffer);
344 }
1c595706 345 return rc;
1c595706 346}