[PATCH] libelf: handle PN_XNUM in elf_getphdrnum before shdr 0 is cached

Omar Sandoval osandov@osandov.com
Sat Mar 21 18:21:36 GMT 2020


On Sat, Mar 21, 2020 at 01:30:55AM +0100, Mark Wielaard wrote:
> Hi Omar,
> 
> On Wed, Mar 18, 2020 at 01:18:51PM -0700, Omar Sandoval wrote:
> > __elf_getphdrnum_rdlock() handles PN_XNUM by getting sh_info from
> > elf->state.elf{32,64}.scns.data[0].shdr.e{32,64}. However, that is only
> > a cache that may or may not have been populated by elf_begin() or
> > elf{32,64}_getshdr(); if it hasn't been cached yet, elf_getphdrnum()
> > returns 65535 (the value of PN_XNUM) instead. We should explicitly get
> > the shdr if it isn't cached.
> 
> I believe this analysis is correct. But how did you find this?  This
> seems to only happen if e_phnum was PN_XNUM and for some reason the
> scns cache wasn't initialized. Do you happen to have a testcase?

I encountered this in drgn on a vmcore for a large server created by
makedumpfile, but I was able to put together a minimal reproducer.
Generate the ELF file with this python script:

---
#!/usr/bin/env python3

import struct
import sys

phnum = 66000

sys.stdout.buffer.write(
    struct.pack(
        "<16BHHIQQQIHHHHHH",
        # EI_MAG
        *b"\x7fELF",
        # EI_CLASS = ELFCLASS64
        2,
        # EI_DATA = ELFDATA2LSB
        1,
        # EI_VERSION
        1,
        # EI_OSABI = ELFOSABI_SYSV
        0,
        # EI_ABIVERSION
        0,
        # EI_PAD
        *bytes(7),
        # e_type = ET_CORE
        4,
        # e_machine = EM_X86_64
        62,
        # e_version
        1,
        # e_entry
        0,
        # e_phoff = sizeof(Elf64_Ehdr) + sizeof(Elf64_Shdr)
        128,
        # e_shoff = sizeof(Elf64_Ehdr)
        64,
        # e_flags
        0,
        # e_ehsize
        64,
        # e_phentsize
        56,
        # e_phnum = PN_XNUM
        0xFFFF,
        # e_shentsize
        64,
        # e_shnum
        1,
        # e_shstrndx
        0,
    )
)

sys.stdout.buffer.write(
    struct.pack(
        "<IIQQQQIIQQ",
        # sh_name
        0,
        # sh_type = SHT_NULL
        0,
        # sh_flags
        0,
        # sh_addr
        0,
        # sh_offset
        0,
        # sh_size
        0,
        # sh_link
        0,
        # sh_info
        phnum,
        # sh_addralign
        0,
        # sh_entsize
        0,
    )
)

for i in range(phnum):
    sys.stdout.buffer.write(
        struct.pack(
            "<IIQQQQQQ",
            # p_type = PT_LOAD
            1,
            # p_flags = PF_X|PF_W|PF_R
            0x7,
            # p_offset
            0,
            # p_vaddr
            i * 4096,
            # p_paddr
            0,
            # p_filesz
            0,
            # p_memsz
            4096,
            # p_align
            0,
        )
    )
---

And run this program:

---
#include <elfutils/libdwelf.h>
#include <fcntl.h>
#include <libelf.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(int argc, const char **argv)
{
	int fd;
	Elf *elf;
	size_t phnum;

	if (argc != 2) {
		fprintf(stderr, "usage: %s FILE\n", argv[0]);
		return EXIT_FAILURE;
	}

	fd = open(argv[1], O_RDONLY);
	if (fd == -1) {
		perror("open");
		return EXIT_FAILURE;
	}
	elf_version(EV_CURRENT);
	elf = elf_begin(fd, ELF_C_READ, NULL);
	if (!elf) {
		fprintf(stderr, "elf_begin: %s\n", elf_errmsg(-1));
		return EXIT_FAILURE;
	}
	if (elf_getphdrnum(elf, &phnum)) {
		fprintf(stderr, "elf_getphdrnum: %s\n", elf_errmsg(-1));
		return EXIT_FAILURE;
	}
	printf("%zu\n", phnum);
	return EXIT_SUCCESS;
}
---

This should output 66000, but it outputs 65535 instead.

Looking at file_read_elf, the cache is only initialized from elf_begin
from ELF_C_RDWR_MMAP and ELF_C_READ_MMAP_PRIVATE as long as endianness
matches the host and the section headers are properly aligned:

      if (map_address != NULL && e_ident[EI_DATA] == MY_ELFDATA
	  && cmd != ELF_C_READ_MMAP /* We need a copy to be able to write.  */
	  && (ALLOW_UNALIGNED
	      || (((uintptr_t) ((char *) ehdr + e_shoff)
		   & (__alignof__ (Elf64_Shdr) - 1)) == 0)))


More information about the Elfutils-devel mailing list