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