]>
Commit | Line | Data |
---|---|---|
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 | ||
19 | static EFI_PXE_BASE_CODE *pxe; | |
20 | static EFI_IP_ADDRESS tftp_addr; | |
cb89c25a | 21 | static CHAR8 *full_path; |
1c595706 MG |
22 | |
23 | ||
1c595706 MG |
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 | */ | |
da49ac6d | 35 | BOOLEAN 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 | 64 | static 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 | 99 | static 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 | 117 | static 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 | 163 | static 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 | ||
217 | static 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 | ||
233 | static 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 | 289 | EFI_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 | 310 | EFI_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 | ||
326 | try_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 | } |