[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v2] xen/x86: fix xen.efi boot crash from some bootloaders



xen.efi PE does not boot when loaded from shim or some patched
downstream grub2.

What happens is the bootloader would honour the MEM_DISCARDABLE
flag of the .reloc section meaning it would not load its content
into memory.

But Xen is parsing the .reloc section content twice at boot:
* https://elixir.bootlin.com/xen/v4.20.1/source/xen/common/efi/boot.c#L1362
* https://elixir.bootlin.com/xen/v4.20.1/source/xen/arch/x86/efi/efi-boot.h#L237

Therefore it would crash with the following message:
"Unsupported relocation type" as reported there:

* https://github.com/QubesOS/qubes-issues/issues/8206#issuecomment-2619048838
* 
https://lore.kernel.org/xen-devel/7e039262-1f54-46e1-8f70-ac3f03607d5a@xxxxxxxx/T/#me122b9e6c27cd98db917da2c9f67e74a2c6ad7a5

This commit adds a small C host tool named keeprelocs
that is called after xen.efi is produced by the build system
in order to remove this bit from its .reloc section header.

Signed-off-by: Yann Sionneau <yann.sionneau@xxxxxxxxxx>
---
Changes since v1:
- use xen coding style instead of Linux kernel one
- use void * to store the return value from mmap()
- PE file is passed as non-optional argument instead of -i FILE
- use bool instead of int type for "quiet" flag
- remove useless break after return
- add missing 0x before %08x conversion specifier
- add SPDX header
- remove DEBUG stuff
- improve opt_hdr_size checks
- removed useless tools_keeprelocs Makefile target
- fixed -I include path for keeprelocs tool: use $(srctree)
- use memcmp instead of strncmp
- produce an intermediate xen.efi.1 before running keeprelocs
  so that an interrupted build would not end up having a bad
  xen.efi (with MEM_DISCARDABLE flag in .reloc section).
  In other words: make sure the final xen.efi is produced only
  after all modifications are done.

Link to v1: 
https://lore.kernel.org/xen-devel/20250723135620.1327914-1-yann.sionneau@xxxxxxxxxx/
---
 xen/arch/x86/Makefile  |   6 +-
 xen/tools/Makefile     |   3 +
 xen/tools/keeprelocs.c | 165 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 172 insertions(+), 2 deletions(-)
 create mode 100644 xen/tools/keeprelocs.c

diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
index ce724a9daa..91af6ae214 100644
--- a/xen/arch/x86/Makefile
+++ b/xen/arch/x86/Makefile
@@ -232,10 +232,12 @@ endif
        $(MAKE) $(build)=$(@D) .$(@F).1r.o .$(@F).1s.o
        $(LD) $(call EFI_LDFLAGS,$(VIRT_BASE)) -T $(obj)/efi.lds $< \
              $(dot-target).1r.o $(dot-target).1s.o $(orphan-handling-y) \
-             $(note_file_option) -o $@
-       $(NM) -pa --format=sysv $@ \
+             $(note_file_option) -o $@.1
+       $(NM) -pa --format=sysv $@.1 \
                | $(objtree)/tools/symbols --all-symbols --xensyms --sysv 
--sort \
                > $@.map
+       $(objtree)/tools/keeprelocs -q $@.1
+       mv $@.1 $@
 ifeq ($(CONFIG_DEBUG_INFO),y)
        $(if $(filter --strip-debug,$(EFI_LDFLAGS)),:$(space))$(OBJCOPY) -O 
elf64-x86-64 $@ $@.elf
 endif
diff --git a/xen/tools/Makefile b/xen/tools/Makefile
index a5078b7cb8..620063ab1e 100644
--- a/xen/tools/Makefile
+++ b/xen/tools/Makefile
@@ -1,2 +1,5 @@
 hostprogs-always-y += symbols
 hostprogs-always-y += fixdep
+hostprogs-always-$(XEN_BUILD_PE) += keeprelocs
+# next line is to allow including include/efi/pe.h
+HOSTCFLAGS_keeprelocs.o := -I $(srctree)/include
\ No newline at end of file
diff --git a/xen/tools/keeprelocs.c b/xen/tools/keeprelocs.c
new file mode 100644
index 0000000000..284378564a
--- /dev/null
+++ b/xen/tools/keeprelocs.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <efi/pe.h>
+
+static void print_usage(const char *name)
+{
+    printf("%s: [-q] [-h] xen.efi\n", name);
+}
+
+static int get_minimum_opt_hdr_size(uint16_t opt_hdr_magic)
+{
+    switch ( opt_hdr_magic )
+    {
+        case PE_OPT_MAGIC_PE32:
+            return sizeof(struct pe32_opt_hdr);
+        case PE_OPT_MAGIC_PE32PLUS:
+            return sizeof(struct pe32plus_opt_hdr);
+        default:
+            return -1;
+    }
+}
+
+int main(int argc, char **argv)
+{
+    char *filename = NULL;
+    int fd;
+    void *mem;
+    struct stat st;
+    off_t len;
+    int ret;
+    struct mz_hdr *mz;
+    struct pe_hdr *pe;
+    int opt;
+    const char *prog_name = argv[0];
+    bool quiet = false;
+    int min_opt_hdr_size;
+    unsigned int section_id;
+
+    while ( (opt = getopt(argc, argv, ":qh")) != -1 )
+    {
+        switch ( opt )
+        {
+            case 'q':
+                quiet = true;
+                break;
+            case 'h':
+                print_usage(prog_name);
+                return 0;
+            case '?':
+            default:
+                print_usage(prog_name);
+                return -1;
+            }
+    }
+
+    if ( optind < argc )
+        filename = argv[optind++];
+    else
+    {
+        printf("Error: you must provide the PE file name as first and only 
non-optional argument\n");
+        return -1;
+    }
+
+    if ( optind != argc )
+    {
+        printf("Only one non-optional argument should be supplied: the PE file 
name\n");
+        return -1;
+    }
+
+    fd = open(filename, O_RDWR);
+    if ( fd < 0 )
+    {
+        printf("Could not open file %s: %s\n", filename, strerror(errno));
+        return -1;
+    }
+
+    ret = fstat(fd, &st);
+    if ( ret < 0 )
+    {
+        perror("Error while getting PE file length");
+        return -1;
+    }
+
+    len = st.st_size;
+    mem = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+    if ( mem == MAP_FAILED )
+    {
+        perror("Failed to mmap PE file");
+        return -1;
+    }
+
+    mz = mem;
+    if ( mz->magic != MZ_MAGIC )
+    {
+        printf("file has incorrect MZ header 0x%02x instead of 0x5a4d\n",
+                mz->magic);
+        return -1;
+    }
+
+    pe = mem + mz->peaddr;
+    if ( memcmp((char *)&pe->magic, "PE\0\0", 4) )
+    {
+        printf("file has incorrect PE header magic 0x%08x instead of 
0x00004550\n",
+                pe->magic);
+        return -1;
+    }
+
+    if ( pe->opt_hdr_size < 2 )
+    {
+        printf("file has too small OptionalHeaderSize: %d\n", 
pe->opt_hdr_size);
+        return -1;
+    }
+
+    /* Compute minimum optional header size, based on its
+     * first field (uint16_t magic).
+     */
+    min_opt_hdr_size = get_minimum_opt_hdr_size(*(uint16_t *)((void *)pe
+                                                + sizeof(*pe)));
+    if ( min_opt_hdr_size < 0 )
+    {
+        printf("Incorrect binary type, should be PE32 or PE32+\n");
+        return -1;
+    }
+
+    if ( pe->opt_hdr_size < min_opt_hdr_size )
+    {
+        printf("file has too small OptionalHeaderSize: %d\n", 
pe->opt_hdr_size);
+        return -1;
+    }
+
+    struct section_header *section = (struct section_header *)((uint8_t *)pe
+                                     + sizeof(*pe) + pe->opt_hdr_size);
+    for ( section_id = 0; section_id < pe->sections; section_id++, section++ )
+    {
+        if ( memcmp(section->name, ".reloc", strlen(".reloc") + 1) )
+            continue;
+
+        if ( !quiet )
+            printf(".reloc section characteristics: %08x\n", section->flags);
+        if ( section->flags & IMAGE_SCN_MEM_DISCARDABLE )
+        {
+            if ( !quiet )
+                printf("MEM_DISCARDABLE flag found! Dropping it.\n");
+            section->flags &= ~(IMAGE_SCN_MEM_DISCARDABLE);
+        }
+    }
+
+    munmap(mem, len);
+    close(fd);
+
+    if ( !quiet )
+        printf("Ok!\n");
+    return 0;
+}
-- 
2.43.0



Yann Sionneau | Vates XCP-ng Developer

XCP-ng & Xen Orchestra - Vates solutions

web: https://vates.tech




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.