buffer overflow in loongarch_elf_add_sub_reloc_uleb128

Message ID aciRkavEWp-G6O3h@squeak.grove.modra.org
State New
Headers
Series buffer overflow in loongarch_elf_add_sub_reloc_uleb128 |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 fail Patch failed to apply

Commit Message

Alan Modra March 29, 2026, 2:42 a.m. UTC
  oss-fuzz managed to trigger a buffer overflow processing a bogus
leb128.  Well, the leb128 encoding can be arbitrarily long so this
isn't surprising at all.  If we want to guard against user input
triggering buffer overflows then we'd need to ensure input is
terminated somehow, or do as this patch does.

Remove _bfd_read_unsigned_leb128 and _bfd_read_signed_leb128,
replacing all uses of these functions with _bfd_safe_read_leb128.

	* libbfd.c (_bfd_read_unsigned_leb128): Delete.
	(_bfd_read_signed_leb128): Delete.
	* libbfd-in.h: Remove declarations too.
	* libbfd.h: Regenerate.
	* elf32-msp430.c (msp430_final_link_relocate): Replace
	_bfd_read_unsigned_leb128 with _bfd_safe_read_leb128.
	* elf32-nds32.c (nds32_elf_relax_delete_blanks): Likewise.
	* elfnn-loongarch.c (perform_relocation): Likewise.
	(loongarch_elf_relocate_section): Likewise.
	* elfnn-riscv.c (perform_relocation): Likewise.
	* elfxx-loongarch.c (loongarch_elf_add_sub_reloc_uleb128): Likewise.
	(loongarch_write_unsigned_leb128): Make "len" a size_t.
	* elfxx-loongarch.h (loongarch_write_unsigned_leb128): Adjust.
  

Patch

diff --git a/bfd/elf32-msp430.c b/bfd/elf32-msp430.c
index d7d7730063f..5123a138b0b 100644
--- a/bfd/elf32-msp430.c
+++ b/bfd/elf32-msp430.c
@@ -893,13 +893,16 @@  msp430_final_link_relocate (reloc_howto_type *	   howto,
       bfd_byte *endp, *p;
       unsigned int val = relocation;
 
-      _bfd_read_unsigned_leb128 (input_bfd, contents + rel->r_offset, &len);
+      p = contents + rel->r_offset;
+      endp = contents + input_section->size;
+      _bfd_safe_read_leb128 (input_bfd, &p, false, endp);
 
       /* Clean the contents value to zero.  Do not reduce the length.  */
+      endp = p - 1;
       p = contents + rel->r_offset;
-      endp = (p + len) - 1;
+      len = endp + 1 - p;
       memset (p, 0x80, len - 1);
-      *(endp) = 0;
+      *endp = 0;
 
       /* Get the length of the new uleb128 value.  */
       do
diff --git a/bfd/elf32-nds32.c b/bfd/elf32-nds32.c
index f308f2f16e1..2a76fd70462 100644
--- a/bfd/elf32-nds32.c
+++ b/bfd/elf32-nds32.c
@@ -9322,13 +9322,13 @@  nds32_elf_relax_delete_blanks (bfd *abfd, asection *sec,
 	  else if (ELF32_R_TYPE (irel->r_info) == R_NDS32_DIFF_ULEB128
 	      && isym[ELF32_R_SYM (irel->r_info)].st_shndx == sec_shndx)
 	    {
-	      bfd_vma val = 0;
-	      unsigned int len = 0;
+	      bfd_vma val;
 	      unsigned long before, between;
 	      bfd_byte *endp, *p;
 
-	      val = _bfd_read_unsigned_leb128 (abfd, contents + irel->r_offset,
-					       &len);
+	      p = contents + irel->r_offset;
+	      endp = contents + sec->size;
+	      val = _bfd_safe_read_leb128 (abfd, &p, false, endp);
 
 	      before = get_nds32_elf_blank_total (&blank_t, irel->r_addend, 0);
 	      between = get_nds32_elf_blank_total (&blank_t,
@@ -9336,10 +9336,10 @@  nds32_elf_relax_delete_blanks (bfd *abfd, asection *sec,
 	      if (between == before)
 		goto done_adjust_diff;
 
+	      endp = p - 1;
 	      p = contents + irel->r_offset;
-	      endp = p + len -1;
-	      memset (p, 0x80, len);
-	      *(endp) = 0;
+	      memset (p, 0x80, endp - p);
+	      *endp = 0;
 	      p = write_uleb128 (p, val - (between - before)) - 1;
 	      if (p < endp)
 		*p |= 0x80;
diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
index 2c67cccd9fb..a7149691ffd 100644
--- a/bfd/elfnn-loongarch.c
+++ b/bfd/elfnn-loongarch.c
@@ -3110,9 +3110,11 @@  perform_relocation (const Elf_Internal_Rela *rel, asection *input_section,
     case R_LARCH_ADD_ULEB128:
     case R_LARCH_SUB_ULEB128:
       {
-	unsigned int len = 0;
-	/* Before write uleb128, first read it to get it's length.  */
-	_bfd_read_unsigned_leb128 (input_bfd, contents + rel->r_offset, &len);
+	/* Before write uleb128, first read it to get its length.  */
+	bfd_byte *p = contents + rel->r_offset;
+	bfd_byte *end = contents + input_section->size;
+	_bfd_safe_read_leb128 (input_bfd, &p, false, end);
+	size_t len = p - (contents + rel->r_offset);
 	loongarch_write_unsigned_leb128 (contents + rel->r_offset, len, value);
 	r = bfd_reloc_ok;
 	break;
@@ -3805,9 +3807,11 @@  loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 	case R_LARCH_SUB_ULEB128:
 	  {
 	    /* Get the value and length of the uleb128 data.  */
-	    unsigned int len = 0;
-	    bfd_vma old_value = _bfd_read_unsigned_leb128 (input_bfd,
-				    contents + rel->r_offset, &len);
+	    bfd_byte *p = contents + rel->r_offset;
+	    bfd_byte *end = contents + input_section->size;
+	    bfd_vma old_value = _bfd_safe_read_leb128 (input_bfd, &p,
+						       false, end);
+	    size_t len = p - (contents + rel->r_offset);
 
 	    if (R_LARCH_ADD_ULEB128 == ELFNN_R_TYPE (rel->r_info))
 	      relocation = old_value + relocation + rel->r_addend;
diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
index d3963ec9a58..97b469bd7db 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -2069,20 +2069,22 @@  perform_relocation (const reloc_howto_type *howto,
     /* R_RISCV_SET_ULEB128 won't go into here.  */
     case R_RISCV_SUB_ULEB128:
       {
-	unsigned int len = 0;
-	_bfd_read_unsigned_leb128 (input_bfd, contents + rel->r_offset, &len);
+	bfd_byte *p = contents + rel->r_offset;
+	bfd_byte *endp = contents + input_section->size;
+	_bfd_safe_read_leb128 (input_bfd, &p, false, endp);
 
 	/* Clean the contents value to zero (0x80), but keep the original
 	   length.  */
-	bfd_byte *p = contents + rel->r_offset;
-	bfd_byte *endp = p + len - 1;
+	endp = p - 1;
+	p = contents + rel->r_offset;
+	size_t len = endp + 1 - p;
 	memset (p, 0x80, len - 1);
-	*(endp) = 0;
+	*endp = 0;
 
-	/* Make sure the length of the new uleb128 value within the
+	/* Make sure the length of the new uleb128 value fits within the
 	   original (available) length.  */
-	unsigned int new_len = 0;
-	unsigned int val_t = value;
+	size_t new_len = 0;
+	bfd_vma val_t = value;
 	do
 	  {
 	    new_len++;
diff --git a/bfd/elfxx-loongarch.c b/bfd/elfxx-loongarch.c
index c65682e22b1..2dd470ddafb 100644
--- a/bfd/elfxx-loongarch.c
+++ b/bfd/elfxx-loongarch.c
@@ -2409,9 +2409,13 @@  loongarch_elf_add_sub_reloc_uleb128 (bfd *abfd,
 				  input_section, octets))
     return bfd_reloc_outofrange;
 
-  unsigned int len = 0;
-  bfd_byte *p = data + reloc_entry->address;
-  bfd_vma old_value = _bfd_read_unsigned_leb128 (abfd, p, &len);
+  bfd_byte *contents = data;
+  bfd_byte *p = contents + octets;
+  bfd_byte *endp = contents + input_section->size;
+  bfd_vma old_value = _bfd_safe_read_leb128 (abfd, &p, false, endp);
+  endp = p;
+  p = contents + octets;
+  size_t len = endp - p;
 
   switch (howto->type)
     {
@@ -2437,7 +2441,7 @@  loongarch_elf_add_sub_reloc_uleb128 (bfd *abfd,
    LEN is the uleb128 value length.
    Return a pointer to the byte following the last byte that was written.  */
 bfd_byte *
-loongarch_write_unsigned_leb128 (bfd_byte *p, unsigned int len, bfd_vma value)
+loongarch_write_unsigned_leb128 (bfd_byte *p, size_t len, bfd_vma value)
 {
   bfd_byte c;
   do
diff --git a/bfd/elfxx-loongarch.h b/bfd/elfxx-loongarch.h
index 3a6e8ea0950..400b7377ccd 100644
--- a/bfd/elfxx-loongarch.h
+++ b/bfd/elfxx-loongarch.h
@@ -43,8 +43,7 @@  void
 bfd_elf64_loongarch_set_data_segment_info (struct bfd_link_info *, int *);
 
 bfd_byte *
-loongarch_write_unsigned_leb128 (bfd_byte *p, unsigned int len, bfd_vma value)
-  ATTRIBUTE_HIDDEN;
+loongarch_write_unsigned_leb128 (bfd_byte *, size_t, bfd_vma) ATTRIBUTE_HIDDEN;
 
 /* TRUE if this is a PLT reference to a local IFUNC.  */
 #define PLT_LOCAL_IFUNC_P(INFO, H) \
diff --git a/bfd/libbfd-in.h b/bfd/libbfd-in.h
index 4cc391fad47..7b4c93228a7 100644
--- a/bfd/libbfd-in.h
+++ b/bfd/libbfd-in.h
@@ -826,10 +826,6 @@  extern void bfd_section_already_linked_table_traverse
   (bool (*) (struct bfd_section_already_linked_hash_entry *,
 		    void *), void *) ATTRIBUTE_HIDDEN;
 
-extern bfd_vma _bfd_read_unsigned_leb128
-  (bfd *, bfd_byte *, unsigned int *) ATTRIBUTE_HIDDEN;
-extern bfd_signed_vma _bfd_read_signed_leb128
-  (bfd *, bfd_byte *, unsigned int *) ATTRIBUTE_HIDDEN;
 extern bfd_vma _bfd_safe_read_leb128
   (bfd *, bfd_byte **, bool, const bfd_byte * const) ATTRIBUTE_HIDDEN;
 extern bfd_byte * _bfd_write_unsigned_leb128
diff --git a/bfd/libbfd.c b/bfd/libbfd.c
index 6311ef49f6e..0e6a6cae779 100644
--- a/bfd/libbfd.c
+++ b/bfd/libbfd.c
@@ -1385,37 +1385,6 @@  bfd_generic_is_local_label_name (bfd *abfd, const char *name)
   return name[0] == locals_prefix;
 }
 
-/* Helper function for reading uleb128 encoded data.  */
-
-bfd_vma
-_bfd_read_unsigned_leb128 (bfd *abfd ATTRIBUTE_UNUSED,
-			   bfd_byte *buf,
-			   unsigned int *bytes_read_ptr)
-{
-  bfd_vma result;
-  unsigned int num_read;
-  unsigned int shift;
-  bfd_byte byte;
-
-  result = 0;
-  shift = 0;
-  num_read = 0;
-  do
-    {
-      byte = bfd_get_8 (abfd, buf);
-      buf++;
-      num_read++;
-      if (shift < 8 * sizeof (result))
-	{
-	  result |= (((bfd_vma) byte & 0x7f) << shift);
-	  shift += 7;
-	}
-    }
-  while (byte & 0x80);
-  *bytes_read_ptr = num_read;
-  return result;
-}
-
 /* Read in a LEB128 encoded value from ABFD starting at *PTR.
    If SIGN is true, return a signed LEB128 value.
    *PTR is incremented by the number of bytes read.
@@ -1453,39 +1422,6 @@  _bfd_safe_read_leb128 (bfd *abfd ATTRIBUTE_UNUSED,
   return result;
 }
 
-/* Helper function for reading sleb128 encoded data.  */
-
-bfd_signed_vma
-_bfd_read_signed_leb128 (bfd *abfd ATTRIBUTE_UNUSED,
-			 bfd_byte *buf,
-			 unsigned int *bytes_read_ptr)
-{
-  bfd_vma result;
-  unsigned int shift;
-  unsigned int num_read;
-  bfd_byte byte;
-
-  result = 0;
-  shift = 0;
-  num_read = 0;
-  do
-    {
-      byte = bfd_get_8 (abfd, buf);
-      buf ++;
-      num_read ++;
-      if (shift < 8 * sizeof (result))
-	{
-	  result |= (((bfd_vma) byte & 0x7f) << shift);
-	  shift += 7;
-	}
-    }
-  while (byte & 0x80);
-  if (shift < 8 * sizeof (result) && (byte & 0x40))
-    result |= (((bfd_vma) -1) << shift);
-  *bytes_read_ptr = num_read;
-  return result;
-}
-
 /* Write VAL in uleb128 format to P.
    END indicates the last byte of allocated space for the uleb128 value to fit
    in.
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index fdf3588e18e..dad2a7fbdd5 100644
--- a/bfd/libbfd.h
+++ b/bfd/libbfd.h
@@ -832,10 +832,6 @@  extern void bfd_section_already_linked_table_traverse
   (bool (*) (struct bfd_section_already_linked_hash_entry *,
 		    void *), void *) ATTRIBUTE_HIDDEN;
 
-extern bfd_vma _bfd_read_unsigned_leb128
-  (bfd *, bfd_byte *, unsigned int *) ATTRIBUTE_HIDDEN;
-extern bfd_signed_vma _bfd_read_signed_leb128
-  (bfd *, bfd_byte *, unsigned int *) ATTRIBUTE_HIDDEN;
 extern bfd_vma _bfd_safe_read_leb128
   (bfd *, bfd_byte **, bool, const bfd_byte * const) ATTRIBUTE_HIDDEN;
 extern bfd_byte * _bfd_write_unsigned_leb128