From f67741e172bf342291fe3abd2b395899ce6433a0 Mon Sep 17 00:00:00 2001 From: "Potharla, Rupesh" 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 --- 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 ""; + 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