From f67741e172bf342291fe3abd2b395899ce6433a0 Mon Sep 17 00:00:00 2001
From: "Potharla, Rupesh" <Rupesh.Potharla@amd.com>
Date: Tue, 24 May 2022 00:01:49 +0000
Subject: [PATCH] bfd: Add Support for DW_FORM_strx* and DW_FORM_addrx*

Upstream-Status: Backport [https://sourceware.org/git/?p=binutils-gdb.git;a=commitdiff;h=f67741e172bf342291fe3abd2b395899ce6433a0]

CVE: CVE-2023-1579

Signed-off-by: Yash Shinde <Yash.Shinde@windriver.com>

---
 bfd/dwarf2.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 268 insertions(+), 14 deletions(-)

diff --git a/bfd/dwarf2.c b/bfd/dwarf2.c
index f6b0183720b..45e286754e4 100644
--- a/bfd/dwarf2.c
+++ b/bfd/dwarf2.c
@@ -189,6 +189,18 @@ struct dwarf2_debug_file
   /* Length of the loaded .debug_str section.  */
   bfd_size_type dwarf_str_size;

+  /* Pointer to the .debug_str_offsets section loaded into memory.  */
+  bfd_byte *dwarf_str_offsets_buffer;
+
+  /* Length of the loaded .debug_str_offsets section.  */
+  bfd_size_type dwarf_str_offsets_size;
+
+  /* Pointer to the .debug_addr section loaded into memory.  */
+  bfd_byte *dwarf_addr_buffer;
+
+  /* Length of the loaded .debug_addr section.  */
+  bfd_size_type dwarf_addr_size;
+
   /* Pointer to the .debug_line_str section loaded into memory.  */
   bfd_byte *dwarf_line_str_buffer;

@@ -382,6 +394,12 @@ struct comp_unit
   /* Used when iterating over trie leaves to know which units we have
      already seen in this iteration.  */
   bool mark;
+
+ /* Base address of debug_addr section.  */
+  size_t dwarf_addr_offset;
+
+  /* Base address of string offset table.  */
+  size_t dwarf_str_offset;
 };

 /* This data structure holds the information of an abbrev.  */
@@ -424,6 +442,8 @@ const struct dwarf_debug_section dwarf_debug_sections[] =
   { ".debug_static_vars",	".zdebug_static_vars" },
   { ".debug_str",		".zdebug_str", },
   { ".debug_str",		".zdebug_str", },
+  { ".debug_str_offsets",	".zdebug_str_offsets", },
+  { ".debug_addr",		".zdebug_addr", },
   { ".debug_line_str",		".zdebug_line_str", },
   { ".debug_types",		".zdebug_types" },
   /* GNU DWARF 1 extensions */
@@ -458,6 +478,8 @@ enum dwarf_debug_section_enum
   debug_static_vars,
   debug_str,
   debug_str_alt,
+  debug_str_offsets,
+  debug_addr,
   debug_line_str,
   debug_types,
   debug_sfnames,
@@ -1307,12 +1329,92 @@ is_int_form (const struct attribute *attr)
     }
 }

+/* Returns true if the form is strx[1-4].  */
+
+static inline bool
+is_strx_form (enum dwarf_form form)
+{
+  return (form == DW_FORM_strx
+	  || form == DW_FORM_strx1
+	  || form == DW_FORM_strx2
+	  || form == DW_FORM_strx3
+	  || form == DW_FORM_strx4);
+}
+
+/* Return true if the form is addrx[1-4].  */
+
+static inline bool
+is_addrx_form (enum dwarf_form form)
+{
+  return (form == DW_FORM_addrx
+	  || form == DW_FORM_addrx1
+	  || form == DW_FORM_addrx2
+	  || form == DW_FORM_addrx3
+	  || form == DW_FORM_addrx4);
+}
+
+/* Returns the address in .debug_addr section using DW_AT_addr_base.
+   Used to implement DW_FORM_addrx*.  */
+static bfd_vma
+read_indexed_address (bfd_uint64_t idx,
+		      struct comp_unit *unit)
+{
+  struct dwarf2_debug *stash = unit->stash;
+  struct dwarf2_debug_file *file = unit->file;
+  size_t addr_base = unit->dwarf_addr_offset;
+  bfd_byte *info_ptr;
+
+  if (stash == NULL)
+    return 0;
+
+  if (!read_section (unit->abfd, &stash->debug_sections[debug_addr],
+		     file->syms, 0,
+		     &file->dwarf_addr_buffer, &file->dwarf_addr_size))
+    return 0;
+
+  info_ptr = file->dwarf_addr_buffer + addr_base + idx * unit->offset_size;
+
+  if (unit->offset_size == 4)
+    return bfd_get_32 (unit->abfd, info_ptr);
+  else
+    return bfd_get_64 (unit->abfd, info_ptr);
+}
+
+/* Returns the string using DW_AT_str_offsets_base.
+   Used to implement DW_FORM_strx*.  */
 static const char *
-read_indexed_string (bfd_uint64_t idx ATTRIBUTE_UNUSED,
-		     struct comp_unit * unit ATTRIBUTE_UNUSED)
+read_indexed_string (bfd_uint64_t idx,
+		     struct comp_unit *unit)
 {
-  /* FIXME: Add support for indexed strings.  */
-  return "<indexed strings not yet supported>";
+  struct dwarf2_debug *stash = unit->stash;
+  struct dwarf2_debug_file *file = unit->file;
+  bfd_byte *info_ptr;
+  unsigned long str_offset;
+
+  if (stash == NULL)
+    return NULL;
+
+  if (!read_section (unit->abfd, &stash->debug_sections[debug_str],
+		     file->syms, 0,
+		     &file->dwarf_str_buffer, &file->dwarf_str_size))
+    return NULL;
+
+  if (!read_section (unit->abfd, &stash->debug_sections[debug_str_offsets],
+		     file->syms, 0,
+		     &file->dwarf_str_offsets_buffer,
+		     &file->dwarf_str_offsets_size))
+    return NULL;
+
+  info_ptr = (file->dwarf_str_offsets_buffer
+	      + unit->dwarf_str_offset
+	      + idx * unit->offset_size);
+
+  if (unit->offset_size == 4)
+    str_offset = bfd_get_32 (unit->abfd, info_ptr);
+  else
+    str_offset = bfd_get_64 (unit->abfd, info_ptr);
+
+  return (const char *) file->dwarf_str_buffer + str_offset;
 }

 /* Read and fill in the value of attribute ATTR as described by FORM.
@@ -1381,21 +1483,37 @@ read_attribute_value (struct attribute *  attr,
     case DW_FORM_ref1:
     case DW_FORM_flag:
     case DW_FORM_data1:
+      attr->u.val = read_1_byte (abfd, &info_ptr, info_ptr_end);
+      break;
     case DW_FORM_addrx1:
       attr->u.val = read_1_byte (abfd, &info_ptr, info_ptr_end);
+      /* dwarf_addr_offset value 0 indicates the attribute DW_AT_addr_base
+	 is not yet read.  */
+      if (unit->dwarf_addr_offset != 0)
+	attr->u.val = read_indexed_address (attr->u.val, unit);
       break;
     case DW_FORM_data2:
-    case DW_FORM_addrx2:
     case DW_FORM_ref2:
       attr->u.val = read_2_bytes (abfd, &info_ptr, info_ptr_end);
       break;
+    case DW_FORM_addrx2:
+      attr->u.val = read_2_bytes (abfd, &info_ptr, info_ptr_end);
+      if (unit->dwarf_addr_offset != 0)
+	attr->u.val = read_indexed_address (attr->u.val, unit);
+      break;
     case DW_FORM_addrx3:
       attr->u.val = read_3_bytes (abfd, &info_ptr, info_ptr_end);
+      if (unit->dwarf_addr_offset != 0)
+	attr->u.val = read_indexed_address(attr->u.val, unit);
       break;
     case DW_FORM_ref4:
     case DW_FORM_data4:
+      attr->u.val = read_4_bytes (abfd, &info_ptr, info_ptr_end);
+      break;
     case DW_FORM_addrx4:
       attr->u.val = read_4_bytes (abfd, &info_ptr, info_ptr_end);
+      if (unit->dwarf_addr_offset != 0)
+	attr->u.val = read_indexed_address (attr->u.val, unit);
       break;
     case DW_FORM_data8:
     case DW_FORM_ref8:
@@ -1416,24 +1534,31 @@ read_attribute_value (struct attribute *  attr,
       break;
     case DW_FORM_strx1:
       attr->u.val = read_1_byte (abfd, &info_ptr, info_ptr_end);
-      attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
+      /* dwarf_str_offset value 0 indicates the attribute DW_AT_str_offsets_base
+	 is not yet read.  */
+      if (unit->dwarf_str_offset != 0)
+	attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
       break;
     case DW_FORM_strx2:
       attr->u.val = read_2_bytes (abfd, &info_ptr, info_ptr_end);
-      attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
+      if (unit->dwarf_str_offset != 0)
+	attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
       break;
     case DW_FORM_strx3:
       attr->u.val = read_3_bytes (abfd, &info_ptr, info_ptr_end);
-      attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
+      if (unit->dwarf_str_offset != 0)
+	attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
       break;
     case DW_FORM_strx4:
       attr->u.val = read_4_bytes (abfd, &info_ptr, info_ptr_end);
-      attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
+      if (unit->dwarf_str_offset != 0)
+	attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
       break;
     case DW_FORM_strx:
       attr->u.val = _bfd_safe_read_leb128 (abfd, &info_ptr,
 					   false, info_ptr_end);
-      attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
+      if (unit->dwarf_str_offset != 0)
+	attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
       break;
     case DW_FORM_exprloc:
     case DW_FORM_block:
@@ -1455,9 +1580,14 @@ read_attribute_value (struct attribute *  attr,
       break;
     case DW_FORM_ref_udata:
     case DW_FORM_udata:
+      attr->u.val = _bfd_safe_read_leb128 (abfd, &info_ptr,
+					   false, info_ptr_end);
+      break;
     case DW_FORM_addrx:
       attr->u.val = _bfd_safe_read_leb128 (abfd, &info_ptr,
 					   false, info_ptr_end);
+      if (unit->dwarf_addr_offset != 0)
+	attr->u.val = read_indexed_address (attr->u.val, unit);
       break;
     case DW_FORM_indirect:
       form = _bfd_safe_read_leb128 (abfd, &info_ptr,
@@ -2396,6 +2526,11 @@ read_formatted_entries (struct comp_unit *unit, bfd_byte **bufp,
 	    {
 	    case DW_FORM_string:
 	    case DW_FORM_line_strp:
+	    case DW_FORM_strx:
+	    case DW_FORM_strx1:
+	    case DW_FORM_strx2:
+	    case DW_FORM_strx3:
+	    case DW_FORM_strx4:
 	      *stringp = attr.u.str;
 	      break;

@@ -4031,6 +4166,80 @@ scan_unit_for_symbols (struct comp_unit *unit)
   return false;
 }

+/* Read the attributes of the form strx and addrx.  */
+
+static void
+reread_attribute (struct comp_unit *unit,
+		  struct attribute *attr,
+		  bfd_vma *low_pc,
+		  bfd_vma *high_pc,
+		  bool *high_pc_relative,
+		  bool compunit)
+{
+  if (is_strx_form (attr->form))
+    attr->u.str = (char *) read_indexed_string (attr->u.val, unit);
+  if (is_addrx_form (attr->form))
+    attr->u.val = read_indexed_address (attr->u.val, unit);
+
+  switch (attr->name)
+    {
+    case DW_AT_stmt_list:
+      unit->stmtlist = 1;
+      unit->line_offset = attr->u.val;
+      break;
+
+    case DW_AT_name:
+      if (is_str_form (attr))
+	unit->name = attr->u.str;
+      break;
+
+    case DW_AT_low_pc:
+      *low_pc = attr->u.val;
+      if (compunit)
+	unit->base_address = *low_pc;
+      break;
+
+    case DW_AT_high_pc:
+      *high_pc = attr->u.val;
+      *high_pc_relative = attr->form != DW_FORM_addr;
+      break;
+
+    case DW_AT_ranges:
+      if (!read_rangelist (unit, &unit->arange,
+			   &unit->file->trie_root, attr->u.val))
+	return;
+      break;
+
+    case DW_AT_comp_dir:
+      {
+	char *comp_dir = attr->u.str;
+
+	if (!is_str_form (attr))
+	  {
+	    _bfd_error_handler
+	      (_("DWARF error: DW_AT_comp_dir attribute encountered "
+		 "with a non-string form"));
+	    comp_dir = NULL;
+	  }
+
+	if (comp_dir)
+	  {
+	    char *cp = strchr (comp_dir, ':');
+
+	    if (cp && cp != comp_dir && cp[-1] == '.' && cp[1] == '/')
+	      comp_dir = cp + 1;
+	  }
+	unit->comp_dir = comp_dir;
+	break;
+      }
+
+    case DW_AT_language:
+      unit->lang = attr->u.val;
+    default:
+      break;
+    }
+}
+
 /* Parse a DWARF2 compilation unit starting at INFO_PTR.  UNIT_LENGTH
    includes the compilation unit header that proceeds the DIE's, but
    does not include the length field that precedes each compilation
@@ -4064,6 +4273,10 @@ parse_comp_unit (struct dwarf2_debug *stash,
   bfd *abfd = file->bfd_ptr;
   bool high_pc_relative = false;
   enum dwarf_unit_type unit_type;
+  struct attribute *str_addrp = NULL;
+  size_t str_count = 0;
+  size_t str_alloc = 0;
+  bool compunit_flag = false;

   version = read_2_bytes (abfd, &info_ptr, end_ptr);
   if (version < 2 || version > 5)
@@ -4168,11 +4381,33 @@ parse_comp_unit (struct dwarf2_debug *stash,
   unit->file = file;
   unit->info_ptr_unit = info_ptr_unit;

+  if (abbrev->tag == DW_TAG_compile_unit)
+    compunit_flag = true;
+
   for (i = 0; i < abbrev->num_attrs; ++i)
     {
       info_ptr = read_attribute (&attr, &abbrev->attrs[i], unit, info_ptr, end_ptr);
       if (info_ptr == NULL)
-	return NULL;
+	goto err_exit;
+
+      /* Identify attributes of the form strx* and addrx* which come before
+	 DW_AT_str_offsets_base and DW_AT_addr_base respectively in the CU.
+	 Store the attributes in an array and process them later.  */
+      if ((unit->dwarf_str_offset == 0 && is_strx_form (attr.form))
+	  || (unit->dwarf_addr_offset == 0 && is_addrx_form (attr.form)))
+	{
+	  if (str_count <= str_alloc)
+	    {
+	      str_alloc = 2 * str_alloc + 200;
+	      str_addrp = bfd_realloc (str_addrp,
+				       str_alloc * sizeof (*str_addrp));
+	      if (str_addrp == NULL)
+		goto err_exit;
+	    }
+	  str_addrp[str_count] = attr;
+	  str_count++;
+	  continue;
+	}

       /* Store the data if it is of an attribute we want to keep in a
 	 partial symbol table.  */
@@ -4198,7 +4433,7 @@ parse_comp_unit (struct dwarf2_debug *stash,
 	      /* If the compilation unit DIE has a DW_AT_low_pc attribute,
 		 this is the base address to use when reading location
 		 lists or range lists.  */
-	      if (abbrev->tag == DW_TAG_compile_unit)
+	      if (compunit_flag)
 		unit->base_address = low_pc;
 	    }
 	  break;
@@ -4215,7 +4450,7 @@ parse_comp_unit (struct dwarf2_debug *stash,
 	  if (is_int_form (&attr)
 	      && !read_rangelist (unit, &unit->arange,
 				  &unit->file->trie_root, attr.u.val))
-	    return NULL;
+	    goto err_exit;
 	  break;

 	case DW_AT_comp_dir:
@@ -4248,21 +4483,40 @@ parse_comp_unit (struct dwarf2_debug *stash,
 	    unit->lang = attr.u.val;
 	  break;

+	case DW_AT_addr_base:
+	  unit->dwarf_addr_offset = attr.u.val;
+	  break;
+
+	case DW_AT_str_offsets_base:
+	  unit->dwarf_str_offset = attr.u.val;
+	  break;
+
 	default:
 	  break;
 	}
     }
+
+  for (i = 0; i < str_count; ++i)
+    reread_attribute (unit, &str_addrp[i], &low_pc, &high_pc,
+		      &high_pc_relative, compunit_flag);
+
   if (high_pc_relative)
     high_pc += low_pc;
   if (high_pc != 0)
     {
       if (!arange_add (unit, &unit->arange, &unit->file->trie_root,
 		       low_pc, high_pc))
-	return NULL;
+	goto err_exit;
     }

   unit->first_child_die_ptr = info_ptr;
+
+  free (str_addrp);
   return unit;
+
+ err_exit:
+  free (str_addrp);
+  return NULL;
 }

 /* Return TRUE if UNIT may contain the address given by ADDR.  When
--
2.31.1

