CVE-2014-9620 (C, published 2015-01-21T18:59Z)
Description: value: The ELF parser in file 5.08 through 5.21 allows remote attackers to cause a denial of service via a large number of notes.
Severity: MEDIUM (CVSS v3 base score: )
Weakness: CWE-399 \u2014 Resource Management Errors
Weaknesses in this category are related to improper management of system resources.
Repository: https://github.com/file/file
Files changed:
ChangeLog
doc/file.man
doc/libmagic.man
src/apprentice.c
src/elfclass.h
src/file.c
src/file.h
src/file_opts.h
src/magic.c
src/magic.h.in
src/readelf.c
Commit message: - Add a limit to the number of ELF notes processed (Suggested by Alexander
  Cherepanov)
- Restructure ELF note printing so that we don't print the same message
  multiple times on repeated notes of the same kind.


Vulnerable code:
.\" $File: file.man,v 1.109 2014/11/28 02:35:05 christos Exp $
.Dd November 27, 2014
.\" $File: libmagic.man,v 1.32 2014/11/28 02:35:05 christos Exp $
.Dd November 27, 2014
parameter controls how many elf program sections will be processed.
parameter controls how many elf sections will be processed.
FILE_RCSID("@(#)$File: apprentice.c,v 1.226 2014/11/28 02:35:05 christos Exp $")
			return toomany(ms, "program", phnum);
		    fsize, &flags) == -1)
		    fsize, &flags, shnum) == -1)
			return toomany(ms, "section", shnum);
		    fsize, &flags, elf_getu16(swap, elfhdr.e_machine),
		    (int)elf_getu16(swap, elfhdr.e_shstrndx)) == -1)
FILE_RCSID("@(#)$File: file.c,v 1.158 2014/11/28 02:35:05 christos Exp $")
 * @(#)$File: file.h,v 1.161 2014/12/04 15:56:46 christos Exp $
FILE_RCSID("@(#)$File: magic.c,v 1.89 2014/11/28 02:46:39 christos Exp $")
FILE_RCSID("@(#)$File: readelf.c,v 1.114 2014/12/11 14:19:36 christos Exp $")
    off_t, int *);
    off_t, int *, int);
    off_t, int *, int, int);
    int, size_t, int *);
	if (file_printf(ms, ", too many %s header sections (%u)", name, num
#define FLAGS_DID_CORE		0x01
#define FLAGS_DID_NOTE		0x02
#define FLAGS_DID_BUILD_ID	0x04
#define FLAGS_DID_CORE_STYLE	0x08
#define FLAGS_IS_CORE		0x10
    int num, size_t size, off_t fsize, int *flags)
			    clazz, swap, 4, flags);
private size_t
donote(struct magic_set *ms, void *vbuf, size_t offset, size_t size,
    int clazz, int swap, size_t align, int *flags)
	Elf32_Nhdr nh32;
	Elf64_Nhdr nh64;
	size_t noff, doff;
#ifdef ELFCORE
	int os_style = -1;
#endif
	uint32_t namesz, descsz;
	unsigned char *nbuf = CAST(unsigned char *, vbuf);
	char sbuf[512];

	if (xnh_sizeof + offset > size) {
		/*
		 * We're out of note headers.
		 */
		return xnh_sizeof + offset;
	}

	(void)memcpy(xnh_addr, &nbuf[offset], xnh_sizeof);
	offset += xnh_sizeof;

	namesz = xnh_namesz;
	descsz = xnh_descsz;
	if ((namesz == 0) && (descsz == 0)) {
		/*
		 * We're out of note headers.
		 */
		return (offset >= size) ? offset : size;
	}

	if (namesz & 0x80000000) {
	    (void)file_printf(ms, ", bad note name size 0x%lx",
		(unsigned long)namesz);
	    return 0;
	}

	if (descsz & 0x80000000) {
	    (void)file_printf(ms, ", bad note description size 0x%lx",
		(unsigned long)descsz);
	    return 0;
	}


	noff = offset;
	doff = ELF_ALIGN(offset + namesz);

	if (offset + namesz > size) {
		/*
		 * We're past the end of the buffer.
		 */
		return doff;
	}

	offset = ELF_ALIGN(doff + descsz);
	if (doff + descsz > size) {
		/*
		 * We're past the end of the buffer.
		 */
		return (offset >= size) ? offset : size;
	if ((*flags & (FLAGS_DID_NOTE|FLAGS_DID_BUILD_ID)) ==
	    (FLAGS_DID_NOTE|FLAGS_DID_BUILD_ID))
		goto core;

	    xnh_type == NT_GNU_VERSION && descsz == 2) {
	    xnh_type == NT_GNU_VERSION && descsz == 16) {
			return size;
				return size;
				return size;
				return size;
				return size;
				return size;
				return size; 
			return size;
		*flags |= FLAGS_DID_NOTE;
		return size;
	if (namesz == 4 && strcmp((char *)&nbuf[noff], "GNU") == 0 &&
	    xnh_type == NT_GNU_BUILD_ID && (descsz == 16 || descsz == 20)) {
	    uint8_t desc[20];
	    uint32_t i;
	    if (file_printf(ms, ", BuildID[%s]=", descsz == 16 ? "md5/uuid" :
		"sha1") == -1)
		    return size;
	    (void)memcpy(desc, &nbuf[doff], descsz);
	    for (i = 0; i < descsz; i++)
		if (file_printf(ms, "%02x", desc[i]) == -1)
		    return size;
	    *flags |= FLAGS_DID_BUILD_ID;
	    xnh_type == NT_NETBSD_PAX && descsz == 4) {
			return size;
				return size;
		}
	}

	if (namesz == 7 && strcmp((char *)&nbuf[noff], "NetBSD") == 0) {
		switch (xnh_type) {
		case NT_NETBSD_VERSION:
			if (descsz == 4) {
				do_note_netbsd_version(ms, swap, &nbuf[doff]);
				*flags |= FLAGS_DID_NOTE;
				return size;
			}
			break;
		case NT_NETBSD_MARCH:
			if (file_printf(ms, ", compiled for: %.*s", (int)descsz,
			    (const char *)&nbuf[doff]) == -1)
				return size;
			break;
		case NT_NETBSD_CMODEL:
			if (file_printf(ms, ", compiler model: %.*s",
			    (int)descsz, (const char *)&nbuf[doff]) == -1)
				return size;
			break;
		default:
			if (file_printf(ms, ", note=%u", xnh_type) == -1)
				return size;
			break;
		}
		return size;
	}

	if (namesz == 8 && strcmp((char *)&nbuf[noff], "FreeBSD") == 0) {
	    	if (xnh_type == NT_FREEBSD_VERSION && descsz == 4) {
			do_note_freebsd_version(ms, swap, &nbuf[doff]);
			*flags |= FLAGS_DID_NOTE;
			return size;
	if (namesz == 8 && strcmp((char *)&nbuf[noff], "OpenBSD") == 0 &&
	    xnh_type == NT_OPENBSD_VERSION && descsz == 4) {
		if (file_printf(ms, ", for OpenBSD") == -1)
			return size;
		/* Content of note is always 0 */
		*flags |= FLAGS_DID_NOTE;
		return size;
	}

	if (namesz == 10 && strcmp((char *)&nbuf[noff], "DragonFly") == 0 &&
	    xnh_type == NT_DRAGONFLY_VERSION && descsz == 4) {
		uint32_t desc;
		if (file_printf(ms, ", for DragonFly") == -1)
			return size;
		(void)memcpy(&desc, &nbuf[doff], sizeof(desc));
		desc = elf_getu32(swap, desc);
		if (file_printf(ms, " %d.%d.%d", desc / 100000,
		    desc / 10000 % 10, desc % 10000) == -1)
			return size;
		*flags |= FLAGS_DID_NOTE;
		return size;
	}

core:
#ifdef ELFCORE
	if ((*flags & FLAGS_DID_CORE) != 0)
		return size;

			return size;
		if (xnh_type == NT_NETBSD_CORE_PROCINFO) {
				return size;
				return size;
			return size;
		if (xnh_type == NT_PRPSINFO && *flags & FLAGS_IS_CORE) {
				for (k = i + 1 ; k < NOFFSETS ; k++) {
					return size;
				return size;
    size_t size, off_t fsize, int *flags, int mach, int strtab)
				    xsh_size, clazz, swap, 4, flags);
    int num, size_t size, off_t fsize, int *flags, int sh_num)
				    flags);
	uint16_t type, phnum, shnum;

Fixed code:
2014-12-16  18:10  Christos Zoulas <christos@zoulas.com>
	
	* restructure elf note printing to avoid repeated messages
	* add note limit, suggested by Alexander Cherepanov

.\" $File: file.man,v 1.110 2014/11/28 02:46:39 christos Exp $
.Dd December 16, 2014
.It Li elf_notes Ta 256 Ta max ELF notes processed
.\" $File: libmagic.man,v 1.33 2014/11/28 02:46:39 christos Exp $
.Dd December 16, 2014
.It Li MAGIC_PARAM_ELF_NOTES_MAX Ta size_t Ta 256
.Dv MAGIC_PARAM_NOTES_MAX
parameter controls how many ELF notes will be processed.
.Pp
The
parameter controls how many ELF program sections will be processed.
parameter controls how many ELF sections will be processed.
FILE_RCSID("@(#)$File: apprentice.c,v 1.227 2014/11/28 02:46:39 christos Exp $")
	ms->elf_notes_max = FILE_ELF_NOTES_MAX;
	notecount = ms->elf_notes_max;
			return toomany(ms, "program headers", phnum);
		    fsize, &flags, &notecount) == -1)
		    fsize, shnum, &flags, &notecount) == -1)
			return toomany(ms, "section headers", shnum);
		    fsize, elf_getu16(swap, elfhdr.e_machine),
		    (int)elf_getu16(swap, elfhdr.e_shstrndx),
		    &flags, &notecount) == -1)
	if (notecount == 0)
		return toomany(ms, "notes", ms->elf_notes_max);
FILE_RCSID("@(#)$File: file.c,v 1.159 2014/11/28 02:46:39 christos Exp $")
	{ "elf_notes",	MAGIC_PARAM_ELF_NOTES_MAX, 0 },
 * @(#)$File: file.h,v 1.162 2014/12/11 12:34:24 christos Exp $
	uint16_t elf_notes_max;
#define	FILE_ELF_NOTES_MAX		256
    "                               elf_notes   256 max ELF notes processed\n"
FILE_RCSID("@(#)$File: magic.c,v 1.90 2014/12/04 15:56:46 christos Exp $")
	case MAGIC_PARAM_ELF_NOTES_MAX:
		ms->elf_notes_max = *(const size_t *)val;
		return 0;
	case MAGIC_PARAM_ELF_NOTES_MAX:
		*(size_t *)val = ms->elf_notes_max;
		return 0;
#define MAGIC_PARAM_ELF_NOTES_MAX	4
FILE_RCSID("@(#)$File: readelf.c,v 1.115 2014/12/16 20:53:05 christos Exp $")
    off_t, int *, uint16_t *);
    off_t, int, int *, uint16_t *);
    off_t, int, int, int *, uint16_t *);
    int, size_t, int *, uint16_t *);
	if (file_printf(ms, ", too many %s (%u)", name, num
#define FLAGS_DID_CORE			0x001
#define FLAGS_DID_OS_NOTE		0x002
#define FLAGS_DID_BUILD_ID		0x004
#define FLAGS_DID_CORE_STYLE		0x008
#define FLAGS_DID_NETBSD_PAX		0x010
#define FLAGS_DID_NETBSD_MARCH		0x020
#define FLAGS_DID_NETBSD_CMODEL		0x040
#define FLAGS_DID_NETBSD_UNKNOWN	0x080
#define FLAGS_IS_CORE			0x100
    int num, size_t size, off_t fsize, int *flags, uint16_t *notecount)
			    clazz, swap, 4, flags, notecount);
private int
do_bid_note(struct magic_set *ms, unsigned char *nbuf, uint32_t type,
    int swap __attribute__((__unused__)), uint32_t namesz, uint32_t descsz,
    size_t noff, size_t doff, int *flags)
	if (namesz == 4 && strcmp((char *)&nbuf[noff], "GNU") == 0 &&
	    type == NT_GNU_BUILD_ID && (descsz == 16 || descsz == 20)) {
		uint8_t desc[20];
		uint32_t i;
		*flags |= FLAGS_DID_BUILD_ID;
		if (file_printf(ms, ", BuildID[%s]=", descsz == 16 ? "md5/uuid" :
		    "sha1") == -1)
			return 1;
		(void)memcpy(desc, &nbuf[doff], descsz);
		for (i = 0; i < descsz; i++)
		    if (file_printf(ms, "%02x", desc[i]) == -1)
			return 1;
		return 1;
	return 0;
}
private int
do_os_note(struct magic_set *ms, unsigned char *nbuf, uint32_t type,
    int swap, uint32_t namesz, uint32_t descsz,
    size_t noff, size_t doff, int *flags)
{
	    type == NT_GNU_VERSION && descsz == 2) {
	    *flags |= FLAGS_DID_OS_NOTE;
	    return 1;

	    type == NT_GNU_VERSION && descsz == 16) {
		*flags |= FLAGS_DID_OS_NOTE;
			return 1;
				return 1;
				return 1;
				return 1;
				return 1;
				return 1;
				return 1; 
			return 1;
		return 1;
	if (namesz == 7 && strcmp((char *)&nbuf[noff], "NetBSD") == 0) {
	    	if (type == NT_NETBSD_VERSION && descsz == 4) {
			*flags |= FLAGS_DID_OS_NOTE;
			do_note_netbsd_version(ms, swap, &nbuf[doff]);
			return 1;
		}
	}

	if (namesz == 8 && strcmp((char *)&nbuf[noff], "FreeBSD") == 0) {
	    	if (type == NT_FREEBSD_VERSION && descsz == 4) {
			*flags |= FLAGS_DID_OS_NOTE;
			do_note_freebsd_version(ms, swap, &nbuf[doff]);
			return 1;
		}
	if (namesz == 8 && strcmp((char *)&nbuf[noff], "OpenBSD") == 0 &&
	    type == NT_OPENBSD_VERSION && descsz == 4) {
		*flags |= FLAGS_DID_OS_NOTE;
		if (file_printf(ms, ", for OpenBSD") == -1)
			return 1;
		/* Content of note is always 0 */
		return 1;
	}

	if (namesz == 10 && strcmp((char *)&nbuf[noff], "DragonFly") == 0 &&
	    type == NT_DRAGONFLY_VERSION && descsz == 4) {
		uint32_t desc;
		*flags |= FLAGS_DID_OS_NOTE;
		if (file_printf(ms, ", for DragonFly") == -1)
			return 1;
		(void)memcpy(&desc, &nbuf[doff], sizeof(desc));
		desc = elf_getu32(swap, desc);
		if (file_printf(ms, " %d.%d.%d", desc / 100000,
		    desc / 10000 % 10, desc % 10000) == -1)
			return 1;
		return 1;
	}
	return 0;
}

private int
do_pax_note(struct magic_set *ms, unsigned char *nbuf, uint32_t type,
    int swap, uint32_t namesz, uint32_t descsz,
    size_t noff, size_t doff, int *flags)
{
	    type == NT_NETBSD_PAX && descsz == 4) {
		*flags |= FLAGS_DID_NETBSD_PAX;
			return 1;
				return 1;
		return 1;
	return 0;
}
private int
do_core_note(struct magic_set *ms, unsigned char *nbuf, uint32_t type,
    int swap, uint32_t namesz, uint32_t descsz,
    size_t noff, size_t doff, int *flags, size_t size, int clazz)
{
#ifdef ELFCORE
	int os_style = -1;
			return 1;
		if (type == NT_NETBSD_CORE_PROCINFO) {
			char sbuf[512];
				return 1;
				return 1;
			return 1;
		if (type == NT_PRPSINFO && *flags & FLAGS_IS_CORE) {
				for (k = i + 1 ; k < NOFFSETS; k++) {
					return 1;
				return 1;
	return 0;
}

private size_t
donote(struct magic_set *ms, void *vbuf, size_t offset, size_t size,
    int clazz, int swap, size_t align, int *flags, uint16_t *notecount)
{
	Elf32_Nhdr nh32;
	Elf64_Nhdr nh64;
	size_t noff, doff;
	uint32_t namesz, descsz;
	unsigned char *nbuf = CAST(unsigned char *, vbuf);

	if (*notecount == 0)
		return 0;
	--*notecount;

	if (xnh_sizeof + offset > size) {
		/*
		 * We're out of note headers.
		 */
		return xnh_sizeof + offset;
	}

	(void)memcpy(xnh_addr, &nbuf[offset], xnh_sizeof);
	offset += xnh_sizeof;

	namesz = xnh_namesz;
	descsz = xnh_descsz;
	if ((namesz == 0) && (descsz == 0)) {
		/*
		 * We're out of note headers.
		 */
		return (offset >= size) ? offset : size;
	}

	if (namesz & 0x80000000) {
	    (void)file_printf(ms, ", bad note name size 0x%lx",
		(unsigned long)namesz);
	    return 0;
	}

	if (descsz & 0x80000000) {
	    (void)file_printf(ms, ", bad note description size 0x%lx",
		(unsigned long)descsz);
	    return 0;
	}

	noff = offset;
	doff = ELF_ALIGN(offset + namesz);

	if (offset + namesz > size) {
		/*
		 * We're past the end of the buffer.
		 */
		return doff;
	}

	offset = ELF_ALIGN(doff + descsz);
	if (doff + descsz > size) {
		/*
		 * We're past the end of the buffer.
		 */
		return (offset >= size) ? offset : size;
	}

	if ((*flags & FLAGS_DID_OS_NOTE) == 0) {
		if (do_os_note(ms, nbuf, xnh_type, swap,
		    namesz, descsz, noff, doff, flags))
			return size;
	}

	if ((*flags & FLAGS_DID_BUILD_ID) == 0) {
		if (do_bid_note(ms, nbuf, xnh_type, swap,
		    namesz, descsz, noff, doff, flags))
			return size;
	}
		
	if ((*flags & FLAGS_DID_NETBSD_PAX) == 0) {
		if (do_pax_note(ms, nbuf, xnh_type, swap,
		    namesz, descsz, noff, doff, flags))
			return size;
	}

	if ((*flags & FLAGS_DID_CORE) == 0) {
		if (do_core_note(ms, nbuf, xnh_type, swap,
		    namesz, descsz, noff, doff, flags, size, clazz))
			return size;
	}

	if (namesz == 7 && strcmp((char *)&nbuf[noff], "NetBSD") == 0) {
		switch (xnh_type) {
	    	case NT_NETBSD_VERSION:
			return size;
		case NT_NETBSD_MARCH:
			if (*flags & FLAGS_DID_NETBSD_MARCH)
				return size;
			if (file_printf(ms, ", compiled for: %.*s", (int)descsz,
			    (const char *)&nbuf[doff]) == -1)
				return size;
			break;
		case NT_NETBSD_CMODEL:
			if (*flags & FLAGS_DID_NETBSD_CMODEL)
				return size;
			if (file_printf(ms, ", compiler model: %.*s",
			    (int)descsz, (const char *)&nbuf[doff]) == -1)
				return size;
			break;
		default:
			if (*flags & FLAGS_DID_NETBSD_UNKNOWN)
				return size;
			if (file_printf(ms, ", note=%u", xnh_type) == -1)
				return size;
			break;
		}
		return size;
	}

    size_t size, off_t fsize, int mach, int strtab, int *flags,
    uint16_t *notecount)
				    xsh_size, clazz, swap, 4, flags, notecount);
    int num, size_t size, off_t fsize, int sh_num, int *flags,
    uint16_t *notecount)
				    flags, notecount);
	uint16_t type, phnum, shnum, notecount;