]> git.proxmox.com Git - efi-boot-shim.git/blob - netboot.c
Update upstream source from tag 'upstream/15.3'
[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 = gBS->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)
164 {
165 CHAR8 *start, *end;
166 CHAR8 ip6str[40];
167 CHAR8 ip6inv[16];
168 CHAR8 template[sizeof DEFAULT_LOADER_CHAR];
169
170 translate_slashes(template, DEFAULT_LOADER_CHAR);
171
172 // to check against str2ip6() errors
173 memset(ip6inv, 0, sizeof(ip6inv));
174
175 if (strncmp((const char *)url, (const char *)"tftp://", 7)) {
176 console_print(L"URLS MUST START WITH tftp://\n");
177 return FALSE;
178 }
179 start = url + 7;
180 if (*start != '[') {
181 console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
182 return FALSE;
183 }
184
185 start++;
186 end = start;
187 while ((*end != '\0') && (*end != ']')) {
188 end++;
189 if (end - start >= (int)sizeof(ip6str)) {
190 console_print(L"TFTP URL includes malformed IPv6 address\n");
191 return FALSE;
192 }
193 }
194 if (*end == '\0') {
195 console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
196 return FALSE;
197 }
198 memset(ip6str, 0, sizeof(ip6str));
199 memcpy(ip6str, start, end - start);
200 end++;
201 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
202 if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0)
203 return FALSE;
204 full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
205 if (!full_path)
206 return FALSE;
207 memcpy(full_path, end, strlen(end));
208 end = (CHAR8 *)strrchr((char *)full_path, '/');
209 if (!end)
210 end = (CHAR8 *)full_path;
211 memcpy(end, template, strlen(template));
212 end[strlen(template)] = '\0';
213
214 return TRUE;
215 }
216
217 static EFI_STATUS parseDhcp6()
218 {
219 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
220 CHAR8 *bootfile_url;
221
222 bootfile_url = get_v6_bootfile_url(packet);
223 if (!bootfile_url)
224 return EFI_NOT_FOUND;
225 if (extract_tftp_info(bootfile_url) == FALSE) {
226 FreePool(bootfile_url);
227 return EFI_NOT_FOUND;
228 }
229 FreePool(bootfile_url);
230 return EFI_SUCCESS;
231 }
232
233 static EFI_STATUS parseDhcp4()
234 {
235 CHAR8 template[sizeof DEFAULT_LOADER_CHAR];
236 INTN template_len;
237 UINTN template_ofs = 0;
238 EFI_PXE_BASE_CODE_DHCPV4_PACKET* pkt_v4 = (EFI_PXE_BASE_CODE_DHCPV4_PACKET *)&pxe->Mode->DhcpAck.Dhcpv4;
239
240 translate_slashes(template, DEFAULT_LOADER_CHAR);
241 template_len = strlen(template) + 1;
242
243 if(pxe->Mode->ProxyOfferReceived) {
244 /*
245 * Proxy should not have precedence. Check if DhcpAck
246 * contained boot info.
247 */
248 if(pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile[0] == '\0')
249 pkt_v4 = &pxe->Mode->ProxyOffer.Dhcpv4;
250 }
251
252 if(pxe->Mode->PxeReplyReceived) {
253 /*
254 * If we have no bootinfo yet search for it in the PxeReply.
255 * Some mainboards run into this when the server uses boot menus
256 */
257 if(pkt_v4->BootpBootFile[0] == '\0' && pxe->Mode->PxeReply.Dhcpv4.BootpBootFile[0] != '\0')
258 pkt_v4 = &pxe->Mode->PxeReply.Dhcpv4;
259 }
260
261 INTN dir_len = strnlen((CHAR8 *)pkt_v4->BootpBootFile, 127);
262 INTN i;
263 UINT8 *dir = pkt_v4->BootpBootFile;
264
265 for (i = dir_len; i >= 0; i--) {
266 if (dir[i] == '/')
267 break;
268 }
269 dir_len = (i >= 0) ? i + 1 : 0;
270
271 full_path = AllocateZeroPool(dir_len + template_len);
272
273 if (!full_path)
274 return EFI_OUT_OF_RESOURCES;
275
276 if (dir_len > 0) {
277 strncpy(full_path, (CHAR8 *)dir, dir_len);
278 if (full_path[dir_len-1] == '/' && template[0] == '/')
279 full_path[dir_len-1] = '\0';
280 }
281 if (dir_len == 0 && dir[0] != '/' && template[0] == '/')
282 template_ofs++;
283 strcat(full_path, template + template_ofs);
284 memcpy(&tftp_addr.v4, pkt_v4->BootpSiAddr, 4);
285
286 return EFI_SUCCESS;
287 }
288
289 EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle UNUSED)
290 {
291
292 EFI_STATUS efi_status;
293
294 if (!pxe)
295 return EFI_NOT_READY;
296
297 memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
298
299 /*
300 * If we've discovered an active pxe protocol figure out
301 * if its ipv4 or ipv6
302 */
303 if (pxe->Mode->UsingIpv6){
304 efi_status = parseDhcp6();
305 } else
306 efi_status = parseDhcp4();
307 return efi_status;
308 }
309
310 EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle UNUSED, VOID **buffer, UINT64 *bufsiz)
311 {
312 EFI_STATUS efi_status;
313 EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
314 BOOLEAN overwrite = FALSE;
315 BOOLEAN nobuffer = FALSE;
316 UINTN blksz = 512;
317
318 console_print(L"Fetching Netboot Image\n");
319 if (*buffer == NULL) {
320 *buffer = AllocatePool(4096 * 1024);
321 if (!*buffer)
322 return EFI_OUT_OF_RESOURCES;
323 *bufsiz = 4096 * 1024;
324 }
325
326 try_again:
327 efi_status = pxe->Mtftp(pxe, read, *buffer, overwrite, bufsiz, &blksz,
328 &tftp_addr, (UINT8 *)full_path, NULL, nobuffer);
329 if (efi_status == EFI_BUFFER_TOO_SMALL) {
330 /* try again, doubling buf size */
331 *bufsiz *= 2;
332 FreePool(*buffer);
333 *buffer = AllocatePool(*bufsiz);
334 if (!*buffer)
335 return EFI_OUT_OF_RESOURCES;
336 goto try_again;
337 }
338
339 if (EFI_ERROR(efi_status) && *buffer) {
340 FreePool(*buffer);
341 }
342 return efi_status;
343 }