]> git.proxmox.com Git - grub2.git/blame - grub-core/loader/i386/coreboot/chainloader.c
verifiers: File type for fine-grained signature-verification controlling
[grub2.git] / grub-core / loader / i386 / coreboot / chainloader.c
CommitLineData
ba7df45e
VS
1/*
2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2011 Free Software Foundation, Inc.
4 *
5 * GRUB is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * GRUB is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include <grub/loader.h>
20#include <grub/memory.h>
21#include <grub/i386/memory.h>
22#include <grub/file.h>
23#include <grub/err.h>
24#include <grub/dl.h>
25#include <grub/mm.h>
26#include <grub/elfload.h>
27#include <grub/video.h>
28#include <grub/relocator.h>
29#include <grub/i386/relocator.h>
30#include <grub/command.h>
31#include <grub/i18n.h>
99c971af 32#include <grub/cbfs_core.h>
89977306 33#include <grub/lib/LzmaDec.h>
5460cfeb
VS
34#include <grub/efi/pe32.h>
35#include <grub/i386/cpuid.h>
ba7df45e
VS
36
37GRUB_MOD_LICENSE ("GPLv3+");
38
39static grub_addr_t entry;
40static struct grub_relocator *relocator = NULL;
41
42static grub_err_t
43grub_chain_boot (void)
44{
45 struct grub_relocator32_state state;
46
47 grub_video_set_mode ("text", 0, 0);
48
49 state.eip = entry;
9be4c45d 50 return grub_relocator32_boot (relocator, state, 0);
ba7df45e
VS
51}
52
53static grub_err_t
54grub_chain_unload (void)
55{
56 grub_relocator_unload (relocator);
57 relocator = NULL;
58
59 return GRUB_ERR_NONE;
60}
61
ba7df45e 62static grub_err_t
99c971af 63load_elf (grub_file_t file, const char *filename)
ba7df45e 64{
ba7df45e 65 grub_elf_t elf;
73bf57e2 66 Elf32_Phdr *phdr;
99c971af 67 grub_err_t err;
ba7df45e 68
99c971af 69 elf = grub_elf_file (file, filename);
ba7df45e 70 if (!elf)
99c971af 71 return grub_errno;
ba7df45e
VS
72
73 if (!grub_elf_is_elf32 (elf))
99c971af 74 return grub_error (GRUB_ERR_BAD_OS, "only ELF32 can be coreboot payload");
ba7df45e 75
73bf57e2
VS
76 entry = elf->ehdr.ehdr32.e_entry;
77
78 FOR_ELF32_PHDRS(elf, phdr)
79 {
80 grub_uint8_t *load_addr;
81 grub_relocator_chunk_t ch;
82
83 if (phdr->p_type != PT_LOAD)
84 continue;
85
86 err = grub_relocator_alloc_chunk_addr (relocator, &ch,
87 phdr->p_paddr, phdr->p_memsz);
88 if (err)
99c971af
VS
89 {
90 elf->file = 0;
91 grub_elf_close (elf);
92 return err;
93 }
73bf57e2
VS
94
95 load_addr = get_virtual_current_address (ch);
96
97 if (grub_file_seek (elf->file, phdr->p_offset) == (grub_off_t) -1)
99c971af
VS
98 {
99 elf->file = 0;
100 grub_elf_close (elf);
101 return grub_errno;
102 }
73bf57e2
VS
103
104 if (phdr->p_filesz)
105 {
106 grub_ssize_t read;
107 read = grub_file_read (elf->file, load_addr, phdr->p_filesz);
108 if (read != (grub_ssize_t) phdr->p_filesz)
109 {
110 if (!grub_errno)
99c971af
VS
111 grub_error (GRUB_ERR_FILE_READ_ERROR,
112 N_("premature end of file %s"),
113 filename);
114 elf->file = 0;
115 grub_elf_close (elf);
116 return grub_errno;
73bf57e2
VS
117 }
118 }
119
120 if (phdr->p_filesz < phdr->p_memsz)
121 grub_memset ((load_addr + phdr->p_filesz),
122 0, phdr->p_memsz - phdr->p_filesz);
123 }
ba7df45e 124
99c971af 125 elf->file = 0;
ba7df45e 126 grub_elf_close (elf);
99c971af
VS
127 return GRUB_ERR_NONE;
128}
129
89977306
VS
130static void *SzAlloc(void *p __attribute__ ((unused)), size_t size) { return grub_malloc (size); }
131static void SzFree(void *p __attribute__ ((unused)), void *address) { grub_free (address); }
132static ISzAlloc g_Alloc = { SzAlloc, SzFree };
133
134
99c971af
VS
135static grub_err_t
136load_segment (grub_file_t file, const char *filename,
137 void *load_addr, grub_uint32_t comp,
89977306 138 grub_size_t *size, grub_size_t max_size)
99c971af
VS
139{
140 switch (comp)
141 {
142 case grub_cpu_to_be32_compile_time (CBFS_COMPRESS_NONE):
89977306
VS
143 if (grub_file_read (file, load_addr, *size)
144 != (grub_ssize_t) *size)
99c971af
VS
145 {
146 if (!grub_errno)
147 grub_error (GRUB_ERR_FILE_READ_ERROR,
148 N_("premature end of file %s"),
149 filename);
150 return grub_errno;
151 }
152 return GRUB_ERR_NONE;
89977306
VS
153 case grub_cpu_to_be32_compile_time (CBFS_COMPRESS_LZMA):
154 {
155 grub_uint8_t *buf;
156 grub_size_t outsize, insize;
157 SRes res;
158 SizeT src_len, dst_len;
159 ELzmaStatus status;
160 if (*size < 13)
161 return grub_error (GRUB_ERR_BAD_OS, "invalid compressed chunk");
162 buf = grub_malloc (*size);
163 if (!buf)
164 return grub_errno;
87d62d7d 165 if (grub_file_read (file, buf, *size)
89977306
VS
166 != (grub_ssize_t) *size)
167 {
168 if (!grub_errno)
169 grub_error (GRUB_ERR_FILE_READ_ERROR,
170 N_("premature end of file %s"),
171 filename);
172 grub_free (buf);
173 return grub_errno;
174 }
175 outsize = grub_get_unaligned64 (buf + 5);
176 if (outsize > max_size)
177 {
178 grub_free (buf);
179 return grub_error (GRUB_ERR_BAD_OS, "invalid compressed chunk");
180 }
181 insize = *size - 13;
182
183 src_len = insize;
184 dst_len = outsize;
185 res = LzmaDecode (load_addr, &dst_len, buf + 13, &src_len,
186 buf, 5, LZMA_FINISH_END, &status, &g_Alloc);
187 /* ELzmaFinishMode finishMode,
188 ELzmaStatus *status, ISzAlloc *alloc)*/
189 grub_free (buf);
87d62d7d
VS
190 grub_dprintf ("chain", "%x, %x, %x, %x\n",
191 insize, src_len, outsize, dst_len);
89977306
VS
192 if (res != SZ_OK
193 || src_len != insize || dst_len != outsize)
194 return grub_error (GRUB_ERR_BAD_OS, "decompression failure %d", res);
195 *size = outsize;
196 }
197 return GRUB_ERR_NONE;
99c971af
VS
198 default:
199 return grub_error (GRUB_ERR_BAD_OS, "unsupported compression %d",
200 grub_be_to_cpu32 (comp));
201 }
202}
203
5460cfeb
VS
204static grub_err_t
205load_tianocore (grub_file_t file)
206{
207 grub_uint16_t header_length;
208 grub_uint32_t section_head;
209 grub_uint8_t mz[2], pe[4];
210 struct grub_pe32_coff_header coff_head;
211 struct file_header
212 {
213 grub_uint8_t unused[18];
214 grub_uint8_t type;
215 grub_uint8_t unused2;
216 grub_uint8_t size[3];
217 grub_uint8_t unused3;
218 } file_head;
219 grub_relocator_chunk_t ch;
220
221 if (grub_file_seek (file, 48) == (grub_off_t) -1
222 || grub_file_read (file, &header_length, sizeof (header_length))
223 != sizeof (header_length)
224 || grub_file_seek (file, header_length) == (grub_off_t) -1)
225 goto fail;
226
227 while (1)
228 {
229 grub_off_t off;
230 if (grub_file_read (file, &file_head, sizeof (file_head))
231 != sizeof (file_head))
232 goto fail;
233 if (file_head.type != 0xf0)
234 break;
235 off = grub_get_unaligned32 (file_head.size) & 0xffffff;
236 if (off < sizeof (file_head))
237 goto fail;
238 if (grub_file_seek (file, grub_file_tell (file) + off
239 - sizeof (file_head)) == (grub_off_t) -1)
240 goto fail;
241 }
242
243 if (file_head.type != 0x03)
244 goto fail;
245
246 while (1)
247 {
248 if (grub_file_read (file, &section_head, sizeof (section_head))
249 != sizeof (section_head))
250 goto fail;
251 if ((section_head >> 24) != 0x19)
252 break;
253
254 if ((section_head & 0xffffff) < sizeof (section_head))
255 goto fail;
256
257 if (grub_file_seek (file, grub_file_tell (file)
258 + (section_head & 0xffffff)
259 - sizeof (section_head)) == (grub_off_t) -1)
260 goto fail;
261 }
262
263 if ((section_head >> 24) != 0x10)
264 goto fail;
265
266 grub_off_t exe_start = grub_file_tell (file);
267
268 if (grub_file_read (file, &mz, sizeof (mz)) != sizeof (mz))
269 goto fail;
270 if (mz[0] != 'M' || mz[1] != 'Z')
271 goto fail;
272
273 if (grub_file_seek (file, grub_file_tell (file) + 0x3a) == (grub_off_t) -1)
274 goto fail;
275
276 if (grub_file_read (file, &section_head, sizeof (section_head))
277 != sizeof (section_head))
278 goto fail;
279 if (section_head < 0x40)
280 goto fail;
281
282 if (grub_file_seek (file, grub_file_tell (file)
283 + section_head - 0x40) == (grub_off_t) -1)
284 goto fail;
285
286 if (grub_file_read (file, &pe, sizeof (pe))
287 != sizeof (pe))
288 goto fail;
289
290 if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != '\0' || pe[3] != '\0')
291 goto fail;
292
293 if (grub_file_read (file, &coff_head, sizeof (coff_head))
294 != sizeof (coff_head))
295 goto fail;
296
297 grub_uint32_t loadaddr;
298
299 switch (coff_head.machine)
300 {
301 case GRUB_PE32_MACHINE_I386:
302 {
303 struct grub_pe32_optional_header oh;
304 if (grub_file_read (file, &oh, sizeof (oh))
305 != sizeof (oh))
306 goto fail;
307 if (oh.magic != GRUB_PE32_PE32_MAGIC)
308 goto fail;
309 loadaddr = oh.image_base - exe_start;
310 entry = oh.image_base + oh.entry_addr;
311 break;
312 }
313 case GRUB_PE32_MACHINE_X86_64:
314 {
315 struct grub_pe64_optional_header oh;
316 if (! grub_cpuid_has_longmode)
317 {
318 grub_error (GRUB_ERR_BAD_OS, "your CPU does not implement AMD64 architecture");
319 goto fail;
320 }
321
322 if (grub_file_read (file, &oh, sizeof (oh))
323 != sizeof (oh))
324 goto fail;
325 if (oh.magic != GRUB_PE32_PE64_MAGIC)
326 goto fail;
327 loadaddr = oh.image_base - exe_start;
328 entry = oh.image_base + oh.entry_addr;
329 break;
330 }
331 default:
332 goto fail;
333 }
334 if (grub_file_seek (file, 0) == (grub_off_t) -1)
335 goto fail;
336
337 grub_size_t fz = grub_file_size (file);
338
339 if (grub_relocator_alloc_chunk_addr (relocator, &ch,
340 loadaddr, fz))
341 goto fail;
342
343 if (grub_file_read (file, get_virtual_current_address (ch), fz)
344 != (grub_ssize_t) fz)
345 goto fail;
346
347 return GRUB_ERR_NONE;
348
349 fail:
350 if (!grub_errno)
351 grub_error (GRUB_ERR_BAD_OS, "fv volume is invalid");
352 return grub_errno;
353}
354
99c971af
VS
355static grub_err_t
356load_chewed (grub_file_t file, const char *filename)
357{
358 grub_size_t i;
359 for (i = 0;; i++)
360 {
361 struct cbfs_payload_segment segment;
362 grub_err_t err;
363
364 if (grub_file_seek (file, sizeof (segment) * i) == (grub_off_t) -1
365 || grub_file_read (file, &segment, sizeof (segment))
366 != sizeof (segment))
367 {
368 if (!grub_errno)
369 return grub_error (GRUB_ERR_BAD_OS,
370 "payload is too short");
371 return grub_errno;
372 }
373
374 switch (segment.type)
375 {
376 case PAYLOAD_SEGMENT_PARAMS:
377 break;
378
379 case PAYLOAD_SEGMENT_ENTRY:
380 entry = grub_be_to_cpu64 (segment.load_addr);
381 return GRUB_ERR_NONE;
382
383 case PAYLOAD_SEGMENT_BSS:
384 segment.len = 0;
385 segment.offset = 0;
386 segment.len = 0;
d454509b 387 /* Fallthrough. */
99c971af
VS
388 case PAYLOAD_SEGMENT_CODE:
389 case PAYLOAD_SEGMENT_DATA:
390 {
391 grub_uint32_t target = grub_be_to_cpu64 (segment.load_addr);
392 grub_uint32_t memsize = grub_be_to_cpu32 (segment.mem_len);
393 grub_uint32_t filesize = grub_be_to_cpu32 (segment.len);
394 grub_uint8_t *load_addr;
395 grub_relocator_chunk_t ch;
396
397 if (memsize < filesize)
398 memsize = filesize;
399
89977306
VS
400 grub_dprintf ("chain", "%x+%x\n", target, memsize);
401
99c971af
VS
402 err = grub_relocator_alloc_chunk_addr (relocator, &ch,
403 target, memsize);
404 if (err)
405 return err;
406
407 load_addr = get_virtual_current_address (ch);
408
409 if (filesize)
410 {
411 if (grub_file_seek (file, grub_be_to_cpu32 (segment.offset))
412 == (grub_off_t) -1)
413 return grub_errno;
414
415 err = load_segment (file, filename, load_addr,
89977306 416 segment.compression, &filesize, memsize);
99c971af
VS
417 if (err)
418 return err;
419 }
420
421 if (filesize < memsize)
422 grub_memset ((load_addr + filesize),
423 0, memsize - filesize);
424 }
425 }
426 }
427}
428
429static grub_err_t
430grub_cmd_chain (grub_command_t cmd __attribute__ ((unused)),
431 int argc, char *argv[])
432{
433 grub_err_t err;
434 grub_file_t file;
435 grub_uint32_t head;
436
437 if (argc != 1)
438 return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
439
440 grub_loader_unset ();
441
ca0a4f68 442 file = grub_file_open (argv[0], GRUB_FILE_TYPE_COREBOOT_CHAINLOADER);
99c971af 443 if (!file)
73bf57e2 444 return grub_errno;
ba7df45e 445
99c971af
VS
446 relocator = grub_relocator_new ();
447 if (!relocator)
448 {
449 grub_file_close (file);
450 return grub_errno;
451 }
452
453 if (grub_file_read (file, &head, sizeof (head)) != sizeof (head)
454 || grub_file_seek (file, 0) == (grub_off_t) -1)
455 {
456 grub_file_close (file);
457 grub_relocator_unload (relocator);
458 relocator = 0;
459 if (!grub_errno)
460 return grub_error (GRUB_ERR_BAD_OS,
461 "payload is too short");
462 return grub_errno;
463 }
464
465 switch (head)
466 {
467 case ELFMAG0 | (ELFMAG1 << 8) | (ELFMAG2 << 16) | (ELFMAG3 << 24):
468 err = load_elf (file, argv[0]);
469 break;
470 case PAYLOAD_SEGMENT_CODE:
471 case PAYLOAD_SEGMENT_DATA:
472 case PAYLOAD_SEGMENT_PARAMS:
473 case PAYLOAD_SEGMENT_BSS:
474 case PAYLOAD_SEGMENT_ENTRY:
475 err = load_chewed (file, argv[0]);
476 break;
477
478 default:
5460cfeb
VS
479 if (grub_file_seek (file, 40) == (grub_off_t) -1
480 || grub_file_read (file, &head, sizeof (head)) != sizeof (head)
481 || grub_file_seek (file, 0) == (grub_off_t) -1
482 || head != 0x4856465f)
483 err = grub_error (GRUB_ERR_BAD_OS, "unrecognised payload type");
484 else
485 err = load_tianocore (file);
99c971af
VS
486 break;
487 }
488 grub_file_close (file);
489 if (err)
490 {
491 grub_relocator_unload (relocator);
492 relocator = 0;
493 return err;
494 }
495
ba7df45e
VS
496 grub_loader_set (grub_chain_boot, grub_chain_unload, 0);
497 return GRUB_ERR_NONE;
498}
499
500static grub_command_t cmd_chain;
501
502GRUB_MOD_INIT (chain)
503{
504 cmd_chain = grub_register_command ("chainloader", grub_cmd_chain,
9c4b5c13
VS
505 N_("FILE"),
506 /* TRANSLATORS: "payload" is a term used
507 by coreboot and must be translated in
508 sync with coreboot. If unsure,
509 let it untranslated. */
510 N_("Load another coreboot payload"));
ba7df45e
VS
511}
512
513GRUB_MOD_FINI (chain)
514{
515 grub_unregister_command (cmd_chain);
516 grub_chain_unload ();
517}