#1103692 syslinux-efi: 32-bit EFI mode doesn't work

#1103692#5
Date:
2025-04-20 17:56:31 UTC
From:
To:
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

#1103692#10
Date:
2025-04-20 18:53:33 UTC
From:
To:
Dear Maintainer,

Here is some additional information that I forgot to mention in the original
message:

  1. Whether or not the issue happens appears to be semi-random, e.g. it
doesn't
     happen on the x86_64 build, even though the .elf loading code is
the same,
     and the patch I've provided also updates it.

     It also didn't happen when I enabled global debugging by defining
     the CORE_DEBUG and DEBUG_STDIO macros on the 32-bit version, I've
     actually demonstrated that to Philipp Kern on the April 2025 Vienna
BSP.

  2. Speaking of the April 2025 Vienna BSP, I said there that the issue
     happened on the prebuilt binaries of syslinux 6.03 from kernel.org too,
     but that was because of incorrect testing due to the separate issue
that
     causes early error messages in EFI syslinux to be invisible.

  3. The issue also doesn't happen on the version of syslinux that's
currently
     in trixie, which appears to have been built back in 2020.

  4. The BSS section of an ELF file is not present on-disk, since it's
expected
     to be initialized to zero by the ELF loader.

  5. The elf loader in syslinux doesn't currently appear to be able to
handle
     zero-sized sections even if the integer underflow error isn't
triggered,
     it uses image_read() from com32/lib/sys/module/common.c which requires
     at least one byte to be loaded to report success.

  6. In cases when the error doesn't manifest, the loop that goes through
     the sections doesn't appear to come across an on-disk zero-sized
section
     with p_type == PT_LOAD.

     I presume there's some sort of section merging process going on, since
     objdump reports 17 sections to me, but the loop in load_segments()
     at line 112 of com32/lib/sys/module/i386/elf_module.c appears to only
     process 4 sections, where only the first 2 are p_type == PT_LOAD,
     and in the case when the error doesn't occur, there's only one
     p_type == PT_LOAD section, presumably .bss got merged with the previous
     sections in that case.

Best Regards,
Marek

#1103692#15
Date:
2025-04-25 19:08:51 UTC
From:
To:
Hello,

FIY, I've forwarded the patch to upstream:
https://www.syslinux.org/archives/2025-April/026927.html


Best Regards,
Marek