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