]> git.proxmox.com Git - efi-boot-shim.git/blame - netboot.c
Update upstream source from tag 'upstream/15.5'
[efi-boot-shim.git] / netboot.c
CommitLineData
031e5cce
SM
1// SPDX-License-Identifier: BSD-2-Clause-Patent
2
1c595706
MG
3/*
4 * netboot - trivial UEFI first-stage bootloader netboot support
5 *
031e5cce
SM
6 * Copyright Red Hat, Inc
7 * Author: Matthew Garrett
1c595706
MG
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
1c595706 14#include "shim.h"
f892ac66 15
f7a18215 16#define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */
1c595706
MG
17#define htons(x) ntohs(x)
18
19static EFI_PXE_BASE_CODE *pxe;
20static EFI_IP_ADDRESS tftp_addr;
cb89c25a 21static CHAR8 *full_path;
1c595706
MG
22
23
1c595706
MG
24typedef 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 */
da49ac6d 35BOOLEAN findNetboot(EFI_HANDLE device)
1c595706 36{
f892ac66 37 EFI_STATUS efi_status;
1c595706 38
8529e0f7
SM
39 efi_status = BS->HandleProtocol(device, &PxeBaseCodeProtocol,
40 (VOID **) &pxe);
f892ac66 41 if (EFI_ERROR(efi_status)) {
da49ac6d 42 pxe = NULL;
1c595706 43 return FALSE;
1c595706
MG
44 }
45
da49ac6d
GCPL
46 if (!pxe || !pxe->Mode) {
47 pxe = NULL;
e4d55afe
MG
48 return FALSE;
49 }
50
da49ac6d
GCPL
51 if (!pxe->Mode->Started || !pxe->Mode->DhcpAckReceived) {
52 pxe = NULL;
53 return FALSE;
1c595706
MG
54 }
55
da49ac6d
GCPL
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;
1c595706
MG
62}
63
af049ff4 64static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
1c595706 65{
f6bff34f
SK
66 void *optr = NULL, *end = NULL;
67 EFI_DHCP6_PACKET_OPTION *option = NULL;
68 CHAR8 *url = NULL;
69 UINT32 urllen = 0;
1c595706
MG
70
71 optr = pkt->DhcpOptions;
f6bff34f 72 end = optr + sizeof(pkt->DhcpOptions);
1c595706 73
f6bff34f 74 for (;;) {
1c595706
MG
75 option = (EFI_DHCP6_PACKET_OPTION *)optr;
76
77 if (ntohs(option->OpCode) == 0)
f6bff34f 78 break;
1c595706
MG
79
80 if (ntohs(option->OpCode) == 59) {
81 /* This is the bootfile url option */
82 urllen = ntohs(option->Length);
f6bff34f
SK
83 if ((void *)(option->Data + urllen) > end)
84 break;
85 url = AllocateZeroPool(urllen + 1);
1c595706 86 if (!url)
f6bff34f 87 break;
1c595706
MG
88 memcpy(url, option->Data, urllen);
89 return url;
90 }
91 optr += 4 + ntohs(option->Length);
f6bff34f
SK
92 if (optr + sizeof(EFI_DHCP6_PACKET_OPTION) > end)
93 break;
1c595706
MG
94 }
95
96 return NULL;
97}
98
cb89c25a 99static CHAR16 str2ns(CHAR8 *str)
1c595706 100{
cb89c25a
PJ
101 CHAR16 ret = 0;
102 CHAR8 v;
1c595706
MG
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
cb89c25a 117static CHAR8 *str2ip6(CHAR8 *str)
1c595706 118{
f6bff34f
SK
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];
1c595706 124
f6bff34f
SK
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;
1c595706
MG
161}
162
af049ff4 163static BOOLEAN extract_tftp_info(CHAR8 *url)
1c595706 164{
e2979f2c 165 CHAR8 *start, *end;
cb89c25a 166 CHAR8 ip6str[40];
f6bff34f 167 CHAR8 ip6inv[16];
031e5cce
SM
168 CHAR8 template[sizeof DEFAULT_LOADER_CHAR];
169
170 translate_slashes(template, DEFAULT_LOADER_CHAR);
1c595706 171
f6bff34f
SK
172 // to check against str2ip6() errors
173 memset(ip6inv, 0, sizeof(ip6inv));
174
031e5cce 175 if (strncmp((const char *)url, (const char *)"tftp://", 7)) {
f892ac66 176 console_print(L"URLS MUST START WITH tftp://\n");
1c595706
MG
177 return FALSE;
178 }
af049ff4 179 start = url + 7;
1c595706 180 if (*start != '[') {
f892ac66 181 console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
1c595706
MG
182 return FALSE;
183 }
184
185 start++;
186 end = start;
187 while ((*end != '\0') && (*end != ']')) {
188 end++;
f6bff34f 189 if (end - start >= (int)sizeof(ip6str)) {
f892ac66 190 console_print(L"TFTP URL includes malformed IPv6 address\n");
69a54db4
SL
191 return FALSE;
192 }
1c595706 193 }
159609ee 194 if (*end == '\0') {
f892ac66 195 console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
1c595706
MG
196 return FALSE;
197 }
f6bff34f 198 memset(ip6str, 0, sizeof(ip6str));
3816832b 199 memcpy(ip6str, start, end - start);
1c595706
MG
200 end++;
201 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
f6bff34f
SK
202 if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0)
203 return FALSE;
e2979f2c 204 full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
1c595706
MG
205 if (!full_path)
206 return FALSE;
e2979f2c
SL
207 memcpy(full_path, end, strlen(end));
208 end = (CHAR8 *)strrchr((char *)full_path, '/');
1c595706 209 if (!end)
e2979f2c
SL
210 end = (CHAR8 *)full_path;
211 memcpy(end, template, strlen(template));
212 end[strlen(template)] = '\0';
1c595706
MG
213
214 return TRUE;
215}
216
217static EFI_STATUS parseDhcp6()
218{
219 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
af049ff4 220 CHAR8 *bootfile_url;
1c595706
MG
221
222 bootfile_url = get_v6_bootfile_url(packet);
1c595706
MG
223 if (!bootfile_url)
224 return EFI_NOT_FOUND;
6eaa1a9c
SL
225 if (extract_tftp_info(bootfile_url) == FALSE) {
226 FreePool(bootfile_url);
227 return EFI_NOT_FOUND;
228 }
229 FreePool(bootfile_url);
1c595706
MG
230 return EFI_SUCCESS;
231}
232
233static EFI_STATUS parseDhcp4()
234{
031e5cce
SM
235 CHAR8 template[sizeof DEFAULT_LOADER_CHAR];
236 INTN template_len;
237 UINTN template_ofs = 0;
f892ac66
MTL
238 EFI_PXE_BASE_CODE_DHCPV4_PACKET* pkt_v4 = (EFI_PXE_BASE_CODE_DHCPV4_PACKET *)&pxe->Mode->DhcpAck.Dhcpv4;
239
031e5cce
SM
240 translate_slashes(template, DEFAULT_LOADER_CHAR);
241 template_len = strlen(template) + 1;
242
f892ac66
MTL
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 }
d9355ab6 251
031e5cce
SM
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);
e724cfb1 262 INTN i;
f892ac66 263 UINT8 *dir = pkt_v4->BootpBootFile;
d9355ab6
PJ
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);
1c595706 272
e2979f2c 273 if (!full_path)
1c595706
MG
274 return EFI_OUT_OF_RESOURCES;
275
d9355ab6 276 if (dir_len > 0) {
031e5cce 277 strncpy(full_path, (CHAR8 *)dir, dir_len);
d9355ab6
PJ
278 if (full_path[dir_len-1] == '/' && template[0] == '/')
279 full_path[dir_len-1] = '\0';
280 }
e724cfb1 281 if (dir_len == 0 && dir[0] != '/' && template[0] == '/')
031e5cce
SM
282 template_ofs++;
283 strcat(full_path, template + template_ofs);
f892ac66 284 memcpy(&tftp_addr.v4, pkt_v4->BootpSiAddr, 4);
1c595706 285
1c595706
MG
286 return EFI_SUCCESS;
287}
288
031e5cce 289EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle UNUSED)
1c595706
MG
290{
291
f892ac66 292 EFI_STATUS efi_status;
1c595706
MG
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){
f892ac66 304 efi_status = parseDhcp6();
1c595706 305 } else
f892ac66
MTL
306 efi_status = parseDhcp4();
307 return efi_status;
1c595706
MG
308}
309
031e5cce 310EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle UNUSED, VOID **buffer, UINT64 *bufsiz)
1c595706 311{
f892ac66 312 EFI_STATUS efi_status;
1c595706
MG
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
f892ac66 318 console_print(L"Fetching Netboot Image\n");
1c595706
MG
319 if (*buffer == NULL) {
320 *buffer = AllocatePool(4096 * 1024);
321 if (!*buffer)
f892ac66 322 return EFI_OUT_OF_RESOURCES;
1c595706
MG
323 *bufsiz = 4096 * 1024;
324 }
325
326try_again:
f892ac66 327 efi_status = pxe->Mtftp(pxe, read, *buffer, overwrite, bufsiz, &blksz,
031e5cce 328 &tftp_addr, (UINT8 *)full_path, NULL, nobuffer);
f892ac66 329 if (efi_status == EFI_BUFFER_TOO_SMALL) {
1c595706
MG
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
f892ac66 339 if (EFI_ERROR(efi_status) && *buffer) {
5ccacd3a
SL
340 FreePool(*buffer);
341 }
f892ac66 342 return efi_status;
1c595706 343}