ctf-archive sanity checks

Nick Alcock nick.alcock@oracle.com
Mon Nov 3 17:04:06 GMT 2025


On 3 Nov 2025, Alan Modra verbalised:

> Existing code checks that the first uint64_t ctfa_magic field is
> available before reading but neglects to check that the last uint64_t
> ctfa_ctfs is available before reading it in ctf_arc_bufpreamble.
> ctf_arc_bufopen sets up a pointer to the struct ctf_archive in
> ctf_new_archive_internal.  Extend the check to cover the entire struct.

Thanks!

I have a more robust fix for this coming in CTFv4, range-checking
absolutely everything (at just the same time as I obsolete creating this
archive format at all), but this seems doable now, with adjustments I'll
be adding shortly.

> Applied.

I'll throw it through my test matrix too :)

> 	PR 33548
> 	PR 33549
> 	* ctf-archive.c (ctf_arc_bufpreamble, ctf_arc_bufopen): Check
> 	that buffer contains at least an entire struct ctf_archive
> 	when accessing such a struct.
>
> diff --git a/libctf/ctf-archive.c b/libctf/ctf-archive.c
> index 6c4595fcc84..63184e66244 100644
> --- a/libctf/ctf-archive.c
> +++ b/libctf/ctf-archive.c
> @@ -395,7 +395,7 @@ const ctf_preamble_t *
>  ctf_arc_bufpreamble (const ctf_sect_t *ctfsect)
>  {
>    if (ctfsect->cts_data != NULL
> -      && ctfsect->cts_size > sizeof (uint64_t)
> +      && ctfsect->cts_size >= sizeof (struct ctf_archive)
>        && (le64toh ((*(uint64_t *) ctfsect->cts_data)) == CTFA_MAGIC))
>      {
>        struct ctf_archive *arc = (struct ctf_archive *) ctfsect->cts_data;

I don't think that's quite enough.  Right below that, we do

      return (const ctf_preamble_t *) ((char *) arc + le64toh (arc->ctfa_ctfs)
                                       + sizeof (uint64_t));

which is just off the end of struct ctf_archive.  So we should check

ctfsect->cts_size >= (sizeof (struct ctf_archive) + sizeof (uint64_t)).

I think. A theoretical (but not malformed!) zero-element archive will
fail this new check where it currently passes and hands back a pointer
to uninitialized garbage. The caller is absolutely not ready to deal
with either this situation or the range-check failing at all, and will
dereference garbage and check it for a flag value.

I'll whip a fix up. (No real point whipping up a test, the code to write
these archives is not long for this world so I'd just have to remove it
again...)

> @@ -418,7 +418,7 @@ ctf_arc_bufopen (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
>    ctf_dict_t *fp = NULL;
>  
>    if (ctfsect->cts_data != NULL
> -      && ctfsect->cts_size > sizeof (uint64_t)
> +      && ctfsect->cts_size >= sizeof (struct ctf_archive)
>        && (le64toh ((*(uint64_t *) ctfsect->cts_data)) == CTFA_MAGIC))
>      {
>        /* The archive is mmappable, so this operation is trivial.

It's reasonable to verify that the archive we return is entirely
in-bounds, so this is fine.

-- 
NULL && (void)


More information about the Binutils mailing list