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