Archive sanity checks

Adds some sanity checking to size values read from file.

	* archive.c (do_slurp_bsd_armap): Increase minimum parsed_size, and
	bfd_set_error on failing test.  Don't bother changing bfd_error on
	file read error.  Check symdef_count is multiple of BSD_SYMDEF_SIZE.
	Check sym name is within string buffer.  Use size_t for some vars.
	(do_slurp_coff_armap): Use size_t for some variables, fix size of
	int_buf.  Don't change bfd_error on file read error.  Use
	_bfd_mul_overflow when calculating carsym buffer size.  Reorder
	calculations to catch overflows before they occur.  malloc and
	free raw armap rather than using bfd_alloc.  Read raw armap before
	allocating carsym+strings buffer.
	(_bfd_slurp_extended_name_table): Localize variables.  Check
	name size against file size.
This commit is contained in:
Alan Modra
2020-02-26 17:02:38 +10:30
parent cc4c4f40a2
commit 02f7e7eed9
2 changed files with 103 additions and 68 deletions

View File

@ -1,3 +1,18 @@
2020-02-26 Alan Modra <amodra@gmail.com>
* archive.c (do_slurp_bsd_armap): Increase minimum parsed_size, and
bfd_set_error on failing test. Don't bother changing bfd_error on
file read error. Check symdef_count is multiple of BSD_SYMDEF_SIZE.
Check sym name is within string buffer. Use size_t for some vars.
(do_slurp_coff_armap): Use size_t for some variables, fix size of
int_buf. Don't change bfd_error on file read error. Use
_bfd_mul_overflow when calculating carsym buffer size. Reorder
calculations to catch overflows before they occur. malloc and
free raw armap rather than using bfd_alloc. Read raw armap before
allocating carsym+strings buffer.
(_bfd_slurp_extended_name_table): Localize variables. Check
name size against file size.
2020-02-26 Alan Modra <amodra@gmail.com> 2020-02-26 Alan Modra <amodra@gmail.com>
* vms-lib.c (vms_lib_read_index): Release correct buffer. * vms-lib.c (vms_lib_read_index): Release correct buffer.

View File

@ -951,11 +951,12 @@ static bfd_boolean
do_slurp_bsd_armap (bfd *abfd) do_slurp_bsd_armap (bfd *abfd)
{ {
struct areltdata *mapdata; struct areltdata *mapdata;
unsigned int counter; size_t counter;
bfd_byte *raw_armap, *rbase; bfd_byte *raw_armap, *rbase;
struct artdata *ardata = bfd_ardata (abfd); struct artdata *ardata = bfd_ardata (abfd);
char *stringbase; char *stringbase;
bfd_size_type parsed_size, amt; bfd_size_type parsed_size;
size_t amt, string_size;
carsym *set; carsym *set;
mapdata = (struct areltdata *) _bfd_read_ar_hdr (abfd); mapdata = (struct areltdata *) _bfd_read_ar_hdr (abfd);
@ -965,44 +966,51 @@ do_slurp_bsd_armap (bfd *abfd)
free (mapdata); free (mapdata);
/* PR 17512: file: 883ff754. */ /* PR 17512: file: 883ff754. */
/* PR 17512: file: 0458885f. */ /* PR 17512: file: 0458885f. */
if (parsed_size < 4) if (parsed_size < BSD_SYMDEF_COUNT_SIZE + BSD_STRING_COUNT_SIZE)
return FALSE; {
bfd_set_error (bfd_error_malformed_archive);
return FALSE;
}
raw_armap = (bfd_byte *) _bfd_alloc_and_read (abfd, parsed_size, parsed_size); raw_armap = (bfd_byte *) _bfd_alloc_and_read (abfd, parsed_size, parsed_size);
if (raw_armap == NULL) if (raw_armap == NULL)
{ return FALSE;
if (bfd_get_error () != bfd_error_system_call)
bfd_set_error (bfd_error_malformed_archive);
return FALSE;
}
ardata->symdef_count = H_GET_32 (abfd, raw_armap) / BSD_SYMDEF_SIZE; parsed_size -= BSD_SYMDEF_COUNT_SIZE + BSD_STRING_COUNT_SIZE;
if (ardata->symdef_count * BSD_SYMDEF_SIZE > amt = H_GET_32 (abfd, raw_armap);
parsed_size - BSD_SYMDEF_COUNT_SIZE) if (amt > parsed_size
|| amt % BSD_SYMDEF_SIZE != 0)
{ {
/* Probably we're using the wrong byte ordering. */ /* Probably we're using the wrong byte ordering. */
bfd_set_error (bfd_error_wrong_format); bfd_set_error (bfd_error_wrong_format);
bfd_release (abfd, raw_armap); goto release_armap;
return FALSE;
} }
rbase = raw_armap + BSD_SYMDEF_COUNT_SIZE; rbase = raw_armap + BSD_SYMDEF_COUNT_SIZE;
stringbase = ((char *) rbase stringbase = (char *) rbase + amt + BSD_STRING_COUNT_SIZE;
+ ardata->symdef_count * BSD_SYMDEF_SIZE string_size = parsed_size - amt;
+ BSD_STRING_COUNT_SIZE);
amt = ardata->symdef_count * sizeof (carsym); ardata->symdef_count = amt / BSD_SYMDEF_SIZE;
if (_bfd_mul_overflow (ardata->symdef_count, sizeof (carsym), &amt))
{
bfd_set_error (bfd_error_no_memory);
goto release_armap;
}
ardata->symdefs = (struct carsym *) bfd_alloc (abfd, amt); ardata->symdefs = (struct carsym *) bfd_alloc (abfd, amt);
if (!ardata->symdefs) if (!ardata->symdefs)
{ goto release_armap;
bfd_release (abfd, raw_armap);
return FALSE;
}
for (counter = 0, set = ardata->symdefs; for (counter = 0, set = ardata->symdefs;
counter < ardata->symdef_count; counter < ardata->symdef_count;
counter++, set++, rbase += BSD_SYMDEF_SIZE) counter++, set++, rbase += BSD_SYMDEF_SIZE)
{ {
set->name = H_GET_32 (abfd, rbase) + stringbase; unsigned nameoff = H_GET_32 (abfd, rbase);
if (nameoff >= string_size)
{
bfd_set_error (bfd_error_malformed_archive);
goto release_armap;
}
set->name = stringbase + nameoff;
set->file_offset = H_GET_32 (abfd, rbase + BSD_SYMDEF_OFFSET_SIZE); set->file_offset = H_GET_32 (abfd, rbase + BSD_SYMDEF_OFFSET_SIZE);
} }
@ -1014,6 +1022,12 @@ do_slurp_bsd_armap (bfd *abfd)
to be allocated on an objalloc anyway... */ to be allocated on an objalloc anyway... */
abfd->has_armap = TRUE; abfd->has_armap = TRUE;
return TRUE; return TRUE;
release_armap:
ardata->symdef_count = 0;
ardata->symdefs = NULL;
bfd_release (abfd, raw_armap);
return FALSE;
} }
/* Read a COFF archive symbol table. Returns FALSE on error, TRUE /* Read a COFF archive symbol table. Returns FALSE on error, TRUE
@ -1029,12 +1043,12 @@ do_slurp_coff_armap (bfd *abfd)
char *stringend; char *stringend;
bfd_size_type stringsize; bfd_size_type stringsize;
bfd_size_type parsed_size; bfd_size_type parsed_size;
ufile_ptr filesize;
size_t nsymz, carsym_size, ptrsize, i;
carsym *carsyms; carsym *carsyms;
bfd_size_type nsymz; /* Number of symbols in armap. */
bfd_vma (*swap) (const void *); bfd_vma (*swap) (const void *);
char int_buf[sizeof (long)]; char int_buf[4];
bfd_size_type carsym_size, ptrsize; struct areltdata *tmp;
unsigned int i;
mapdata = (struct areltdata *) _bfd_read_ar_hdr (abfd); mapdata = (struct areltdata *) _bfd_read_ar_hdr (abfd);
if (mapdata == NULL) if (mapdata == NULL)
@ -1043,28 +1057,33 @@ do_slurp_coff_armap (bfd *abfd)
free (mapdata); free (mapdata);
if (bfd_bread (int_buf, 4, abfd) != 4) if (bfd_bread (int_buf, 4, abfd) != 4)
{ return FALSE;
if (bfd_get_error () != bfd_error_system_call)
bfd_set_error (bfd_error_malformed_archive);
return FALSE;
}
/* It seems that all numeric information in a coff archive is always /* It seems that all numeric information in a coff archive is always
in big endian format, nomatter the host or target. */ in big endian format, no matter the host or target. */
swap = bfd_getb32; swap = bfd_getb32;
nsymz = bfd_getb32 (int_buf); nsymz = bfd_getb32 (int_buf);
stringsize = parsed_size - (4 * nsymz) - 4;
/* The coff armap must be read sequentially. So we construct a /* The coff armap must be read sequentially. So we construct a
bsd-style one in core all at once, for simplicity. */ bsd-style one in core all at once, for simplicity. */
if (nsymz > ~ (bfd_size_type) 0 / sizeof (carsym)) if (_bfd_mul_overflow (nsymz, sizeof (carsym), &carsym_size))
{ {
bfd_set_error (bfd_error_no_memory); bfd_set_error (bfd_error_no_memory);
return FALSE; return FALSE;
} }
carsym_size = (nsymz * sizeof (carsym)); filesize = bfd_get_file_size (abfd);
ptrsize = (4 * nsymz); ptrsize = 4 * nsymz;
if ((filesize != 0 && parsed_size > filesize)
|| parsed_size < 4
|| parsed_size - 4 < ptrsize)
{
bfd_set_error (bfd_error_malformed_archive);
return FALSE;
}
stringsize = parsed_size - ptrsize - 4;
if (carsym_size + stringsize + 1 <= carsym_size) if (carsym_size + stringsize + 1 <= carsym_size)
{ {
@ -1072,22 +1091,20 @@ do_slurp_coff_armap (bfd *abfd)
return FALSE; return FALSE;
} }
/* Allocate and read in the raw offsets. */
raw_armap = (int *) _bfd_malloc_and_read (abfd, ptrsize, ptrsize);
if (raw_armap == NULL)
return FALSE;
ardata->symdefs = (struct carsym *) bfd_alloc (abfd, ardata->symdefs = (struct carsym *) bfd_alloc (abfd,
carsym_size + stringsize + 1); carsym_size + stringsize + 1);
if (ardata->symdefs == NULL) if (ardata->symdefs == NULL)
return FALSE; goto free_armap;
carsyms = ardata->symdefs; carsyms = ardata->symdefs;
stringbase = ((char *) ardata->symdefs) + carsym_size; stringbase = ((char *) ardata->symdefs) + carsym_size;
/* Allocate and read in the raw offsets. */ if (bfd_bread (stringbase, stringsize, abfd) != stringsize)
raw_armap = (int *) _bfd_alloc_and_read (abfd, ptrsize, ptrsize); goto release_symdefs;
if (raw_armap == NULL
|| (bfd_bread (stringbase, stringsize, abfd) != stringsize))
{
if (bfd_get_error () != bfd_error_system_call)
bfd_set_error (bfd_error_malformed_archive);
goto release_symdefs;
}
/* OK, build the carsyms. */ /* OK, build the carsyms. */
stringend = stringbase + stringsize; stringend = stringbase + stringsize;
@ -1107,32 +1124,29 @@ do_slurp_coff_armap (bfd *abfd)
ardata->first_file_filepos = bfd_tell (abfd); ardata->first_file_filepos = bfd_tell (abfd);
/* Pad to an even boundary if you have to. */ /* Pad to an even boundary if you have to. */
ardata->first_file_filepos += (ardata->first_file_filepos) % 2; ardata->first_file_filepos += (ardata->first_file_filepos) % 2;
if (bfd_seek (abfd, ardata->first_file_filepos, SEEK_SET) != 0)
goto release_symdefs;
abfd->has_armap = TRUE; abfd->has_armap = TRUE;
bfd_release (abfd, raw_armap); free (raw_armap);
/* Check for a second archive header (as used by PE). */ /* Check for a second archive header (as used by PE). */
{ tmp = (struct areltdata *) _bfd_read_ar_hdr (abfd);
struct areltdata *tmp; if (tmp != NULL)
{
bfd_seek (abfd, ardata->first_file_filepos, SEEK_SET); if (tmp->arch_header[0] == '/'
tmp = (struct areltdata *) _bfd_read_ar_hdr (abfd); && tmp->arch_header[1] == ' ')
if (tmp != NULL) ardata->first_file_filepos
{ += (tmp->parsed_size + sizeof (struct ar_hdr) + 1) & ~(unsigned) 1;
if (tmp->arch_header[0] == '/' free (tmp);
&& tmp->arch_header[1] == ' ') }
{
ardata->first_file_filepos +=
(tmp->parsed_size + sizeof (struct ar_hdr) + 1) & ~(unsigned) 1;
}
free (tmp);
}
}
return TRUE; return TRUE;
release_symdefs: release_symdefs:
bfd_release (abfd, (ardata)->symdefs); bfd_release (abfd, (ardata)->symdefs);
free_armap:
free (raw_armap);
return FALSE; return FALSE;
} }
@ -1209,8 +1223,6 @@ bfd_boolean
_bfd_slurp_extended_name_table (bfd *abfd) _bfd_slurp_extended_name_table (bfd *abfd)
{ {
char nextname[17]; char nextname[17];
struct areltdata *namedata;
bfd_size_type amt;
/* FIXME: Formatting sucks here, and in case of failure of BFD_READ, /* FIXME: Formatting sucks here, and in case of failure of BFD_READ,
we probably don't want to return TRUE. */ we probably don't want to return TRUE. */
@ -1219,6 +1231,10 @@ _bfd_slurp_extended_name_table (bfd *abfd)
if (bfd_bread (nextname, 16, abfd) == 16) if (bfd_bread (nextname, 16, abfd) == 16)
{ {
struct areltdata *namedata;
bfd_size_type amt;
ufile_ptr filesize;
if (bfd_seek (abfd, (file_ptr) -16, SEEK_CUR) != 0) if (bfd_seek (abfd, (file_ptr) -16, SEEK_CUR) != 0)
return FALSE; return FALSE;
@ -1234,9 +1250,13 @@ _bfd_slurp_extended_name_table (bfd *abfd)
if (namedata == NULL) if (namedata == NULL)
return FALSE; return FALSE;
filesize = bfd_get_file_size (abfd);
amt = namedata->parsed_size; amt = namedata->parsed_size;
if (amt + 1 == 0) if (amt + 1 == 0 || (filesize != 0 && amt > filesize))
goto byebye; {
bfd_set_error (bfd_error_malformed_archive);
goto byebye;
}
bfd_ardata (abfd)->extended_names_size = amt; bfd_ardata (abfd)->extended_names_size = amt;
bfd_ardata (abfd)->extended_names = (char *) bfd_alloc (abfd, amt + 1); bfd_ardata (abfd)->extended_names = (char *) bfd_alloc (abfd, amt + 1);