]> git.proxmox.com Git - efi-boot-shim.git/blob - netboot.c
debian/patches/netboot-cleanup: roll-up of miscellaneous fixes to
[efi-boot-shim.git] / netboot.c
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"
41
42
43 static inline unsigned short int __swap16(unsigned short int x)
44 {
45 __asm__("xchgb %b0,%h0"
46 : "=q" (x)
47 : "0" (x));
48 return x;
49 }
50
51 #define ntohs(x) __swap16(x)
52 #define htons(x) ntohs(x)
53
54 static EFI_PXE_BASE_CODE *pxe;
55 static EFI_IP_ADDRESS tftp_addr;
56 static UINT8 *full_path;
57
58
59 typedef struct {
60 UINT16 OpCode;
61 UINT16 Length;
62 UINT8 Data[1];
63 } EFI_DHCP6_PACKET_OPTION;
64
65 /*
66 * usingNetboot
67 * Returns TRUE if we identify a protocol that is enabled and Providing us with
68 * the needed information to fetch a grubx64.efi image
69 */
70 BOOLEAN findNetboot(EFI_HANDLE image_handle)
71 {
72 UINTN bs = sizeof(EFI_HANDLE);
73 EFI_GUID pxe_base_code_protocol = EFI_PXE_BASE_CODE_PROTOCOL;
74 EFI_HANDLE *hbuf;
75 BOOLEAN rc = FALSE;
76 void *buffer = AllocatePool(bs);
77 UINTN errcnt = 0;
78 UINTN i;
79 EFI_STATUS status;
80
81 if (!buffer)
82 return FALSE;
83
84 try_again:
85 status = uefi_call_wrapper(BS->LocateHandle,5, ByProtocol,
86 &pxe_base_code_protocol, NULL, &bs,
87 buffer);
88
89 if (status == EFI_BUFFER_TOO_SMALL) {
90 errcnt++;
91 FreePool(buffer);
92 if (errcnt > 1)
93 return FALSE;
94 buffer = AllocatePool(bs);
95 if (!buffer)
96 return FALSE;
97 goto try_again;
98 }
99
100 if (status == EFI_NOT_FOUND) {
101 FreePool(buffer);
102 return FALSE;
103 }
104
105 /*
106 * We have a list of pxe supporting protocols, lets see if any are
107 * active
108 */
109 hbuf = buffer;
110 pxe = NULL;
111 for (i=0; i < (bs / sizeof(EFI_HANDLE)); i++) {
112 status = uefi_call_wrapper(BS->OpenProtocol, 6, hbuf[i],
113 &pxe_base_code_protocol,
114 (void **)&pxe, image_handle, NULL,
115 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
116
117 if (status != EFI_SUCCESS) {
118 pxe = NULL;
119 continue;
120 }
121
122 if (!pxe || !pxe->Mode) {
123 pxe = NULL;
124 continue;
125 }
126
127 if (pxe->Mode->Started && pxe->Mode->DhcpAckReceived) {
128 /*
129 * We've located a pxe protocol handle thats been
130 * started and has received an ACK, meaning its
131 * something we'll be able to get tftp server info
132 * out of
133 */
134 rc = TRUE;
135 break;
136 }
137
138 }
139
140 FreePool(buffer);
141 return rc;
142 }
143
144 static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
145 {
146 void *optr;
147 EFI_DHCP6_PACKET_OPTION *option;
148 CHAR8 *url;
149 UINT32 urllen;
150
151 optr = pkt->DhcpOptions;
152
153 for(;;) {
154 option = (EFI_DHCP6_PACKET_OPTION *)optr;
155
156 if (ntohs(option->OpCode) == 0)
157 return NULL;
158
159 if (ntohs(option->OpCode) == 59) {
160 /* This is the bootfile url option */
161 urllen = ntohs(option->Length);
162 url = AllocateZeroPool(urllen+1);
163 if (!url)
164 return NULL;
165 memcpy(url, option->Data, urllen);
166 return url;
167 }
168 optr += 4 + ntohs(option->Length);
169 }
170
171 return NULL;
172 }
173
174 static UINT16 str2ns(UINT8 *str)
175 {
176 UINT16 ret = 0;
177 UINT8 v;
178 for(;*str;str++) {
179 if ('0' <= *str && *str <= '9')
180 v = *str - '0';
181 else if ('A' <= *str && *str <= 'F')
182 v = *str - 'A' + 10;
183 else if ('a' <= *str && *str <= 'f')
184 v = *str - 'a' + 10;
185 else
186 v = 0;
187 ret = (ret << 4) + v;
188 }
189 return htons(ret);
190 }
191
192 static UINT8 *str2ip6(char *str)
193 {
194 UINT8 i, j, p;
195 size_t len;
196 UINT8 *a, *b, t;
197 static UINT16 ip[8];
198
199 for(i=0; i < 8; i++) {
200 ip[i] = 0;
201 }
202 len = strlen((UINT8 *)str);
203 a = b = (UINT8 *)str;
204 for(i=p=0; i < len; i++, b++) {
205 if (*b != ':')
206 continue;
207 *b = '\0';
208 ip[p++] = str2ns(a);
209 *b = ':';
210 a = b + 1;
211 if ( *(b+1) == ':' )
212 break;
213 }
214 a = b = (UINT8 *)(str + len);
215 for(j=len, p=7; j > i; j--, a--) {
216 if (*a != ':')
217 continue;
218 t = *b;
219 *b = '\0';
220 ip[p--] = str2ns(a+1);
221 *b = t;
222 b = a;
223 }
224 return (UINT8 *)ip;
225 }
226
227 static BOOLEAN extract_tftp_info(CHAR8 *url)
228 {
229 CHAR8 *start, *end;
230 char ip6str[40];
231 CHAR8 *template = (CHAR8 *)"/grubx64.efi";
232
233 if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
234 Print(L"URLS MUST START WITH tftp://\n");
235 return FALSE;
236 }
237 start = url + 7;
238 if (*start != '[') {
239 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
240 return FALSE;
241 }
242
243 start++;
244 end = start;
245 while ((*end != '\0') && (*end != ']')) {
246 end++;
247 if (end - start > 39) {
248 Print(L"TFTP URL includes malformed IPv6 address\n");
249 return FALSE;
250 }
251 }
252 if (end == '\0') {
253 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
254 return FALSE;
255 }
256 memset(ip6str, 0, 40);
257 memcpy(ip6str, start, end - start);
258 end++;
259 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
260 full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
261 if (!full_path)
262 return FALSE;
263 memcpy(full_path, end, strlen(end));
264 end = (CHAR8 *)strrchr((char *)full_path, '/');
265 if (!end)
266 end = (CHAR8 *)full_path;
267 memcpy(end, template, strlen(template));
268 end[strlen(template)] = '\0';
269
270 return TRUE;
271 }
272
273 static EFI_STATUS parseDhcp6()
274 {
275 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
276 CHAR8 *bootfile_url;
277
278 bootfile_url = get_v6_bootfile_url(packet);
279 if (!bootfile_url)
280 return EFI_NOT_FOUND;
281 if (extract_tftp_info(bootfile_url) == FALSE) {
282 FreePool(bootfile_url);
283 return EFI_NOT_FOUND;
284 }
285 FreePool(bootfile_url);
286 return EFI_SUCCESS;
287 }
288
289 static EFI_STATUS parseDhcp4()
290 {
291 CHAR8 *template = (CHAR8 *)"/grubx64.efi";
292 full_path = AllocateZeroPool(strlen(template)+1);
293
294 if (!full_path)
295 return EFI_OUT_OF_RESOURCES;
296
297 memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
298
299 memcpy(full_path, template, strlen(template));
300
301 /* Note we don't capture the filename option here because we know its shim.efi
302 * We instead assume the filename at the end of the path is going to be grubx64.efi
303 */
304 return EFI_SUCCESS;
305 }
306
307 EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
308 {
309
310 EFI_STATUS rc;
311
312 if (!pxe)
313 return EFI_NOT_READY;
314
315 memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
316
317 /*
318 * If we've discovered an active pxe protocol figure out
319 * if its ipv4 or ipv6
320 */
321 if (pxe->Mode->UsingIpv6){
322 rc = parseDhcp6();
323 } else
324 rc = parseDhcp4();
325 return rc;
326 }
327
328 EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
329 {
330 EFI_STATUS rc;
331 EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
332 BOOLEAN overwrite = FALSE;
333 BOOLEAN nobuffer = FALSE;
334 UINTN blksz = 512;
335
336 Print(L"Fetching Netboot Image\n");
337 if (*buffer == NULL) {
338 *buffer = AllocatePool(4096 * 1024);
339 if (!*buffer)
340 return EFI_OUT_OF_RESOURCES;
341 *bufsiz = 4096 * 1024;
342 }
343
344 try_again:
345 rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite,
346 bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer);
347
348 if (rc == EFI_BUFFER_TOO_SMALL) {
349 /* try again, doubling buf size */
350 *bufsiz *= 2;
351 FreePool(*buffer);
352 *buffer = AllocatePool(*bufsiz);
353 if (!*buffer)
354 return EFI_OUT_OF_RESOURCES;
355 goto try_again;
356 }
357
358 if (rc != EFI_SUCCESS && *buffer) {
359 FreePool(*buffer);
360 }
361 return rc;
362 }