/* Compressed section support (intended for debug sections).
   Copyright (C) 2008-2022 Free Software Foundation, Inc.

   This file is part of BFD, the Binary File Descriptor library.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "sysdep.h"
#include <zlib.h>
#include "bfd.h"
#include "libbfd.h"
#include "safe-ctype.h"

#define MAX_COMPRESSION_HEADER_SIZE 24

static bool
decompress_contents (bfd_byte *compressed_buffer,
		     bfd_size_type compressed_size,
		     bfd_byte *uncompressed_buffer,
		     bfd_size_type uncompressed_size)
{
  z_stream strm;
  int rc;

  /* It is possible the section consists of several compressed
     buffers concatenated together, so we uncompress in a loop.  */
  /* PR 18313: The state field in the z_stream structure is supposed
     to be invisible to the user (ie us), but some compilers will
     still complain about it being used without initialisation.  So
     we first zero the entire z_stream structure and then set the fields
     that we need.  */
  memset (& strm, 0, sizeof strm);
  strm.avail_in = compressed_size;
  strm.next_in = (Bytef*) compressed_buffer;
  strm.avail_out = uncompressed_size;
  /* FIXME: strm.avail_in and strm.avail_out are typically unsigned
     int.  Supporting sizes that don't fit in an unsigned int is
     possible but will require some rewriting of this function.  */
  if (strm.avail_in != compressed_size || strm.avail_out != uncompressed_size)
    return false;

  BFD_ASSERT (Z_OK == 0);
  rc = inflateInit (&strm);
  while (strm.avail_in > 0 && strm.avail_out > 0)
    {
      if (rc != Z_OK)
	break;
      strm.next_out = ((Bytef*) uncompressed_buffer
		       + (uncompressed_size - strm.avail_out));
      rc = inflate (&strm, Z_FINISH);
      if (rc != Z_STREAM_END)
	break;
      rc = inflateReset (&strm);
    }
  return inflateEnd (&strm) == Z_OK && rc == Z_OK && strm.avail_out == 0;
}

/* Compress data of the size specified in @var{uncompressed_size}
   and pointed to by @var{uncompressed_buffer} using zlib and store
   as the contents field.  This function assumes the contents
   field was allocated using bfd_malloc() or equivalent.

   Return the uncompressed size if the full section contents is
   compressed successfully.  Otherwise return 0.  */

static bfd_size_type
bfd_compress_section_contents (bfd *abfd, sec_ptr sec,
			       bfd_byte *uncompressed_buffer,
			       bfd_size_type uncompressed_size)
{
  uLong compressed_size;
  bfd_byte *buffer;
  bfd_size_type buffer_size;
  bool decompress;
  int zlib_size = 0;
  int orig_compression_header_size;
  bfd_size_type orig_uncompressed_size;
  unsigned int orig_uncompressed_alignment_pow;
  int header_size = bfd_get_compression_header_size (abfd, NULL);
  bool compressed
    = bfd_is_section_compressed_with_header (abfd, sec,
					     &orig_compression_header_size,
					     &orig_uncompressed_size,
					     &orig_uncompressed_alignment_pow);

  /* Either ELF compression header or the 12-byte, "ZLIB" + 8-byte size,
     overhead in .zdebug* section.  */
  if (!header_size)
     header_size = 12;

  if (compressed)
    {
      /* We shouldn't decompress unsupported compressed section.  */
      if (orig_compression_header_size < 0)
	abort ();

      /* Different compression schemes.  Just move the compressed section
	 contents to the right position. */
      if (orig_compression_header_size == 0)
	{
	  /* Convert it from .zdebug* section.  Get the uncompressed
	     size first.  We need to subtract the 12-byte overhead in
	     .zdebug* section.  Set orig_compression_header_size to
	     the 12-bye overhead.  */
	  orig_compression_header_size = 12;
	  zlib_size = uncompressed_size - 12;
	}
      else
	{
	  /* Convert it to .zdebug* section.  */
	  zlib_size = uncompressed_size - orig_compression_header_size;
	}

      /* Add the header size.  */
      compressed_size = zlib_size + header_size;
    }
  else
    compressed_size = compressBound (uncompressed_size) + header_size;

  /* Uncompress if it leads to smaller size.  */
  if (compressed && compressed_size > orig_uncompressed_size)
    {
      decompress = true;
      buffer_size = orig_uncompressed_size;
    }
  else
    {
      decompress = false;
      buffer_size = compressed_size;
    }
  buffer = (bfd_byte *) bfd_alloc (abfd, buffer_size);
  if (buffer == NULL)
    return 0;

  if (compressed)
    {
      sec->size = orig_uncompressed_size;
      if (decompress)
	{
	  if (!decompress_contents (uncompressed_buffer
				    + orig_compression_header_size,
				    zlib_size, buffer, buffer_size))
	    {
	      bfd_set_error (bfd_error_bad_value);
	      bfd_release (abfd, buffer);
	      return 0;
	    }
	  free (uncompressed_buffer);
	  bfd_set_section_alignment (sec, orig_uncompressed_alignment_pow);

	  sec->contents = buffer;
	  sec->compress_status = COMPRESS_SECTION_DONE;
	  return orig_uncompressed_size;
	}
      else
	{
	  bfd_update_compression_header (abfd, buffer, sec);
	  memmove (buffer + header_size,
		   uncompressed_buffer + orig_compression_header_size,
		   zlib_size);
	}
    }
  else
    {
      if (compress ((Bytef*) buffer + header_size,
		    &compressed_size,
		    (const Bytef*) uncompressed_buffer,
		    uncompressed_size) != Z_OK)
	{
	  bfd_release (abfd, buffer);
	  bfd_set_error (bfd_error_bad_value);
	  return 0;
	}

      compressed_size += header_size;
      /* PR binutils/18087: If compression didn't make the section smaller,
	 just keep it uncompressed.  */
      if (compressed_size < uncompressed_size)
	bfd_update_compression_header (abfd, buffer, sec);
      else
	{
	  /* NOTE: There is a small memory leak here since
	     uncompressed_buffer is malloced and won't be freed.  */
	  bfd_release (abfd, buffer);
	  sec->contents = uncompressed_buffer;
	  sec->compress_status = COMPRESS_SECTION_NONE;
	  return uncompressed_size;
	}
    }

  free (uncompressed_buffer);
  sec->contents = buffer;
  sec->size = compressed_size;
  sec->compress_status = COMPRESS_SECTION_DONE;

  return uncompressed_size;
}

/*
FUNCTION
	bfd_get_full_section_contents

SYNOPSIS
	bool bfd_get_full_section_contents
	  (bfd *abfd, asection *section, bfd_byte **ptr);

DESCRIPTION
	Read all data from @var{section} in BFD @var{abfd}, decompress
	if needed, and store in @var{*ptr}.  If @var{*ptr} is NULL,
	return @var{*ptr} with memory malloc'd by this function.

	Return @code{TRUE} if the full section contents is retrieved
	successfully.  If the section has no contents then this function
	returns @code{TRUE} but @var{*ptr} is set to NULL.
*/

bool
bfd_get_full_section_contents (bfd *abfd, sec_ptr sec, bfd_byte **ptr)
{
  bfd_size_type sz;
  bfd_byte *p = *ptr;
  bool ret;
  bfd_size_type save_size;
  bfd_size_type save_rawsize;
  bfd_byte *compressed_buffer;
  unsigned int compression_header_size;

  if (abfd->direction != write_direction && sec->rawsize != 0)
    sz = sec->rawsize;
  else
    sz = sec->size;
  if (sz == 0)
    {
      *ptr = NULL;
      return true;
    }

  switch (sec->compress_status)
    {
    case COMPRESS_SECTION_NONE:
      if (p == NULL)
	{
	  ufile_ptr filesize = bfd_get_file_size (abfd);
	  if (filesize > 0
	      && filesize < sz
	      /* PR 24753: Linker created sections can be larger than
		 the file size, eg if they are being used to hold stubs.  */
	      && (bfd_section_flags (sec) & SEC_LINKER_CREATED) == 0
	      /* PR 24753: Sections which have no content should also be
		 excluded as they contain no size on disk.  */
	      && (bfd_section_flags (sec) & SEC_HAS_CONTENTS) != 0
	      /* The MMO file format supports its own special compression
		 technique, but it uses COMPRESS_SECTION_NONE when loading
		 a section's contents.  */
	      && bfd_get_flavour (abfd) != bfd_target_mmo_flavour)
	    {
	      /* PR 24708: Avoid attempts to allocate a ridiculous amount
		 of memory.  */
	      bfd_set_error (bfd_error_file_truncated);
	      _bfd_error_handler
		/* xgettext:c-format */
		(_("error: %pB(%pA) section size (%#" PRIx64 " bytes) is larger than file size (%#" PRIx64 " bytes)"),
		 abfd, sec, (uint64_t) sz, (uint64_t) filesize);
	      return false;
	    }
	  p = (bfd_byte *) bfd_malloc (sz);
	  if (p == NULL)
	    {
	      /* PR 20801: Provide a more helpful error message.  */
	      if (bfd_get_error () == bfd_error_no_memory)
		_bfd_error_handler
		  /* xgettext:c-format */
		  (_("error: %pB(%pA) is too large (%#" PRIx64 " bytes)"),
		  abfd, sec, (uint64_t) sz);
	      return false;
	    }
	}

      if (!bfd_get_section_contents (abfd, sec, p, 0, sz))
	{
	  if (*ptr != p)
	    free (p);
	  return false;
	}
      *ptr = p;
      return true;

    case DECOMPRESS_SECTION_SIZED:
      /* Read in the full compressed section contents.  */
      compressed_buffer = (bfd_byte *) bfd_malloc (sec->compressed_size);
      if (compressed_buffer == NULL)
	return false;
      save_rawsize = sec->rawsize;
      save_size = sec->size;
      /* Clear rawsize, set size to compressed size and set compress_status
	 to COMPRESS_SECTION_NONE.  If the compressed size is bigger than
	 the uncompressed size, bfd_get_section_contents will fail.  */
      sec->rawsize = 0;
      sec->size = sec->compressed_size;
      sec->compress_status = COMPRESS_SECTION_NONE;
      ret = bfd_get_section_contents (abfd, sec, compressed_buffer,
				      0, sec->compressed_size);
      /* Restore rawsize and size.  */
      sec->rawsize = save_rawsize;
      sec->size = save_size;
      sec->compress_status = DECOMPRESS_SECTION_SIZED;
      if (!ret)
	goto fail_compressed;

      if (p == NULL)
	p = (bfd_byte *) bfd_malloc (sz);
      if (p == NULL)
	goto fail_compressed;

      compression_header_size = bfd_get_compression_header_size (abfd, sec);
      if (compression_header_size == 0)
	/* Set header size to the zlib header size if it is a
	   SHF_COMPRESSED section.  */
	compression_header_size = 12;
      if (!decompress_contents (compressed_buffer + compression_header_size,
				sec->compressed_size - compression_header_size, p, sz))
	{
	  bfd_set_error (bfd_error_bad_value);
	  if (p != *ptr)
	    free (p);
	fail_compressed:
	  free (compressed_buffer);
	  return false;
	}

      free (compressed_buffer);
      *ptr = p;
      return true;

    case COMPRESS_SECTION_DONE:
      if (sec->contents == NULL)
	return false;
      if (p == NULL)
	{
	  p = (bfd_byte *) bfd_malloc (sz);
	  if (p == NULL)
	    return false;
	  *ptr = p;
	}
      /* PR 17512; file: 5bc29788.  */
      if (p != sec->contents)
	memcpy (p, sec->contents, sz);
      return true;

    default:
      abort ();
    }
}

/*
FUNCTION
	bfd_cache_section_contents

SYNOPSIS
	void bfd_cache_section_contents
	  (asection *sec, void *contents);

DESCRIPTION
	Stash @var(contents) so any following reads of @var(sec) do
	not need to decompress again.
*/

void
bfd_cache_section_contents (asection *sec, void *contents)
{
  if (sec->compress_status == DECOMPRESS_SECTION_SIZED)
    sec->compress_status = COMPRESS_SECTION_DONE;
  sec->contents = contents;
  sec->flags |= SEC_IN_MEMORY;
}

/*
FUNCTION
	bfd_is_section_compressed_with_header

SYNOPSIS
	bool bfd_is_section_compressed_with_header
	  (bfd *abfd, asection *section,
	  int *compression_header_size_p,
	  bfd_size_type *uncompressed_size_p,
	  unsigned int *uncompressed_alignment_power_p);

DESCRIPTION
	Return @code{TRUE} if @var{section} is compressed.  Compression
	header size is returned in @var{compression_header_size_p},
	uncompressed size is returned in @var{uncompressed_size_p}
	and the uncompressed data alignement power is returned in
	@var{uncompressed_align_pow_p}.  If compression is
	unsupported, compression header size is returned with -1
	and uncompressed size is returned with 0.
*/

bool
bfd_is_section_compressed_with_header (bfd *abfd, sec_ptr sec,
				       int *compression_header_size_p,
				       bfd_size_type *uncompressed_size_p,
				       unsigned int *uncompressed_align_pow_p)
{
  bfd_byte header[MAX_COMPRESSION_HEADER_SIZE];
  int compression_header_size;
  int header_size;
  unsigned int saved = sec->compress_status;
  bool compressed;

  *uncompressed_align_pow_p = 0;

  compression_header_size = bfd_get_compression_header_size (abfd, sec);
  if (compression_header_size > MAX_COMPRESSION_HEADER_SIZE)
    abort ();
  header_size = compression_header_size ? compression_header_size : 12;

  /* Don't decompress the section.  */
  sec->compress_status = COMPRESS_SECTION_NONE;

  /* Read the header.  */
  if (bfd_get_section_contents (abfd, sec, header, 0, header_size))
    {
      if (compression_header_size == 0)
	/* In this case, it should be "ZLIB" followed by the uncompressed
	   section size, 8 bytes in big-endian order.  */
	compressed = startswith ((char*) header , "ZLIB");
      else
	compressed = true;
    }
  else
    compressed = false;

  *uncompressed_size_p = sec->size;
  if (compressed)
    {
      if (compression_header_size != 0)
	{
	  if (!bfd_check_compression_header (abfd, header, sec,
					     uncompressed_size_p,
					     uncompressed_align_pow_p))
	    compression_header_size = -1;
	}
      /* Check for the pathalogical case of a debug string section that
	 contains the string ZLIB.... as the first entry.  We assume that
	 no uncompressed .debug_str section would ever be big enough to
	 have the first byte of its (big-endian) size be non-zero.  */
      else if (strcmp (sec->name, ".debug_str") == 0
	       && ISPRINT (header[4]))
	compressed = false;
      else
	*uncompressed_size_p = bfd_getb64 (header + 4);
    }

  /* Restore compress_status.  */
  sec->compress_status = saved;
  *compression_header_size_p = compression_header_size;
  return compressed;
}

/*
FUNCTION
	bfd_is_section_compressed

SYNOPSIS
	bool bfd_is_section_compressed
	  (bfd *abfd, asection *section);

DESCRIPTION
	Return @code{TRUE} if @var{section} is compressed.
*/

bool
bfd_is_section_compressed (bfd *abfd, sec_ptr sec)
{
  int compression_header_size;
  bfd_size_type uncompressed_size;
  unsigned int uncompressed_align_power;
  return (bfd_is_section_compressed_with_header (abfd, sec,
						 &compression_header_size,
						 &uncompressed_size,
						 &uncompressed_align_power)
	  && compression_header_size >= 0
	  && uncompressed_size > 0);
}

/*
FUNCTION
	bfd_init_section_decompress_status

SYNOPSIS
	bool bfd_init_section_decompress_status
	  (bfd *abfd, asection *section);

DESCRIPTION
	Record compressed section size, update section size with
	decompressed size and set compress_status to
	DECOMPRESS_SECTION_SIZED.

	Return @code{FALSE} if the section is not a valid compressed
	section.  Otherwise, return @code{TRUE}.
*/

bool
bfd_init_section_decompress_status (bfd *abfd, sec_ptr sec)
{
  bfd_byte header[MAX_COMPRESSION_HEADER_SIZE];
  int compression_header_size;
  int header_size;
  bfd_size_type uncompressed_size;
  unsigned int uncompressed_alignment_power = 0;
  z_stream strm;

  compression_header_size = bfd_get_compression_header_size (abfd, sec);
  if (compression_header_size > MAX_COMPRESSION_HEADER_SIZE)
    abort ();
  header_size = compression_header_size ? compression_header_size : 12;

  /* Read the header.  */
  if (sec->rawsize != 0
      || sec->contents != NULL
      || sec->compress_status != COMPRESS_SECTION_NONE
      || !bfd_get_section_contents (abfd, sec, header, 0, header_size))
    {
      bfd_set_error (bfd_error_invalid_operation);
      return false;
    }

  if (compression_header_size == 0)
    {
      /* In this case, it should be "ZLIB" followed by the uncompressed
	 section size, 8 bytes in big-endian order.  */
      if (! startswith ((char*) header, "ZLIB"))
	{
	  bfd_set_error (bfd_error_wrong_format);
	  return false;
	}
      uncompressed_size = bfd_getb64 (header + 4);
    }
  else if (!bfd_check_compression_header (abfd, header, sec,
					  &uncompressed_size,
					  &uncompressed_alignment_power))
    {
      bfd_set_error (bfd_error_wrong_format);
      return false;
    }

  /* PR28530, reject sizes unsupported by decompress_contents.  */
  strm.avail_in = sec->size;
  strm.avail_out = uncompressed_size;
  if (strm.avail_in != sec->size || strm.avail_out != uncompressed_size)
    {
      bfd_set_error (bfd_error_nonrepresentable_section);
      return false;
    }

  sec->compressed_size = sec->size;
  sec->size = uncompressed_size;
  bfd_set_section_alignment (sec, uncompressed_alignment_power);
  sec->compress_status = DECOMPRESS_SECTION_SIZED;

  return true;
}

/*
FUNCTION
	bfd_init_section_compress_status

SYNOPSIS
	bool bfd_init_section_compress_status
	  (bfd *abfd, asection *section);

DESCRIPTION
	If open for read, compress section, update section size with
	compressed size and set compress_status to COMPRESS_SECTION_DONE.

	Return @code{FALSE} if the section is not a valid compressed
	section.  Otherwise, return @code{TRUE}.
*/

bool
bfd_init_section_compress_status (bfd *abfd, sec_ptr sec)
{
  bfd_size_type uncompressed_size;
  bfd_byte *uncompressed_buffer;

  /* Error if not opened for read.  */
  if (abfd->direction != read_direction
      || sec->size == 0
      || sec->rawsize != 0
      || sec->contents != NULL
      || sec->compress_status != COMPRESS_SECTION_NONE)
    {
      bfd_set_error (bfd_error_invalid_operation);
      return false;
    }

  /* Read in the full section contents and compress it.  */
  uncompressed_size = sec->size;
  uncompressed_buffer = (bfd_byte *) bfd_malloc (uncompressed_size);
  /* PR 21431 */
  if (uncompressed_buffer == NULL)
    return false;

  if (!bfd_get_section_contents (abfd, sec, uncompressed_buffer,
				 0, uncompressed_size))
    return false;

  uncompressed_size = bfd_compress_section_contents (abfd, sec,
						     uncompressed_buffer,
						     uncompressed_size);
  return uncompressed_size != 0;
}

/*
FUNCTION
	bfd_compress_section

SYNOPSIS
	bool bfd_compress_section
	  (bfd *abfd, asection *section, bfd_byte *uncompressed_buffer);

DESCRIPTION
	If open for write, compress section, update section size with
	compressed size and set compress_status to COMPRESS_SECTION_DONE.

	Return @code{FALSE} if compression fail.  Otherwise, return
	@code{TRUE}.
*/

bool
bfd_compress_section (bfd *abfd, sec_ptr sec, bfd_byte *uncompressed_buffer)
{
  bfd_size_type uncompressed_size = sec->size;

  /* Error if not opened for write.  */
  if (abfd->direction != write_direction
      || uncompressed_size == 0
      || uncompressed_buffer == NULL
      || sec->contents != NULL
      || sec->compressed_size != 0
      || sec->compress_status != COMPRESS_SECTION_NONE)
    {
      bfd_set_error (bfd_error_invalid_operation);
      return false;
    }

  /* Compress it.  */
  return bfd_compress_section_contents (abfd, sec, uncompressed_buffer,
					uncompressed_size) != 0;
}
