]> git.proxmox.com Git - efi-boot-shim.git/blob - netboot.c
New upstream version 13~git1505328970.9c1c35c5
[efi-boot-shim.git] / netboot.c
1 /*
2 * netboot - trivial UEFI first-stage bootloader netboot support
3 *
4 * Copyright 2012 Red Hat, Inc <mjg@redhat.com>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the
16 * distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 * Significant portions of this code are derived from Tianocore
32 * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
33 * Corporation.
34 */
35
36 #include "shim.h"
37 #include <string.h>
38 #include "netboot.h"
39 #include "str.h"
40
41 #define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */
42 #define htons(x) ntohs(x)
43
44 static EFI_PXE_BASE_CODE *pxe;
45 static EFI_IP_ADDRESS tftp_addr;
46 static CHAR8 *full_path;
47
48
49 typedef struct {
50 UINT16 OpCode;
51 UINT16 Length;
52 UINT8 Data[1];
53 } EFI_DHCP6_PACKET_OPTION;
54
55 /*
56 * usingNetboot
57 * Returns TRUE if we identify a protocol that is enabled and Providing us with
58 * the needed information to fetch a grubx64.efi image
59 */
60 BOOLEAN findNetboot(EFI_HANDLE device)
61 {
62 EFI_STATUS status;
63
64 status = uefi_call_wrapper(BS->HandleProtocol, 3, device,
65 &PxeBaseCodeProtocol, (VOID **)&pxe);
66 if (status != EFI_SUCCESS) {
67 pxe = NULL;
68 return FALSE;
69 }
70
71 if (!pxe || !pxe->Mode) {
72 pxe = NULL;
73 return FALSE;
74 }
75
76 if (!pxe->Mode->Started || !pxe->Mode->DhcpAckReceived) {
77 pxe = NULL;
78 return FALSE;
79 }
80
81 /*
82 * We've located a pxe protocol handle thats been started and has
83 * received an ACK, meaning its something we'll be able to get
84 * tftp server info out of
85 */
86 return TRUE;
87 }
88
89 static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
90 {
91 void *optr = NULL, *end = NULL;
92 EFI_DHCP6_PACKET_OPTION *option = NULL;
93 CHAR8 *url = NULL;
94 UINT32 urllen = 0;
95
96 optr = pkt->DhcpOptions;
97 end = optr + sizeof(pkt->DhcpOptions);
98
99 for (;;) {
100 option = (EFI_DHCP6_PACKET_OPTION *)optr;
101
102 if (ntohs(option->OpCode) == 0)
103 break;
104
105 if (ntohs(option->OpCode) == 59) {
106 /* This is the bootfile url option */
107 urllen = ntohs(option->Length);
108 if ((void *)(option->Data + urllen) > end)
109 break;
110 url = AllocateZeroPool(urllen + 1);
111 if (!url)
112 break;
113 memcpy(url, option->Data, urllen);
114 return url;
115 }
116 optr += 4 + ntohs(option->Length);
117 if (optr + sizeof(EFI_DHCP6_PACKET_OPTION) > end)
118 break;
119 }
120
121 return NULL;
122 }
123
124 static CHAR16 str2ns(CHAR8 *str)
125 {
126 CHAR16 ret = 0;
127 CHAR8 v;
128 for(;*str;str++) {
129 if ('0' <= *str && *str <= '9')
130 v = *str - '0';
131 else if ('A' <= *str && *str <= 'F')
132 v = *str - 'A' + 10;
133 else if ('a' <= *str && *str <= 'f')
134 v = *str - 'a' + 10;
135 else
136 v = 0;
137 ret = (ret << 4) + v;
138 }
139 return htons(ret);
140 }
141
142 static CHAR8 *str2ip6(CHAR8 *str)
143 {
144 UINT8 i = 0, j = 0, p = 0;
145 size_t len = 0, dotcount = 0;
146 enum { MAX_IP6_DOTS = 7 };
147 CHAR8 *a = NULL, *b = NULL, t = 0;
148 static UINT16 ip[8];
149
150 memset(ip, 0, sizeof(ip));
151
152 /* Count amount of ':' to prevent overflows.
153 * max. count = 7. Returns an invalid ip6 that
154 * can be checked against
155 */
156 for (a = str; *a != 0; ++a) {
157 if (*a == ':')
158 ++dotcount;
159 }
160 if (dotcount > MAX_IP6_DOTS)
161 return (CHAR8 *)ip;
162
163 len = strlen(str);
164 a = b = str;
165 for (i = p = 0; i < len; i++, b++) {
166 if (*b != ':')
167 continue;
168 *b = '\0';
169 ip[p++] = str2ns(a);
170 *b = ':';
171 a = b + 1;
172 if (b[1] == ':' )
173 break;
174 }
175 a = b = (str + len);
176 for (j = len, p = 7; j > i; j--, a--) {
177 if (*a != ':')
178 continue;
179 t = *b;
180 *b = '\0';
181 ip[p--] = str2ns(a+1);
182 *b = t;
183 b = a;
184 }
185 return (CHAR8 *)ip;
186 }
187
188 static BOOLEAN extract_tftp_info(CHAR8 *url)
189 {
190 CHAR8 *start, *end;
191 CHAR8 ip6str[40];
192 CHAR8 ip6inv[16];
193 CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
194
195 // to check against str2ip6() errors
196 memset(ip6inv, 0, sizeof(ip6inv));
197
198 if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
199 Print(L"URLS MUST START WITH tftp://\n");
200 return FALSE;
201 }
202 start = url + 7;
203 if (*start != '[') {
204 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
205 return FALSE;
206 }
207
208 start++;
209 end = start;
210 while ((*end != '\0') && (*end != ']')) {
211 end++;
212 if (end - start >= (int)sizeof(ip6str)) {
213 Print(L"TFTP URL includes malformed IPv6 address\n");
214 return FALSE;
215 }
216 }
217 if (*end == '\0') {
218 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
219 return FALSE;
220 }
221 memset(ip6str, 0, sizeof(ip6str));
222 memcpy(ip6str, start, end - start);
223 end++;
224 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
225 if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0)
226 return FALSE;
227 full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
228 if (!full_path)
229 return FALSE;
230 memcpy(full_path, end, strlen(end));
231 end = (CHAR8 *)strrchr((char *)full_path, '/');
232 if (!end)
233 end = (CHAR8 *)full_path;
234 memcpy(end, template, strlen(template));
235 end[strlen(template)] = '\0';
236
237 return TRUE;
238 }
239
240 static EFI_STATUS parseDhcp6()
241 {
242 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
243 CHAR8 *bootfile_url;
244
245 bootfile_url = get_v6_bootfile_url(packet);
246 if (!bootfile_url)
247 return EFI_NOT_FOUND;
248 if (extract_tftp_info(bootfile_url) == FALSE) {
249 FreePool(bootfile_url);
250 return EFI_NOT_FOUND;
251 }
252 FreePool(bootfile_url);
253 return EFI_SUCCESS;
254 }
255
256 static EFI_STATUS parseDhcp4()
257 {
258 CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
259 INTN template_len = strlen(template) + 1;
260
261 INTN dir_len = strnlena(pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile, 127);
262 INTN i;
263 UINT8 *dir = pxe->Mode->DhcpAck.Dhcpv4.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 strncpya(full_path, 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++;
283 strcata(full_path, template);
284 memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
285
286 return EFI_SUCCESS;
287 }
288
289 EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
290 {
291
292 EFI_STATUS rc;
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 rc = parseDhcp6();
305 } else
306 rc = parseDhcp4();
307 return rc;
308 }
309
310 EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
311 {
312 EFI_STATUS rc;
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 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 rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite,
328 bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer);
329
330 if (rc == EFI_BUFFER_TOO_SMALL) {
331 /* try again, doubling buf size */
332 *bufsiz *= 2;
333 FreePool(*buffer);
334 *buffer = AllocatePool(*bufsiz);
335 if (!*buffer)
336 return EFI_OUT_OF_RESOURCES;
337 goto try_again;
338 }
339
340 if (rc != EFI_SUCCESS && *buffer) {
341 FreePool(*buffer);
342 }
343 return rc;
344 }