]> git.proxmox.com Git - efi-boot-shim.git/blob - netboot.c
Check PxeReplyReceived as fallback in netboot
[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 "shim.h"
37
38 #include <string.h>
39
40 #define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */
41 #define htons(x) ntohs(x)
42
43 static EFI_PXE_BASE_CODE *pxe;
44 static EFI_IP_ADDRESS tftp_addr;
45 static CHAR8 *full_path;
46
47
48 typedef struct {
49 UINT16 OpCode;
50 UINT16 Length;
51 UINT8 Data[1];
52 } EFI_DHCP6_PACKET_OPTION;
53
54 /*
55 * usingNetboot
56 * Returns TRUE if we identify a protocol that is enabled and Providing us with
57 * the needed information to fetch a grubx64.efi image
58 */
59 BOOLEAN findNetboot(EFI_HANDLE device)
60 {
61 EFI_STATUS efi_status;
62
63 efi_status = gBS->HandleProtocol(device, &PxeBaseCodeProtocol,
64 (VOID **) &pxe);
65 if (EFI_ERROR(efi_status)) {
66 pxe = NULL;
67 return FALSE;
68 }
69
70 if (!pxe || !pxe->Mode) {
71 pxe = NULL;
72 return FALSE;
73 }
74
75 if (!pxe->Mode->Started || !pxe->Mode->DhcpAckReceived) {
76 pxe = NULL;
77 return FALSE;
78 }
79
80 /*
81 * We've located a pxe protocol handle thats been started and has
82 * received an ACK, meaning its something we'll be able to get
83 * tftp server info out of
84 */
85 return TRUE;
86 }
87
88 static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
89 {
90 void *optr = NULL, *end = NULL;
91 EFI_DHCP6_PACKET_OPTION *option = NULL;
92 CHAR8 *url = NULL;
93 UINT32 urllen = 0;
94
95 optr = pkt->DhcpOptions;
96 end = optr + sizeof(pkt->DhcpOptions);
97
98 for (;;) {
99 option = (EFI_DHCP6_PACKET_OPTION *)optr;
100
101 if (ntohs(option->OpCode) == 0)
102 break;
103
104 if (ntohs(option->OpCode) == 59) {
105 /* This is the bootfile url option */
106 urllen = ntohs(option->Length);
107 if ((void *)(option->Data + urllen) > end)
108 break;
109 url = AllocateZeroPool(urllen + 1);
110 if (!url)
111 break;
112 memcpy(url, option->Data, urllen);
113 return url;
114 }
115 optr += 4 + ntohs(option->Length);
116 if (optr + sizeof(EFI_DHCP6_PACKET_OPTION) > end)
117 break;
118 }
119
120 return NULL;
121 }
122
123 static CHAR16 str2ns(CHAR8 *str)
124 {
125 CHAR16 ret = 0;
126 CHAR8 v;
127 for(;*str;str++) {
128 if ('0' <= *str && *str <= '9')
129 v = *str - '0';
130 else if ('A' <= *str && *str <= 'F')
131 v = *str - 'A' + 10;
132 else if ('a' <= *str && *str <= 'f')
133 v = *str - 'a' + 10;
134 else
135 v = 0;
136 ret = (ret << 4) + v;
137 }
138 return htons(ret);
139 }
140
141 static CHAR8 *str2ip6(CHAR8 *str)
142 {
143 UINT8 i = 0, j = 0, p = 0;
144 size_t len = 0, dotcount = 0;
145 enum { MAX_IP6_DOTS = 7 };
146 CHAR8 *a = NULL, *b = NULL, t = 0;
147 static UINT16 ip[8];
148
149 memset(ip, 0, sizeof(ip));
150
151 /* Count amount of ':' to prevent overflows.
152 * max. count = 7. Returns an invalid ip6 that
153 * can be checked against
154 */
155 for (a = str; *a != 0; ++a) {
156 if (*a == ':')
157 ++dotcount;
158 }
159 if (dotcount > MAX_IP6_DOTS)
160 return (CHAR8 *)ip;
161
162 len = strlen(str);
163 a = b = str;
164 for (i = p = 0; i < len; i++, b++) {
165 if (*b != ':')
166 continue;
167 *b = '\0';
168 ip[p++] = str2ns(a);
169 *b = ':';
170 a = b + 1;
171 if (b[1] == ':' )
172 break;
173 }
174 a = b = (str + len);
175 for (j = len, p = 7; j > i; j--, a--) {
176 if (*a != ':')
177 continue;
178 t = *b;
179 *b = '\0';
180 ip[p--] = str2ns(a+1);
181 *b = t;
182 b = a;
183 }
184 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[sizeof DEFAULT_LOADER_CHAR];
193
194 translate_slashes(template, DEFAULT_LOADER_CHAR);
195
196 // to check against str2ip6() errors
197 memset(ip6inv, 0, sizeof(ip6inv));
198
199 if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
200 console_print(L"URLS MUST START WITH tftp://\n");
201 return FALSE;
202 }
203 start = url + 7;
204 if (*start != '[') {
205 console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
206 return FALSE;
207 }
208
209 start++;
210 end = start;
211 while ((*end != '\0') && (*end != ']')) {
212 end++;
213 if (end - start >= (int)sizeof(ip6str)) {
214 console_print(L"TFTP URL includes malformed IPv6 address\n");
215 return FALSE;
216 }
217 }
218 if (*end == '\0') {
219 console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
220 return FALSE;
221 }
222 memset(ip6str, 0, sizeof(ip6str));
223 memcpy(ip6str, start, end - start);
224 end++;
225 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
226 if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0)
227 return FALSE;
228 full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
229 if (!full_path)
230 return FALSE;
231 memcpy(full_path, end, strlen(end));
232 end = (CHAR8 *)strrchr((char *)full_path, '/');
233 if (!end)
234 end = (CHAR8 *)full_path;
235 memcpy(end, template, strlen(template));
236 end[strlen(template)] = '\0';
237
238 return TRUE;
239 }
240
241 static EFI_STATUS parseDhcp6()
242 {
243 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
244 CHAR8 *bootfile_url;
245
246 bootfile_url = get_v6_bootfile_url(packet);
247 if (!bootfile_url)
248 return EFI_NOT_FOUND;
249 if (extract_tftp_info(bootfile_url) == FALSE) {
250 FreePool(bootfile_url);
251 return EFI_NOT_FOUND;
252 }
253 FreePool(bootfile_url);
254 return EFI_SUCCESS;
255 }
256
257 static EFI_STATUS parseDhcp4()
258 {
259 CHAR8 template[sizeof DEFAULT_LOADER_CHAR];
260 INTN template_len;
261 UINTN template_ofs = 0;
262 EFI_PXE_BASE_CODE_DHCPV4_PACKET* pkt_v4 = (EFI_PXE_BASE_CODE_DHCPV4_PACKET *)&pxe->Mode->DhcpAck.Dhcpv4;
263
264 translate_slashes(template, DEFAULT_LOADER_CHAR);
265 template_len = strlen(template) + 1;
266
267 if(pxe->Mode->ProxyOfferReceived) {
268 /*
269 * Proxy should not have precedence. Check if DhcpAck
270 * contained boot info.
271 */
272 if(pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile[0] == '\0')
273 pkt_v4 = &pxe->Mode->ProxyOffer.Dhcpv4;
274 }
275
276 if(pxe->Mode->PxeReplyReceived) {
277 /*
278 * If we have no bootinfo yet search for it in the PxeReply.
279 * Some mainboards run into this when the server uses boot menus
280 */
281 if(pkt_v4->BootpBootFile[0] == '\0' && pxe->Mode->PxeReply.Dhcpv4.BootpBootFile[0] != '\0')
282 pkt_v4 = &pxe->Mode->PxeReply.Dhcpv4;
283 }
284
285 INTN dir_len = strnlena((CHAR8 *)pkt_v4->BootpBootFile, 127);
286 INTN i;
287 UINT8 *dir = pkt_v4->BootpBootFile;
288
289 for (i = dir_len; i >= 0; i--) {
290 if (dir[i] == '/')
291 break;
292 }
293 dir_len = (i >= 0) ? i + 1 : 0;
294
295 full_path = AllocateZeroPool(dir_len + template_len);
296
297 if (!full_path)
298 return EFI_OUT_OF_RESOURCES;
299
300 if (dir_len > 0) {
301 strncpya(full_path, (CHAR8 *)dir, dir_len);
302 if (full_path[dir_len-1] == '/' && template[0] == '/')
303 full_path[dir_len-1] = '\0';
304 }
305 if (dir_len == 0 && dir[0] != '/' && template[0] == '/')
306 template_ofs++;
307 strcata(full_path, template + template_ofs);
308 memcpy(&tftp_addr.v4, pkt_v4->BootpSiAddr, 4);
309
310 return EFI_SUCCESS;
311 }
312
313 EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
314 {
315
316 EFI_STATUS efi_status;
317
318 if (!pxe)
319 return EFI_NOT_READY;
320
321 memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
322
323 /*
324 * If we've discovered an active pxe protocol figure out
325 * if its ipv4 or ipv6
326 */
327 if (pxe->Mode->UsingIpv6){
328 efi_status = parseDhcp6();
329 } else
330 efi_status = parseDhcp4();
331 return efi_status;
332 }
333
334 EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
335 {
336 EFI_STATUS efi_status;
337 EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
338 BOOLEAN overwrite = FALSE;
339 BOOLEAN nobuffer = FALSE;
340 UINTN blksz = 512;
341
342 console_print(L"Fetching Netboot Image\n");
343 if (*buffer == NULL) {
344 *buffer = AllocatePool(4096 * 1024);
345 if (!*buffer)
346 return EFI_OUT_OF_RESOURCES;
347 *bufsiz = 4096 * 1024;
348 }
349
350 try_again:
351 efi_status = pxe->Mtftp(pxe, read, *buffer, overwrite, bufsiz, &blksz,
352 &tftp_addr, (UINT8 *)full_path, NULL, nobuffer);
353 if (efi_status == EFI_BUFFER_TOO_SMALL) {
354 /* try again, doubling buf size */
355 *bufsiz *= 2;
356 FreePool(*buffer);
357 *buffer = AllocatePool(*bufsiz);
358 if (!*buffer)
359 return EFI_OUT_OF_RESOURCES;
360 goto try_again;
361 }
362
363 if (EFI_ERROR(efi_status) && *buffer) {
364 FreePool(*buffer);
365 }
366 return efi_status;
367 }