Bug 21440

Summary: Malicious PE with invalid extended relocation can cause binutils/objdumo 2.28 to allocate any-size big memory
Product: binutils Reporter: jgj212
Component: binutilsAssignee: Not yet assigned to anyone <unassigned>
Status: RESOLVED FIXED    
Severity: critical CC: nickc
Priority: P2    
Version: 2.28   
Target Milestone: ---   
Host: Target:
Build: Last reconfirmed:
Attachments: Malicious PE with invalid extended relocation sample

Description jgj212 2017-04-27 06:10:21 UTC
Created attachment 10029 [details]
Malicious PE with invalid extended relocation sample

-----------------------
$objdump -x $FILE
-----------------------

With x option, objdump will allocate memory to store relocation data, this is done in the following function:
static void
dump_relocs_in_section (bfd *abfd,
			asection *section,
			void *dummy ATTRIBUTE_UNUSED)
{
	….
	relsize = bfd_get_reloc_upper_bound (abfd, section); //can be controlled
	…
	relpp = (arelent **) xmalloc (relsize);  //allocate memory
  	relcount = bfd_canonicalize_reloc (abfd, section, relpp, syms);  //read reloc data
	…
}

the relsize is computed as :
(asect->reloc_count + 1) * sizeof (arelent *)    // coff_get_reloc_upper_bound(…)

In the coff standard, reoc_count is WORD, which is 16bit wide. sizeof (arelent *) is constant and small. So normally, the result is small. But when PE section contain extended relocation, everything will go bad. 

PE section header is inited in the following function:
static void
coff_set_alignment_hook (bfd * abfd ATTRIBUTE_UNUSED,
			 asection * section,
			 void * scnhdr)
{
	…
	/* Check for extended relocs.  */
	if (hdr->s_flags & IMAGE_SCN_LNK_NRELOC_OVFL)
	{
struct external_reloc dst;
      struct internal_reloc n;
      file_ptr oldpos = bfd_tell (abfd);
      bfd_size_type relsz = bfd_coff_relsz (abfd);

      if (bfd_seek (abfd, (file_ptr) hdr->s_relptr, 0) != 0) //seek to the first reloc data
	 return;
	  
      if (bfd_bread (& dst, relsz, abfd) != relsz) //read the first reloc data
	  return; 

      coff_swap_reloc_in (abfd, &dst, &n);//fill struct
      if (bfd_seek (abfd, oldpos, 0) != 0)
		return;

      section->reloc_count = hdr->s_nreloc = n.r_vaddr - 1;  //can be controlled
      section->rel_filepos += relsz;
}
…
}	

From the above, when pe section contain extended relocation, reloc_count is assigned from n.r_vaddr.  n is a instance of struct internal_reloc as follow:
struct internal_reloc
{
  bfd_vma r_vaddr;		/* Virtual address of reference */
  long r_symndx;		/* Index into symbol table	*/
  unsigned short r_type;	/* Relocation type		*/
  unsigned char r_size;		/* Used by RS/6000 and ECOFF	*/
  unsigned char r_extern;	/* Used by ECOFF		*/
  unsigned long r_offset;	/* Used by Alpha ECOFF, SPARC, others */
};

r_vaddr's type is unsigned long, it can be 32bit and 64bit, according to the os-archive. it is from the dst, which is a instance of struct external_reloc as follow:
struct external_reloc
{
  char r_vaddr[4];
  char r_symndx[4];
  char r_type[2];
};

So n.r_vaddr is really 32bit wide,  section->reloc_count is also 32bit wide.
(asect->reloc_count + 1) * sizeof (arelent *) can be very large.


To control the memory size, it just need to modify the r_vaddr field of first reloc data, if can be from 0 to 0Xffffffff.  

This could cause memory exhaustion to dos.
Comment 1 jgj212 2017-04-27 10:20:00 UTC
Credit:The bug was discovered by ADLab of Venustech
Comment 2 Sourceware Commits 2017-05-02 10:56:08 UTC
The master branch has been updated by Nick Clifton <nickc@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=39ff1b79f687b65f4144ddb379f22587003443fb

commit 39ff1b79f687b65f4144ddb379f22587003443fb
Author: Nick Clifton <nickc@redhat.com>
Date:   Tue May 2 11:54:53 2017 +0100

    Prevent memory exhaustion from a corrupt PE binary with an overlarge number of relocs.
    
    	PR 21440
    	* objdump.c (dump_relocs_in_section): Check for an excessive
    	number of relocs before attempting to dump them.
Comment 3 Nick Clifton 2017-05-02 10:58:19 UTC
Thanks for reporting this bug.

I have applied a patch to add a check for the reloc size being larger than
the size of the file, and hence invalid, before an attempt is made to allocate
memory for the relocs.  I think that this should resolve the problem.

Cheers
  Nick