libctf, ld: diagnose corrupted CTF header cth_strlen

The last section in a CTF dict is the string table, at an offset
represented by the cth_stroff header field.  Its length is recorded in
the next field, cth_strlen, and the two added together are taken as the
size of the CTF dict.  Upon opening a dict, we check that none of the
header offsets exceed this size, and we check when uncompressing a
compressed dict that the result of the uncompression is the same length:
but CTF dicts need not be compressed, and short ones are not.
Uncompressed dicts just use the ctf_size without checking it.  This
field is thankfully almost unused: it is mostly used when reserializing
a dict, which can't be done to dicts read off disk since they're
read-only.

However, when opening an uncompressed foreign-endian dict we have to
copy it out of the mmaped region it is stored in so we can endian-
swap it, and we use ctf_size when doing that.  When the cth_strlen is
corrupt, this can overrun.

Fix this by checking the ctf_size in all uncompressed cases, just as we
already do in the compressed case.  Add a new test.

This came to light because various corrupted-CTF raw-asm tests had an
incorrect cth_strlen: fix all of them so they produce the expected
error again.

libctf/
	PR libctf/28933
	* ctf-open.c (ctf_bufopen_internal): Always check uncompressed
	CTF dict sizes against the section size in case the cth_strlen is
	corrupt.

ld/
	PR libctf/28933
	* testsuite/ld-ctf/diag-strlen-invalid.*: New test,
	derived from diag-cttname-invalid.s.
	* testsuite/ld-ctf/diag-cttname-invalid.s: Fix incorrect cth_strlen.
	* testsuite/ld-ctf/diag-cttname-null.s: Likewise.
	* testsuite/ld-ctf/diag-cuname.s: Likewise.
	* testsuite/ld-ctf/diag-parlabel.s: Likewise.
	* testsuite/ld-ctf/diag-parname.s: Likewise.
This commit is contained in:
Nick Alcock 2022-03-18 00:49:11 +00:00
parent 203bfa2f6b
commit 84f5c557a4
8 changed files with 85 additions and 23 deletions

View file

@ -15,7 +15,7 @@
.long 0x8
.long 0x10
.long 0x40
.long 0x42
.long 0x37
.long 0x1
.long 0x7
.long 0x7

View file

@ -15,7 +15,7 @@
.long 0x8
.long 0x10
.long 0x40
.long 0x42
.long 0x37
.long 0x1
.long 0x7
.long 0x7

View file

@ -15,7 +15,7 @@
.long 0x8
.long 0x10
.long 0x40
.long 0x42
.long 0x37
.long 0x1
.long 0x7
.long 0x7

View file

@ -15,7 +15,7 @@
.long 0x8
.long 0x10
.long 0x40
.long 0x42
.long 0x37
.long 0x1
.long 0x7
.long 0x7

View file

@ -15,7 +15,7 @@
.long 0x8
.long 0x10
.long 0x40
.long 0x42
.long 0x37
.long 0x1
.long 0x7
.long 0x7

View file

@ -0,0 +1,5 @@
#as:
#source: diag-strlen-invalid.s
#ld: -shared
#name: Diagnostics - String offset invalid.
#warning: .* byte long CTF dictionary overruns .* byte long CTF section

View file

@ -0,0 +1,44 @@
.file "A.c"
.section .ctf,"",@progbits
.Lctf0:
.2byte 0xdff2
.byte 0x4
.byte 0
.long 0
.long 0
.long 0x9
.long 0
.long 0
.long 0x4
.long 0x4
.long 0x8
.long 0x8
.long 0x10
.long 0x40
.long 0x42
.long 0x1
.long 0x7
.long 0x7
.long 0x1
.long 0xff00
.long 0x1a000001
.long 0x8
.long 0x5
.long 0
.long 0x3
.long 0x3
.long 0x26000000
.long 0x6
.long 0
.long 0xe000000
.long 0x2
.ascii "\0"
.ascii "A\0"
.ascii "B\0"
.ascii "b\0"
.ascii "a\0"
.ascii "/usr/src/binutils-gdb/ld/testsuite/ld-ctf/A.c\0"
.text
.comm a,8,8
.ident "GCC: (GNU) 8.3.1 20191121 (Red Hat 8.3.1-5.0.1)"
.section .note.GNU-stack,"",@progbits

View file

@ -1517,7 +1517,19 @@ ctf_bufopen_internal (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
goto bad;
}
}
else if (foreign_endian)
else
{
if (_libctf_unlikely_ (ctfsect->cts_size < hdrsz + fp->ctf_size))
{
ctf_err_warn (NULL, 0, ECTF_CORRUPT,
_("%lu byte long CTF dictionary overruns %lu byte long CTF section"),
(unsigned long) ctfsect->cts_size,
(unsigned long) (hdrsz + fp->ctf_size));
err = ECTF_CORRUPT;
goto bad;
}
if (foreign_endian)
{
if ((fp->ctf_base = malloc (fp->ctf_size)) == NULL)
{
@ -1531,13 +1543,14 @@ ctf_bufopen_internal (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
}
else
{
/* We are just using the section passed in -- but its header may be an old
version. Point ctf_buf past the old header, and never touch it
again. */
/* We are just using the section passed in -- but its header may
be an old version. Point ctf_buf past the old header, and
never touch it again. */
fp->ctf_base = (unsigned char *) ctfsect->cts_data;
fp->ctf_dynbase = NULL;
fp->ctf_buf = fp->ctf_base + hdrsz;
}
}
/* Once we have uncompressed and validated the CTF data buffer, we can
proceed with initializing the ctf_dict_t we allocated above.