diff --git a/grub-core/loader/efi/linux.c b/grub-core/loader/efi/linux.c index 99795c1e..0e785c58 100644 --- a/grub-core/loader/efi/linux.c +++ b/grub-core/loader/efi/linux.c @@ -41,6 +41,8 @@ GRUB_MOD_LICENSE ("GPLv3+"); static grub_dl_t my_mod; static int loaded; +static void *kernel_alloc_addr; +static grub_uint32_t kernel_alloc_pages; static void *kernel_addr; static grub_uint64_t kernel_size; static grub_uint32_t handover_offset; @@ -252,9 +254,8 @@ grub_linux_unload (void) GRUB_EFI_BYTES_TO_PAGES (initrd_end - initrd_start)); initrd_start = initrd_end = 0; grub_free (linux_args); - if (kernel_addr) - grub_efi_free_pages ((grub_addr_t) kernel_addr, - GRUB_EFI_BYTES_TO_PAGES (kernel_size)); + if (kernel_alloc_addr) + grub_efi_free_pages ((grub_addr_t) kernel_alloc_addr, kernel_alloc_pages); grub_fdt_unload (); return GRUB_ERR_NONE; } @@ -438,14 +439,35 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), return grub_errno; } +static grub_err_t +parse_pe_header (void *kernel, grub_uint64_t *total_size, + grub_uint32_t *entry_offset, + grub_uint32_t *alignment) +{ + struct linux_arch_kernel_header *lh = kernel; + struct grub_arm64_linux_pe_header *pe; + + pe = (void *)((unsigned long)kernel + lh->hdr_offset); + + if (pe->opt.magic != GRUB_PE32_PE64_MAGIC) + return grub_error(GRUB_ERR_BAD_OS, "Invalid PE optional header magic"); + + *total_size = pe->opt.image_size; + *entry_offset = pe->opt.entry_addr; + *alignment = pe->opt.section_alignment; + + return GRUB_ERR_NONE; +} + static grub_err_t grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { grub_file_t file = 0; - struct linux_arch_kernel_header lh; - struct grub_arm64_linux_pe_header *pe; grub_err_t err; + grub_off_t filelen; + grub_uint32_t align = 0; + void *kernel = NULL; grub_dl_ref (my_mod); @@ -459,39 +481,47 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), if (!file) goto fail; - kernel_size = grub_file_size (file); + filelen = grub_file_size (file); + kernel = grub_malloc(filelen); + if (!kernel) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("cannot allocate kernel load buffer")); + goto fail; + } - if (grub_file_read (file, &lh, sizeof (lh)) < (long) sizeof (lh)) - return grub_errno; + if (grub_file_read (file, kernel, filelen) < (grub_ssize_t)filelen) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, N_("Can't read kernel %s"), + argv[0]); + goto fail; + } - if (grub_arch_efi_linux_check_image (&lh) != GRUB_ERR_NONE) + if (grub_arch_efi_linux_check_image (kernel) != GRUB_ERR_NONE) + goto fail; + if (parse_pe_header (kernel, &kernel_size, &handover_offset, &align) != GRUB_ERR_NONE) goto fail; + grub_dprintf ("linux", "kernel mem size : %lld\n", (long long) kernel_size); + grub_dprintf ("linux", "kernel entry offset : %d\n", handover_offset); + grub_dprintf ("linux", "kernel alignment : 0x%x\n", align); grub_loader_unset(); - grub_dprintf ("linux", "kernel file size: %lld\n", (long long) kernel_size); - kernel_addr = grub_efi_allocate_any_pages (GRUB_EFI_BYTES_TO_PAGES (kernel_size)); - grub_dprintf ("linux", "kernel numpages: %lld\n", - (long long) GRUB_EFI_BYTES_TO_PAGES (kernel_size)); - if (!kernel_addr) + kernel_alloc_pages = GRUB_EFI_BYTES_TO_PAGES (kernel_size + align - 1); + kernel_alloc_addr = grub_efi_allocate_any_pages (kernel_alloc_pages); + grub_dprintf ("linux", "kernel numpages: %d\n", kernel_alloc_pages); + if (!kernel_alloc_addr) { grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory")); goto fail; } - - grub_file_seek (file, 0); - if (grub_file_read (file, kernel_addr, kernel_size) - < (grub_int64_t) kernel_size) - { - if (!grub_errno) - grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), argv[0]); - goto fail; - } + kernel_addr = (void *)ALIGN_UP((grub_uint64_t)kernel_alloc_addr, align); grub_dprintf ("linux", "kernel @ %p\n", kernel_addr); - - pe = (void *)((unsigned long)kernel_addr + lh.hdr_offset); - handover_offset = pe->opt.entry_addr; + grub_memcpy (kernel_addr, kernel, grub_min(filelen, kernel_size)); + if (kernel_size > filelen) + grub_memset ((char *)kernel_addr + filelen, 0, kernel_size - filelen); + grub_free(kernel); + kernel = NULL; cmdline_size = grub_loader_cmdline_size (argc, argv) + sizeof (LINUX_IMAGE); linux_args = grub_malloc (cmdline_size); @@ -515,6 +545,9 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), } fail: + if (kernel) + grub_free (kernel); + if (file) grub_file_close (file); @@ -527,9 +560,8 @@ fail: if (linux_args && !loaded) grub_free (linux_args); - if (kernel_addr && !loaded) - grub_efi_free_pages ((grub_addr_t) kernel_addr, - GRUB_EFI_BYTES_TO_PAGES (kernel_size)); + if (kernel_alloc_addr && !loaded) + grub_efi_free_pages ((grub_addr_t) kernel_alloc_addr, kernel_alloc_pages); return grub_errno; }