CVE-2022-38533 (C, published 2022-08-26T00:15Z)
Description: value: In GNU Binutils before 2.40, there is a heap-buffer-overflow in the error function bfd_getl32 when called from the strip_main function in strip-new via a crafted file.
Severity: nan (CVSS v3 base score: 5.5)
Weakness: CWE-787 \u2014 Out-of-bounds Write
The product writes data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/bminor/binutils-gdb
Files changed:
bfd/elf.c
Commit message: PR29495, rewrite_elf_program_header looping

This patch, in order of significance:
1) Replaces some macros with inline functions.
2) Those inline functions catch and avoid arithmetic overflows when
   comparing addresses.
3) When assigning sections to segments (IS_SECTION_IN_INPUT_SEGMENT)
   use bed->want_p_paddr_set_to_zero to decide whether lma vs p_paddr
   or vma vs p_vaddr should be tested.  When remapping, use the same
   test, and use is_note rather than the more restrictive
   IS_COREFILE_NOTE.

It's important that the later tests not be more restrictive.  If they
are it can lead to the situation triggered by the testcases, where a
section seemingly didn't fit and thus needed a new mapping.  It didn't
fit the new mapping either, and this repeated until memory exhausted.

	PR 29495
	* elf.c (SEGMENT_END, SECTION_SIZE, IS_CONTAINED_BY_VMA): Delete.
	(IS_CONTAINED_BY_LMA, IS_NOTE, IS_COREFILE_NOTE): Delete.
	(segment_size, segment_end, section_size): New inline function.
	(is_contained_by, is_note): Likewise.
	(rewrite_elf_program_header): Use new functions.


Vulnerable code:
  /* Returns the end address of the segment + 1.  */
#define SEGMENT_END(segment, start)					\
  (start + (segment->p_memsz > segment->p_filesz			\
	    ? segment->p_memsz : segment->p_filesz))

#define SECTION_SIZE(section, segment)					\
  (((section->flags & (SEC_HAS_CONTENTS | SEC_THREAD_LOCAL))		\
    != SEC_THREAD_LOCAL || segment->p_type == PT_TLS)			\
   ? section->size : 0)

  /* Returns TRUE if the given section is contained within
     the given segment.  VMA addresses are compared.  */
#define IS_CONTAINED_BY_VMA(section, segment, opb)			\
  (section->vma * (opb) >= segment->p_vaddr				\
   && (section->vma * (opb) + SECTION_SIZE (section, segment)		\
       <= (SEGMENT_END (segment, segment->p_vaddr))))

  /* Returns TRUE if the given section is contained within
     the given segment.  LMA addresses are compared.  */
#define IS_CONTAINED_BY_LMA(section, segment, base, opb)		\
  (section->lma * (opb) >= base						\
   && (section->lma + SECTION_SIZE (section, segment) / (opb) >= section->lma) \
   && (section->lma * (opb) + SECTION_SIZE (section, segment)		\
       <= SEGMENT_END (segment, base)))

  /* Handle PT_NOTE segment.  */
#define IS_NOTE(p, s)							\
  (p->p_type == PT_NOTE							\
   && elf_section_type (s) == SHT_NOTE					\
   && (bfd_vma) s->filepos >= p->p_offset				\
   && ((bfd_vma) s->filepos + s->size					\
       <= p->p_offset + p->p_filesz))

  /* Special case: corefile "NOTE" section containing regs, prpsinfo
     etc.  */
#define IS_COREFILE_NOTE(p, s)						\
  (IS_NOTE (p, s)							\
   && bfd_get_format (ibfd) == bfd_core					\
   && s->vma == 0							\
   && s->lma == 0)

  ((((segment->p_paddr							\
      ? IS_CONTAINED_BY_LMA (section, segment, segment->p_paddr, opb)	\
      : IS_CONTAINED_BY_VMA (section, segment, opb))			\
    || IS_NOTE (segment, section))					\
       || SECTION_SIZE (section, segment) > 0				\
  (seg1->field >= SEGMENT_END (seg2, seg2->field))
	      extra_length = (SEGMENT_END (segment, segment->p_vaddr)
			      - SEGMENT_END (segment2, segment2->p_vaddr));
	      extra_length = (SEGMENT_END (segment2, segment2->p_vaddr)
			      - SEGMENT_END (segment, segment->p_vaddr));
	      if (IS_CONTAINED_BY_LMA (output_section, segment, map->p_paddr,
				       opb)
		  || IS_COREFILE_NOTE (segment, section)
		  || (bed->want_p_paddr_set_to_zero
		      && IS_CONTAINED_BY_VMA (output_section, segment, opb)))
	      if (IS_CONTAINED_BY_LMA (output_section, segment, map->p_paddr,
				       opb)
		  || IS_COREFILE_NOTE (segment, section))
#undef SEGMENT_END
#undef SECTION_SIZE
#undef IS_CONTAINED_BY_VMA
#undef IS_CONTAINED_BY_LMA
#undef IS_NOTE
#undef IS_COREFILE_NOTE

Fixed code:
static inline bfd_vma
segment_size (Elf_Internal_Phdr *segment)
{
  return (segment->p_memsz > segment->p_filesz
	  ? segment->p_memsz : segment->p_filesz);
}


/* Returns the end address of the segment + 1.  */
static inline bfd_vma
segment_end (Elf_Internal_Phdr *segment, bfd_vma start)
{
  return start + segment_size (segment);
}

static inline bfd_size_type
section_size (asection *section, Elf_Internal_Phdr *segment)
{
  if ((section->flags & SEC_HAS_CONTENTS) != 0
      || (section->flags & SEC_THREAD_LOCAL) == 0
      || segment->p_type == PT_TLS)
    return section->size;
  return 0;
}

/* Returns TRUE if the given section is contained within the given
   segment.  LMA addresses are compared against PADDR when
   bed->want_p_paddr_set_to_zero is false, VMA against VADDR when true.  */
static bool
is_contained_by (asection *section, Elf_Internal_Phdr *segment,
		 bfd_vma paddr, bfd_vma vaddr, unsigned int opb,
		 const struct elf_backend_data *bed)
{
  bfd_vma seg_addr = !bed->want_p_paddr_set_to_zero ? paddr : vaddr;
  bfd_vma addr = !bed->want_p_paddr_set_to_zero ? section->lma : section->vma;
  bfd_vma octet;
  if (_bfd_mul_overflow (addr, opb, &octet))
    return false;
  /* The third and fourth lines below are testing that the section end
     address is within the segment.  It's written this way to avoid
     overflow.  Add seg_addr + section_size to both sides of the
     inequality to make it obvious.  */
  return (octet >= seg_addr
	  && segment_size (segment) >= section_size (section, segment)
	  && (octet - seg_addr
	      <= segment_size (segment) - section_size (section, segment)));
}

/* Handle PT_NOTE segment.  */
static bool
is_note (Elf_Internal_Phdr *p, asection *s)
{
  return (p->p_type == PT_NOTE
	  && elf_section_type (s) == SHT_NOTE
	  && (ufile_ptr) s->filepos >= p->p_offset
	  && p->p_filesz >= s->size
	  && ((ufile_ptr) s->filepos - p->p_offset
	      <= p->p_filesz - s->size));
}

  (((is_contained_by (section, segment, segment->p_paddr,		\
		      segment->p_vaddr, opb, bed)			\
    || is_note (segment, section))					\
       || section_size (section, segment) > 0				\
  (seg1->field >= segment_end (seg2, seg2->field))
	      extra_length = (segment_end (segment, segment->p_vaddr)
			      - segment_end (segment2, segment2->p_vaddr));
	      extra_length = (segment_end (segment2, segment2->p_vaddr)
			      - segment_end (segment, segment->p_vaddr));
	      if (is_contained_by (output_section, segment, map->p_paddr,
				   map->p_paddr + map->p_vaddr_offset, opb, bed)
		  || is_note (segment, section))
	      if (is_contained_by (output_section, segment, map->p_paddr,
				   map->p_paddr + map->p_vaddr_offset, opb, bed)
		  || is_note (segment, section))