Package: syslinux-efi Version: 3:6.04~git20190206.bf6db5b4+dfsg1-3.1 Severity: important Tags: patch User: debian...@lists.debian.org Usertags: i386
Dear Maintainer, While testing syslinux after the FTBFS bugs #994274, #1057462, #1091027, I discovered that while the BIOS and amd64 EFI versions of the bootloader appear to work fine, the i386 EFI version does not work. Here are the steps to reproduce: 1. Create new disk image with GPT filesystem and EFI System Partition: # qemu-img create -f raw syslinux-test.img 64M # fdisk syslinux-test.img > g Created a new GPT disklabel (GUID: 19137FD7-7717-40DB-BC29-5D874A03D0C1). > n Partition number (1-128, default 1): First sector (2048-131038, default 2048): Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-131038, default 129023): Created a new partition 1 of type 'Linux filesystem' and of size 62 MiB. > t Selected partition 1 Partition type or alias (type L to list all): 1 Changed type of partition 'Linux filesystem' to 'EFI System'. > w The partition table has been altered. 2. Format EFI System partition as vfat, copy syslinux files: # modprobe nbd # qemu-nbd --connect=/dev/nbd0 --format=raw syslinux-test.img # mkfs.vfat /dev/nbd0p1 # mount /dev/nbd0p1 /mnt/misc # mkdir -p /mnt/misc/EFI/syslinux # cp /usr/lib/SYSLINUX.EFI/efi32/syslinux.efi /mnt/misc/EFI/syslinux/ # cp /usr/lib/syslinux/modules/efi32/ldlinux.e32 /mnt/misc/EFI/syslinux/ 3. Add 32-bit kernel/initrd files, create syslinux configuration: # mkdir /mnt/misc/boot # cp /path/to/vmlinuz /mnt/misc/boot/vmlinuz # cp /path/to/initrd.img /mnt/misc/boot/initrd.img # vi /mnt/misc/EFI/syslinux/syslinux.cfg PROMPT 1 TIMEOUT 50 DEFAULT linux LABEL linux KERNEL /boot/vmlinuz INITRD /boot/initrd.img 4. Use newly created image as qemu hard drive (with ovmf-ia32 firmware): # umount /mnt/misc # qemu-nbd -d /dev/nbd0 $ cp /usr/share/OVMF/OVMF32_CODE_4M.fd . $ cp /usr/share/OVMF/OVMF32_VARS_4M.fd . $ qemu-system-i386 -m 128 -net none \ -drive file=OVMF32_CODE_4M.fd,format=raw,if=pflash \ -drive file=OVMF32_VARS_4M.fd,format=raw,if=pflash \ -drive format=raw,file=syslinux-test.img \ -chardev file,id=char0,path=serial.dump -serial chardev:char0 Shell> FS0:\EFI\syslinux\syslinux.efi Expected outcome (upstream 6.03 build from kernel.org): syslinux starts up and allows me to boot the provided kernel/initrd files. Outcome with 3:6.04~git20190206.bf6db5b4+dfsg1-3.1 from Debian Unstable: syslinux moves the cursor on-screen and then returns back to the EFI Shell. I've spent some time debugging this, and ended up discovering that there is a separate bug with message generation in early EFI syslinux, I'll open a separate bug for that, but it turns out that a message actually is written in the case where it apparently just moves the cursor, it's just that the message has black-on-black coloring, thus being invisible. The message can be extracted from the serial line dump by passing it through ansi2txt: $ ansi2txt < serial.dump > serial.log Here, we see that the issue has to do with loading ldlinux.e32: $ tail -n4 serial.log Shell> FS0:\EFI\syslinux\syslinux.efi Failed to load ldlinux.e32 Shell> I've spent some time debugging this, and it turns out that there's a problem with the ELF loader where it doesn't handle zero-sized program segments correctly. While iterating through the program headers in the load_segments() function in com32/lib/sys/module/i386/elf_module.c, the code encounters a header with a target a target virtual address of 0x0001d000, a size of 0, and a p_offset (section offset within file) of 0. Looking at objdump, this is the .bss section, which means that the above information is valid: $ objdump -x efi32/com32/elflink/ldlinux/ldlinux.e32 ... Sections: Idx Name Size VMA LMA File off Algn 16 .bss 000030fc 0001d000 0001d000 0001d000 2**5 ALLOC Interestingly enough, objdump reports the section file offset as non-zero, so there has to be a difference between how the two ELF library implementations evaluate that field, but it doesn't matter, since according to the SystemV ABI document, chapter 5 [0], when there is a difference between the section size and its on-disk size, the amount of bytes that are specified in p_filesz are loaded from file:p_offset to module_base+p_vaddr and the rest is filled with zeroes. The ELF loading code in syslinux does initialize the memory to zero, so the only thing we need to do is handle situations when there is nothing to load from the section. I'm attaching a patch that adds two checks for this. The first check is a general "skip zero-sized sections" check, the second check is for the partial section load branch, where it checks to see if there is anything to load instead of just blindly subtracting an aux offset from the section size and potentially getting a integer underflow, resulting in a 4GiB load request (this is actually what happened). With the patch, it seems to work fine, and the bootloader successfully boots the provided kernel and initrd (TinyCore Linux in my testing). Best Regards, Marek [0]: https://refspecs.linuxbase.org/elf/gabi4+/ch5.pheader.html -- System Information: Debian Release: trixie/sid APT prefers testing APT policy: (500, 'testing') Architecture: amd64 (x86_64) Kernel: Linux 6.14.2 (SMP w/16 CPU threads; PREEMPT) Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE not set Shell: /bin/sh linked to /usr/bin/dash Init: systemd (via /run/systemd/system) LSM: AppArmor: enabled syslinux-efi depends on no packages. Versions of packages syslinux-efi recommends: ii syslinux-common 3:6.04~git20190206.bf6db5b4+dfsg1-3.1 Versions of packages syslinux-efi suggests: ii dosfstools 4.2-1.2 -- no debconf information
--- syslinux-6.04~git20190206.bf6db5b4+dfsg1.orig/com32/lib/sys/module/i386/elf_module.c +++ syslinux-6.04~git20190206.bf6db5b4+dfsg1/com32/lib/sys/module/i386/elf_module.c @@ -112,6 +112,14 @@ int load_segments(struct elf_module *mod for (i = 0; i < elf_hdr->e_phnum; i++) { cr_pht = (Elf32_Phdr*)(pht + i * elf_hdr->e_phentsize); + if (cr_pht->p_filesz == 0) + { + DBG_PRINT("Skipping loadable segment of zero size at vaddr 0x%08x at 0x%08x.\n" + cr_pht->p_vaddr, + (Elf32_Addr)module_get_absolute(cr_pht->p_vaddr, module)); + continue; + } + if (cr_pht->p_type == PT_LOAD) { // Copy the segment at its destination if (cr_pht->p_offset < module->u.l._cr_offset) { @@ -120,6 +128,16 @@ int load_segments(struct elf_module *mod // headers Elf32_Off aux_off = module->u.l._cr_offset - cr_pht->p_offset; + if (cr_pht->p_filesz <= aux_off) + { + DBG_PRINT("Skipping segment of size 0x%08x at vaddr 0x%08x at 0x%08x, " + "all data is before current offset.\n", + cr_pht->p_filesz, + cr_pht->p_vaddr, + (Elf32_Addr)module_get_absolute(cr_pht->p_vaddr, module)); + continue; + } + if (image_read((char *)module_get_absolute(cr_pht->p_vaddr, module) + aux_off, cr_pht->p_filesz - aux_off, module) < 0) { res = -1; --- syslinux-6.04~git20190206.bf6db5b4+dfsg1.orig/com32/lib/sys/module/x86_64/elf_module.c +++ syslinux-6.04~git20190206.bf6db5b4+dfsg1/com32/lib/sys/module/x86_64/elf_module.c @@ -112,6 +112,14 @@ int load_segments(struct elf_module *mod for (i = 0; i < elf_hdr->e_phnum; i++) { cr_pht = (Elf64_Phdr*)(pht + i * elf_hdr->e_phentsize); + if (cr_pht->p_filesz == 0) + { + DBG_PRINT("Skipping loadable segment of zero size at vaddr 0x%08x at 0x%08x.\n" + cr_pht->p_vaddr, + (Elf32_Addr)module_get_absolute(cr_pht->p_vaddr, module)); + continue; + } + if (cr_pht->p_type == PT_LOAD) { // Copy the segment at its destination if (cr_pht->p_offset < module->u.l._cr_offset) { @@ -120,6 +128,16 @@ int load_segments(struct elf_module *mod // headers Elf64_Off aux_off = module->u.l._cr_offset - cr_pht->p_offset; + if (cr_pht->p_filesz <= aux_off) + { + DBG_PRINT("Skipping segment of size 0x%08x at vaddr 0x%08x at 0x%08x, " + "all data is before current offset.\n", + cr_pht->p_filesz, + cr_pht->p_vaddr, + (Elf32_Addr)module_get_absolute(cr_pht->p_vaddr, module)); + continue; + } + if (image_read((char *)module_get_absolute(cr_pht->p_vaddr, module) + aux_off, cr_pht->p_filesz - aux_off, module) < 0) { res = -1;