alpha_ecoff_get_relocated_section_contents buffer overflow

This is aimed at fixing holes in two alpha-ecoff relocation functions
that access section contents without first bounds checking offsets.
I've also rewritten ALPHA_R_OP_STORE handling to support writing to
the bytes near the end of the section.

	* coff-alpha.c (alpha_ecoff_get_relocated_section_contents): Don't
	bother checking ALPHA_R_LITERAL insn.  Range check before reading
	contents for ALPHA_R_GPDISP, and simplify handling.  Rewrite
	ALPHA_R_OP_STORE handling.  Correct error callback args.
	(alpha_relocate_section): Similarly.  Don't abort, report errors.
This commit is contained in:
Alan Modra
2023-12-05 16:32:34 +10:30
parent 1024d59cb2
commit db4ab410de

View File

@@ -885,24 +885,14 @@ alpha_ecoff_get_relocated_section_contents (bfd *abfd,
use. This would not be particularly difficult, but it is
not currently implemented. */
{
unsigned long insn;
/* I believe that the LITERAL reloc will only apply to a
ldq or ldl instruction, so check my assumption. */
insn = bfd_get_32 (input_bfd, data + rel->address);
BFD_ASSERT (((insn >> 26) & 0x3f) == 0x29
|| ((insn >> 26) & 0x3f) == 0x28);
rel->addend -= gp;
r = bfd_perform_relocation (input_bfd, rel, data, input_section,
output_bfd, &err);
if (r == bfd_reloc_ok && gp_undefined)
{
r = bfd_reloc_dangerous;
err =
(char *) _("GP relative relocation used when GP not defined");
}
err = (char *) _("GP relative relocation used"
" when GP not defined");
}
break;
@@ -918,54 +908,46 @@ alpha_ecoff_get_relocated_section_contents (bfd *abfd,
current location. The second of the pair is r_size bytes
ahead; it used to be marked with an ALPHA_R_IGNORE reloc,
but that no longer happens in OSF/1 3.2. */
if (bfd_reloc_offset_in_range (rel->howto, input_bfd, input_section,
rel->address)
&& bfd_reloc_offset_in_range (rel->howto, input_bfd, input_section,
rel->address + rel->addend))
{
unsigned long insn1, insn2;
bfd_vma addend;
/* Get the two instructions. */
insn1 = bfd_get_32 (input_bfd, data + rel->address);
insn2 = bfd_get_32 (input_bfd, data + rel->address + rel->addend);
bfd_byte *p = data + rel->address;
bfd_vma insn1 = bfd_get_32 (input_bfd, p);
bfd_vma insn2 = bfd_get_32 (input_bfd, p + rel->addend);
BFD_ASSERT (((insn1 >> 26) & 0x3f) == 0x09); /* ldah */
BFD_ASSERT (((insn2 >> 26) & 0x3f) == 0x08); /* lda */
/* Get the existing addend. We must account for the sign
extension done by lda and ldah. */
addend = ((insn1 & 0xffff) << 16) + (insn2 & 0xffff);
if (insn1 & 0x8000)
{
addend -= 0x80000000;
addend -= 0x80000000;
}
if (insn2 & 0x8000)
addend -= 0x10000;
bfd_vma addend = (((((insn1 & 0xffff) ^ 0x8000) - 0x8000) << 16)
+ ((((insn2 & 0xffff) ^ 0x8000) - 0x8000)));
/* The existing addend includes the different between the
gp of the input BFD and the address in the input BFD.
Subtract this out. */
addend -= (ecoff_data (input_bfd)->gp
- (input_section->vma + rel->address));
addend -= ecoff_data (input_bfd)->gp - input_section->vma;
/* Now add in the final gp value, and subtract out the
final address. */
addend += (gp
- (input_section->output_section->vma
+ input_section->output_offset
+ rel->address));
addend += gp - (input_section->output_section->vma
+ input_section->output_offset);
/* Change the instructions, accounting for the sign
extension, and write them out. */
if (addend & 0x8000)
addend += 0x10000;
insn1 = (insn1 & 0xffff0000) | ((addend >> 16) & 0xffff);
insn2 = (insn2 & 0xffff0000) | (addend & 0xffff);
insn1 = (insn1 & ~0xffff) | (((addend + 0x8000) >> 16) & 0xffff);
insn2 = (insn2 & ~0xffff) | (addend & 0xffff);
bfd_put_32 (input_bfd, (bfd_vma) insn1, data + rel->address);
bfd_put_32 (input_bfd, (bfd_vma) insn2,
data + rel->address + rel->addend);
bfd_put_32 (input_bfd, insn1, p);
bfd_put_32 (input_bfd, insn2, p + rel->addend);
}
else
r = bfd_reloc_outofrange;
rel->address += input_section->output_offset;
}
break;
case ALPHA_R_OP_PUSH:
@@ -1007,9 +989,6 @@ alpha_ecoff_get_relocated_section_contents (bfd *abfd,
case ALPHA_R_OP_STORE:
/* Store a value from the reloc stack into a bitfield. */
{
bfd_vma val;
int offset, size;
if (relocatable)
{
rel->address += input_section->output_offset;
@@ -1022,15 +1001,36 @@ alpha_ecoff_get_relocated_section_contents (bfd *abfd,
break;
}
/* The offset and size for this reloc are encoded into the
addend field by alpha_adjust_reloc_in. */
offset = (rel->addend >> 8) & 0xff;
size = rel->addend & 0xff;
/* The offset and size in bits for this reloc are encoded
into the addend field by alpha_adjust_reloc_in. */
unsigned int offset = (rel->addend >> 8) & 0xff;
unsigned int size = rel->addend & 0xff;
unsigned int startbyte = offset >> 3;
unsigned int endbyte = (offset + size + 7) >> 3;
unsigned int bytes = endbyte + 1 - startbyte;
val = bfd_get_64 (abfd, data + rel->address);
val &=~ (((1 << size) - 1) << offset);
val |= (stack[--tos] & ((1 << size) - 1)) << offset;
bfd_put_64 (abfd, val, data + rel->address);
if (bytes <= 8
&& rel->address + startbyte + bytes >= rel->address
&& (rel->address + startbyte + bytes
<= bfd_get_section_limit_octets (input_bfd, input_section)))
{
uint64_t val = 0;
for (int off = bytes - 1; off >= 0; --off)
val = (val << 8) | data[rel->address + startbyte + off];
offset -= startbyte << 3;
size -= startbyte << 3;
uint64_t mask = (((uint64_t) 1 << size) - 1) << offset;
val = (val & ~mask) | ((stack[--tos] << offset) & mask);
for (unsigned int off = 0; off < bytes; ++off)
{
data[rel->address + startbyte + off] = val & 0xff;
val >>= 8;
}
}
else
r = bfd_reloc_outofrange;
}
break;
@@ -1149,20 +1149,20 @@ alpha_ecoff_get_relocated_section_contents (bfd *abfd,
(*link_info->callbacks->einfo)
/* xgettext:c-format */
(_("%X%P: %pB(%pA): relocation \"%pR\" goes out of range\n"),
abfd, input_section, rel);
input_bfd, input_section, rel);
goto error_return;
case bfd_reloc_notsupported:
(*link_info->callbacks->einfo)
/* xgettext:c-format */
(_("%X%P: %pB(%pA): relocation \"%pR\" is not supported\n"),
abfd, input_section, rel);
input_bfd, input_section, rel);
goto error_return;
default:
(*link_info->callbacks->einfo)
/* xgettext:c-format */
(_("%X%P: %pB(%pA): relocation \"%pR\""
" returns an unrecognized value %x\n"),
abfd, input_section, rel, r);
input_bfd, input_section, rel, r);
break;
}
}
@@ -1389,6 +1389,7 @@ alpha_relocate_section (bfd *output_bfd,
struct external_reloc *ext_rel;
struct external_reloc *ext_rel_end;
bfd_size_type amt;
bool ret = true;
/* We keep a table mapping the symndx found in an internal reloc to
the appropriate section. This is faster than looking up the
@@ -1522,6 +1523,7 @@ alpha_relocate_section (bfd *output_bfd,
bool adjust_addrp;
bool gp_usedp;
bfd_vma addend;
bfd_reloc_status_type r;
r_vaddr = H_GET_64 (input_bfd, ext_rel->r_vaddr);
r_symndx = H_GET_32 (input_bfd, ext_rel->r_symndx);
@@ -1539,27 +1541,13 @@ alpha_relocate_section (bfd *output_bfd,
adjust_addrp = true;
gp_usedp = false;
addend = 0;
r = bfd_reloc_ok;
switch (r_type)
{
case ALPHA_R_GPRELHIGH:
_bfd_error_handler (_("%pB: %s unsupported"),
input_bfd, "ALPHA_R_GPRELHIGH");
bfd_set_error (bfd_error_bad_value);
continue;
case ALPHA_R_GPRELLOW:
_bfd_error_handler (_("%pB: %s unsupported"),
input_bfd, "ALPHA_R_GPRELLOW");
bfd_set_error (bfd_error_bad_value);
continue;
default:
/* xgettext:c-format */
_bfd_error_handler (_("%pB: unsupported relocation type %#x"),
input_bfd, (int) r_type);
bfd_set_error (bfd_error_bad_value);
continue;
r = bfd_reloc_notsupported;
break;
case ALPHA_R_IGNORE:
/* This reloc appears after a GPDISP reloc. On earlier
@@ -1616,17 +1604,6 @@ alpha_relocate_section (bfd *output_bfd,
use. This would not be particularly difficult, but it is
not currently implemented. */
/* I believe that the LITERAL reloc will only apply to a ldq
or ldl instruction, so check my assumption. */
{
unsigned long insn;
insn = bfd_get_32 (input_bfd,
contents + r_vaddr - input_section->vma);
BFD_ASSERT (((insn >> 26) & 0x3f) == 0x29
|| ((insn >> 26) & 0x3f) == 0x28);
}
relocatep = true;
addend = ecoff_data (input_bfd)->gp - gp;
gp_usedp = true;
@@ -1643,58 +1620,45 @@ alpha_relocate_section (bfd *output_bfd,
current location. The second of the pair is r_symndx
bytes ahead. It used to be marked with an ALPHA_R_IGNORE
reloc, but OSF/1 3.2 no longer does that. */
if (r_vaddr >= input_section->vma
&& r_vaddr - input_section->vma < input_section->size
&& input_section->size - (r_vaddr - input_section->vma) > r_symndx
&& (input_section->size - (r_vaddr - input_section->vma)
- r_symndx >= 4))
{
unsigned long insn1, insn2;
/* Get the two instructions. */
insn1 = bfd_get_32 (input_bfd,
contents + r_vaddr - input_section->vma);
insn2 = bfd_get_32 (input_bfd,
(contents
+ r_vaddr
- input_section->vma
+ r_symndx));
bfd_byte *p = contents + r_vaddr - input_section->vma;
bfd_vma insn1 = bfd_get_32 (input_bfd, p);
bfd_vma insn2 = bfd_get_32 (input_bfd, p + r_symndx);
BFD_ASSERT (((insn1 >> 26) & 0x3f) == 0x09); /* ldah */
BFD_ASSERT (((insn2 >> 26) & 0x3f) == 0x08); /* lda */
/* Get the existing addend. We must account for the sign
extension done by lda and ldah. */
addend = ((insn1 & 0xffff) << 16) + (insn2 & 0xffff);
if (insn1 & 0x8000)
{
/* This is addend -= 0x100000000 without causing an
integer overflow on a 32 bit host. */
addend -= 0x80000000;
addend -= 0x80000000;
}
if (insn2 & 0x8000)
addend -= 0x10000;
addend = (((((insn1 & 0xffff) ^ 0x8000) - 0x8000) << 16)
+ (((insn2 & 0xffff) ^ 0x8000) - 0x8000));
/* The existing addend includes the difference between the
gp of the input BFD and the address in the input BFD.
We want to change this to the difference between the
final GP and the final address. */
addend += (gp
- ecoff_data (input_bfd)->gp
+ input_section->vma
- (input_section->output_section->vma
+ input_section->output_offset));
addend -= ecoff_data (input_bfd)->gp - input_section->vma;
addend += gp - (input_section->output_section->vma
+ input_section->output_offset);
/* Change the instructions, accounting for the sign
extension, and write them out. */
if (addend & 0x8000)
addend += 0x10000;
insn1 = (insn1 & 0xffff0000) | ((addend >> 16) & 0xffff);
insn2 = (insn2 & 0xffff0000) | (addend & 0xffff);
insn1 = (insn1 & ~0xffff) | (((addend + 0x8000) >> 16) & 0xffff);
insn2 = (insn2 & ~0xffff) | (addend & 0xffff);
bfd_put_32 (input_bfd, (bfd_vma) insn1,
contents + r_vaddr - input_section->vma);
bfd_put_32 (input_bfd, (bfd_vma) insn2,
contents + r_vaddr - input_section->vma + r_symndx);
bfd_put_32 (input_bfd, insn1, p);
bfd_put_32 (input_bfd, insn2, p + r_symndx);
gp_usedp = true;
}
else
r = bfd_reloc_outofrange;
break;
case ALPHA_R_OP_PUSH:
@@ -1709,8 +1673,11 @@ alpha_relocate_section (bfd *output_bfd,
asection *s;
s = symndx_to_section[r_symndx];
if (s == (asection *) NULL)
abort ();
if (s == NULL)
{
r = bfd_reloc_notsupported;
break;
}
addend = s->output_section->vma + s->output_offset - s->vma;
}
else
@@ -1718,8 +1685,11 @@ alpha_relocate_section (bfd *output_bfd,
struct ecoff_link_hash_entry *h;
h = sym_hashes[r_symndx];
if (h == (struct ecoff_link_hash_entry *) NULL)
abort ();
if (h == NULL)
{
r = bfd_reloc_notsupported;
break;
}
if (! bfd_link_relocatable (info))
{
@@ -1773,19 +1743,28 @@ alpha_relocate_section (bfd *output_bfd,
{
case ALPHA_R_OP_PUSH:
if (tos >= RELOC_STACKSIZE)
abort ();
{
r = bfd_reloc_notsupported;
break;
}
stack[tos++] = addend;
break;
case ALPHA_R_OP_PSUB:
if (tos == 0)
abort ();
{
r = bfd_reloc_notsupported;
break;
}
stack[tos - 1] -= addend;
break;
case ALPHA_R_OP_PRSHIFT:
if (tos == 0)
abort ();
{
r = bfd_reloc_notsupported;
break;
}
stack[tos - 1] >>= addend;
break;
}
@@ -1800,28 +1779,34 @@ alpha_relocate_section (bfd *output_bfd,
adjust the address of the reloc. */
if (! bfd_link_relocatable (info))
{
bfd_vma mask;
bfd_vma val;
unsigned int startbyte = r_offset >> 3;
unsigned int endbyte = (r_offset + r_size + 7) >> 3;
unsigned int bytes = endbyte + 1 - startbyte;
if (tos == 0)
abort ();
if (bytes <= 8
&& r_vaddr >= input_section->vma
&& r_vaddr - input_section->vma < input_section->size
&& (input_section->size - (r_vaddr - input_section->vma)
>= startbyte + bytes))
{
bfd_byte *p = contents + (r_vaddr - input_section->vma);
uint64_t val = 0;
for (int off = bytes - 1; off >= 0; --off)
val = (val << 8) | p[startbyte + off];
/* Get the relocation mask. The separate steps and the
casts to bfd_vma are attempts to avoid a bug in the
Alpha OSF 1.3 C compiler. See reloc.c for more
details. */
mask = 1;
mask <<= (bfd_vma) r_size;
mask -= 1;
r_offset -= startbyte << 3;
r_size -= startbyte << 3;
uint64_t mask = (((uint64_t) 1 << r_size) - 1) << r_offset;
val = (val & ~mask) | ((stack[--tos] << r_offset) & mask);
/* FIXME: I don't know what kind of overflow checking,
if any, should be done here. */
val = bfd_get_64 (input_bfd,
contents + r_vaddr - input_section->vma);
val &=~ mask << (bfd_vma) r_offset;
val |= (stack[--tos] & mask) << (bfd_vma) r_offset;
bfd_put_64 (input_bfd, val,
contents + r_vaddr - input_section->vma);
for (unsigned int off = 0; off < bytes; ++off)
{
p[startbyte + off] = val & 0xff;
val >>= 8;
}
}
else
r = bfd_reloc_outofrange;
}
break;
@@ -1832,13 +1817,12 @@ alpha_relocate_section (bfd *output_bfd,
break;
}
if (relocatep)
if (relocatep && r == bfd_reloc_ok)
{
reloc_howto_type *howto;
struct ecoff_link_hash_entry *h = NULL;
asection *s = NULL;
bfd_vma relocation;
bfd_reloc_status_type r;
/* Perform a relocation. */
@@ -1850,8 +1834,8 @@ alpha_relocate_section (bfd *output_bfd,
/* If h is NULL, that means that there is a reloc
against an external symbol which we thought was just
a debugging symbol. This should not happen. */
if (h == (struct ecoff_link_hash_entry *) NULL)
abort ();
if (h == NULL)
r = bfd_reloc_notsupported;
}
else
{
@@ -1860,11 +1844,14 @@ alpha_relocate_section (bfd *output_bfd,
else
s = symndx_to_section[r_symndx];
if (s == (asection *) NULL)
abort ();
if (s == NULL)
r = bfd_reloc_notsupported;
}
if (bfd_link_relocatable (info))
if (r != bfd_reloc_ok)
;
else if (bfd_link_relocatable (info))
{
/* We are generating relocatable output, and must
convert the existing reloc. */
@@ -1930,12 +1917,7 @@ alpha_relocate_section (bfd *output_bfd,
+ hsec->output_offset);
}
else
{
(*info->callbacks->undefined_symbol)
(info, h->root.root.string, input_bfd, input_section,
r_vaddr - input_section->vma, true);
relocation = 0;
}
r = bfd_reloc_undefined;
}
else
{
@@ -1950,6 +1932,7 @@ alpha_relocate_section (bfd *output_bfd,
relocation += input_section->vma;
}
if (r == bfd_reloc_ok)
r = _bfd_final_link_relocate (howto,
input_bfd,
input_section,
@@ -1958,30 +1941,6 @@ alpha_relocate_section (bfd *output_bfd,
relocation,
addend);
}
if (r != bfd_reloc_ok)
{
switch (r)
{
default:
case bfd_reloc_outofrange:
abort ();
case bfd_reloc_overflow:
{
const char *name;
if (r_extern)
name = sym_hashes[r_symndx]->root.root.string;
else
name = bfd_section_name (symndx_to_section[r_symndx]);
(*info->callbacks->reloc_overflow)
(info, NULL, name, alpha_howto_table[r_type].name,
(bfd_vma) 0, input_bfd, input_section,
r_vaddr - input_section->vma);
}
break;
}
}
}
if (bfd_link_relocatable (info) && adjust_addrp)
@@ -1997,20 +1956,65 @@ alpha_relocate_section (bfd *output_bfd,
if (gp_usedp && gp_undefined)
{
(*info->callbacks->reloc_dangerous)
(info, _("GP relative relocation used when GP not defined"),
input_bfd, input_section, r_vaddr - input_section->vma);
r = bfd_reloc_dangerous;
/* Only give the error once per link. */
gp = 4;
_bfd_set_gp_value (output_bfd, gp);
gp_undefined = false;
}
if (r != bfd_reloc_ok)
{
switch (r)
{
case bfd_reloc_overflow:
{
const char *name;
if (r_extern)
name = sym_hashes[r_symndx]->root.root.string;
else
name = bfd_section_name (symndx_to_section[r_symndx]);
(*info->callbacks->reloc_overflow)
(info, NULL, name, alpha_howto_table[r_type].name,
(bfd_vma) 0, input_bfd, input_section,
r_vaddr - input_section->vma);
}
break;
case bfd_reloc_outofrange:
(*info->callbacks->einfo)
/* xgettext:c-format */
(_("%X%P: %pB(%pA): relocation out of range\n"),
input_bfd, input_section);
break;
case bfd_reloc_undefined:
(*info->callbacks->undefined_symbol)
(info, sym_hashes[r_symndx]->root.root.string,
input_bfd, input_section,
r_vaddr - input_section->vma, true);
break;
case bfd_reloc_notsupported:
(*info->callbacks->einfo)
/* xgettext:c-format */
(_("%X%P: %pB(%pA): relocation is not supported\n"),
input_bfd, input_section);
break;
case bfd_reloc_dangerous:
(*info->callbacks->reloc_dangerous)
(info, _("GP relative relocation used when GP not defined"),
input_bfd, input_section, r_vaddr - input_section->vma);
break;
default:
abort ();
}
ret = false;
}
}
if (tos != 0)
abort ();
ret = false;
return true;
return ret;
}
/* Do final adjustments to the filehdr and the aouthdr. This routine