[Bug 206749] Lack of checks on values in ELF headers in kernel linker

bugzilla-noreply at freebsd.org bugzilla-noreply at freebsd.org
Fri Jan 29 21:51:31 UTC 2016


https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=206749

            Bug ID: 206749
           Summary: Lack of checks on values in ELF headers in kernel
                    linker
           Product: Base System
           Version: 11.0-CURRENT
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Some People
          Priority: ---
         Component: kern
          Assignee: freebsd-bugs at FreeBSD.org
          Reporter: cturt at hardenedbsd.org

There are a lack of checks performed on the `e_shnum` and `e_shentsize` values
read from input files in the kernel's ELF handling code.

For example, in `link_elf_ctf_get`:

static int
link_elf_ctf_get(linker_file_t lf, linker_ctf_t *lc)
{
        ...
        int nbytes;

        ...

        /* Allocate memory for the FLF header. */
        if ((hdr = malloc(sizeof(*hdr), M_LINKER, M_WAITOK)) == NULL) {
                error = ENOMEM;
                goto out;
        }

        /* Read the ELF header. */
        if ((error = vn_rdwr(UIO_READ, nd.ni_vp, hdr, sizeof(*hdr),
            0, UIO_SYSSPACE, IO_NODELOCKED, td->td_ucred, NOCRED, &resid,
            td)) != 0)
                goto out;

        /* Sanity check. */
        if (!IS_ELF(*hdr)) {
                error = ENOEXEC;
                goto out;
        }

        nbytes = hdr->e_shnum * hdr->e_shentsize;
        if (nbytes == 0 || hdr->e_shoff == 0 ||
            hdr->e_shentsize != sizeof(Elf_Shdr)) {
                error = ENOEXEC;
                goto out;
        }

        /* Allocate memory for all the section headers */
        if ((shdr = malloc(nbytes, M_LINKER, M_WAITOK)) == NULL) {
                error = ENOMEM;
                goto out;
        }

        ...
}

The `e_shnum` and `e_shentsize` fields are declared as `Elf32_Half` (a
`typedef` for `uint16_t`) in the `Elf_Ehdr` structure (`sys/sys/elf32.h`):

           typedef struct {
                   unsigned char   e_ident[EI_NIDENT];
                   Elf32_Half      e_type;
                   Elf32_Half      e_machine;
                   Elf32_Word      e_version;
                   Elf32_Addr      e_entry;
                   Elf32_Off       e_phoff;
                   Elf32_Off       e_shoff;
                   Elf32_Word      e_flags;
                   Elf32_Half      e_ehsize;
                   Elf32_Half      e_phentsize;
                   Elf32_Half      e_phnum;
                   Elf32_Half      e_shentsize;
                   Elf32_Half      e_shnum;
                   Elf32_Half      e_shstrndx;
           } Elf32_Ehdr;

So consider if `0xffff` and `0x8001` are supplied for `e_shentsize` and
`e_shnum`:

    nbytes = hdr->e_shnum * hdr->e_shentsize;
    nbytes = 0xffff * 0x8001 = 0x80007FFF;

Since `nbytes` is `signed`, this will of course wrap around to `-0x7FFF8001`.

When `-0x7FFF8001` is passed to `malloc` it will be converted to an unsigned
64bit integer, giving an oversized allocation of `0xFFFFFFFF80007FFF`.

This poses no immediate security threat since the result from `malloc` is
checked, and will return `ENOMEM` for an overflown size.

This bug only applies to the `e_shnum` and `e_shentsize` members; the `e_phnum`
and `e_phentsize` members are correctly checked multiple times before use:

        if (!((hdr->e_phentsize == sizeof(Elf_Phdr)) &&
              (hdr->e_phoff + hdr->e_phnum*sizeof(Elf_Phdr) <= PAGE_SIZE) &&
              (hdr->e_phoff + hdr->e_phnum*sizeof(Elf_Phdr) <= nbytes)))
                link_elf_error(filename, "Unreadable program headers");

        ...

        /*    (multiplication of two Elf_Half fields will not overflow) */
        if ((hdr->e_phoff > PAGE_SIZE) ||
            (hdr->e_phentsize * hdr->e_phnum) > PAGE_SIZE - hdr->e_phoff) {
                error = ENOEXEC;
                goto fail;
        }

However, the same code to handle `e_shnum` and `e_shentsize` is reused multiple
times, and so multiple functions have the same bug, for example
`link_elf_load_file` in `sys/kern/link_elf.c`:

static int
link_elf_load_file(linker_class_t cls, const char* filename,
    linker_file_t* result)
{
        ...
        int nbytes;

        ...

        /*
         * Read the elf header from the file.
         */
        firstpage = malloc(PAGE_SIZE, M_LINKER, M_WAITOK);
        hdr = (Elf_Ehdr *)firstpage;
        error = vn_rdwr(UIO_READ, nd.ni_vp, firstpage, PAGE_SIZE, 0,
            UIO_SYSSPACE, IO_NODELOCKED, td->td_ucred, NOCRED,
            &resid, td);

        ...

        /*
         * Try and load the symbol table if it's present.  (you can
         * strip it!)
         */
        nbytes = hdr->e_shnum * hdr->e_shentsize;
        if (nbytes == 0 || hdr->e_shoff == 0)
                goto nosyms;
        shdr = malloc(nbytes, M_LINKER, M_WAITOK | Me_shnum_ZERO);
        error = vn_rdwr(UIO_READ, nd.ni_vp,
            (caddr_t)shdr, nbytes, hdr->e_shoff,
            UIO_SYSSPACE, IO_NODELOCKED, td->td_ucred, NOCRED,
            &resid, td);
        if (error != 0)
                goto out;
        symtabindex = -1;
        symstrindex = -1;
        for (i = 0; i < hdr->e_shnum; i++) {
                if (shdr[i].sh_type == SHT_SYMTAB) {
                        symtabindex = i;
                        symstrindex = shdr[i].sh_link;
                }
        }e_shnum

        ...
}

And `link_elf_load_file` from `sys/kern/link_elf_obj.c`.

-- 
You are receiving this mail because:
You are the assignee for the bug.


More information about the freebsd-bugs mailing list