[PATCH v9 09/19] bfd: parse Object Attributes v2's section in input object files

Matthieu Longo matthieu.longo@arm.com
Mon Sep 1 16:56:16 GMT 2025


This patch adds to BFD the capability of parsing the Object Attributes v2
(OAv2) stored into the section defined by the backend (i.e. .ARM.attributes
for AArch64).

An OAv2 section is organized with the following syntactic structure:
  <format-version: uint8>
  [ <subsection-length: uint32>
    <subsection-name: NTBS>
    <optionality: uint8>
    <encoding: uint8>
    <attribute>*
  ]*

Once parsing is completed, OAv2 are stored in a list of subsections in
elf_obj_tdata.

Regarding ULEB128 parsing, the implementation of _bfd_safe_read_leb128
has known shortcomings. In particular, it may accept malformed ULEB128
values if the 8th bit is set while meeting the end condition "data < end".
The error handling could be improved, but this function is old and used
in more than 50 places. The same issue applies to _bfd_read_unsigned_leb128
and _bfd_read_signed_leb128, which likewise do not report errors in
this case. Since this is long-standing behavior and fixing it would
require broader changes outside the scope of this patch series, the
issue is acknowledged but not addressed here, and is considered
acceptable as this patch does not worsen the situation.
---
 bfd/elf-attrs.c | 254 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 251 insertions(+), 3 deletions(-)

diff --git a/bfd/elf-attrs.c b/bfd/elf-attrs.c
index 78960e888db..22405ab92b7 100644
--- a/bfd/elf-attrs.c
+++ b/bfd/elf-attrs.c
@@ -843,7 +843,7 @@ _bfd_elf_obj_attrs_arg_type (bfd *abfd,
 }
 
 static void
-bfd_elf_parse_attr_section_v1 (bfd *abfd, bfd_byte *p, bfd_byte *p_end)
+oav1_parse_section (bfd *abfd, bfd_byte *p, bfd_byte *p_end)
 {
   const char *std_sec = get_elf_backend_data (abfd)->obj_attrs_vendor;
 
@@ -962,7 +962,245 @@ bfd_elf_parse_attr_section_v1 (bfd *abfd, bfd_byte *p, bfd_byte *p_end)
     }
 }
 
-/* Parse an object attributes section.  */
+#define READ_ULEB128(abfd, var, cursor, end, total_read)		\
+  do									\
+    {									\
+      bfd_byte *begin = (cursor);					\
+      (var) = _bfd_safe_read_leb128 (abfd, &(cursor), false, end);	\
+      (total_read) += (cursor) - begin;					\
+    }									\
+  while (0)
+
+static int
+read_ntbs (bfd *abfd,
+	   const bfd_byte *cursor,
+	   const bfd_byte *end,
+	   const char **s)
+{
+  *s = NULL;
+  const size_t MAX_STR_LEN = end - cursor; /* Including \0.  */
+  const size_t s_len = strnlen ((char *) cursor, MAX_STR_LEN);
+  if (s_len == MAX_STR_LEN)
+    {
+      bfd_set_error (bfd_error_malformed_archive);
+      _bfd_error_handler (_("%pB: error: NTBS value seems corrupted "
+			    "(missing '\\0')"), abfd);
+      return -1;
+    }
+  *s = xstrdup ((const char *) cursor);
+  return s_len + 1;
+}
+
+/* Parse an object attribute (v2 only).  */
+static int
+oav2_parse_attr (bfd *abfd,
+		 bfd_byte *cursor,
+		 const bfd_byte *end,
+		 obj_attr_encoding_v2 attr_type,
+		 obj_attr_v2 **attr)
+{
+  *attr = NULL;
+  size_t total_read = 0;
+
+  obj_attr_tag_t attr_tag;
+  READ_ULEB128 (abfd, attr_tag, cursor, end, total_read);
+
+  union obj_attr_value_v2 attr_val;
+  switch (attr_type)
+    {
+    case OA_ENC_NTBS:
+      {
+	int read = read_ntbs (abfd, cursor, end, &attr_val.string_val);
+	if (read <= 0)
+	  return -1;
+	total_read += read;
+      }
+      break;
+    case OA_ENC_ULEB128:
+      READ_ULEB128 (abfd, attr_val.uint_val, cursor, end, total_read);
+      break;
+    default:
+      abort ();
+    }
+
+  *attr = _bfd_elf_obj_attr_v2_init (attr_tag, attr_val);
+  return total_read;
+}
+
+/* Parse a subsection (object attributes v2 only).  */
+static int
+oav2_parse_subsection (bfd *abfd,
+		       bfd_byte *cursor,
+		       const uint64_t max_read,
+		       obj_attr_subsection_v2 **subsec)
+{
+  *subsec = NULL;
+  size_t total_read = 0;
+
+  const char *subsection_name = NULL;
+  const uint32_t F_SUBSECTION_LEN = sizeof(uint32_t);
+  const uint32_t F_SUBSECTION_COMPREHENSION = sizeof(uint8_t);
+  const uint32_t F_SUBSECTION_ENCODING = sizeof(uint8_t);
+  /* The minimum subsection length is 7: 4 bytes for the length itself, and 1
+     byte for an empty NUL-terminated string, 1 byte for the comprehension,
+     1 byte for the encoding, and no vendor-data.  */
+  const uint32_t F_MIN_SUBSECTION_DATA_LEN
+    = F_SUBSECTION_LEN + 1 /* for '\0' */
+      + F_SUBSECTION_COMPREHENSION + F_SUBSECTION_ENCODING;
+
+  /* Similar to the issues reported in PR 17531, we need to check all the sizes
+     and offsets as we parse the section.  */
+  if (max_read < F_MIN_SUBSECTION_DATA_LEN)
+    {
+      _bfd_error_handler (_("%pB: error: attributes subsection ends "
+			    "prematurely"), abfd);
+      goto error;
+    }
+
+  const uint32_t subsection_len = bfd_get_32 (abfd, cursor);
+  total_read += F_SUBSECTION_LEN;
+  cursor += F_SUBSECTION_LEN;
+  if (subsection_len > max_read)
+    {
+      _bfd_error_handler (_("%pB: error: bad subsection length (%u > max=%lu)"),
+			  abfd, subsection_len, max_read);
+      goto error;
+    }
+  else if (subsection_len < F_MIN_SUBSECTION_DATA_LEN)
+    {
+      _bfd_error_handler (_("%pB: error: subsection length of %u is too small"),
+			  abfd, subsection_len);
+      goto error;
+    }
+
+  const size_t MAX_SUBSECTION_NAME_LEN
+    = subsection_len - F_SUBSECTION_LEN
+      - F_SUBSECTION_COMPREHENSION - F_SUBSECTION_ENCODING;
+  const size_t subsection_name_len
+    = strnlen ((char *) cursor, MAX_SUBSECTION_NAME_LEN);
+  if (subsection_name_len >= MAX_SUBSECTION_NAME_LEN)
+    {
+      _bfd_error_handler (_("%pB: error: subsection name seems corrupted "
+			    "(missing '\\0')"), abfd);
+      goto error;
+    }
+  /* Note: if the length of the subsection name is 0 (i.e. the string is '\0'),
+     it is still considered a valid name, even if it is not particularly
+     useful.  */
+
+  const bfd_byte *const end = cursor - F_SUBSECTION_LEN + subsection_len;
+
+  /* Note: at this stage,
+     1. the length of the subsection name is validated, as the presence of '\0'
+	at the end of the string, so no risk of buffer overrun.
+     2. the data for comprehension and encoding can also safely be read.  */
+  {
+    int read = read_ntbs (abfd, cursor, cursor + subsection_name_len + 1,
+			  &subsection_name);
+    total_read += read;
+    cursor += read;
+  }
+
+  uint8_t comprehension_raw = bfd_get_8 (abfd, cursor);
+  ++cursor;
+  ++total_read;
+
+  /* Comprehension is supposed to be a boolean, so any value greater than 1 is
+     considered invalid.  */
+  if (comprehension_raw > 1)
+    {
+      _bfd_error_handler (_("%pB: error: '%s' seems corrupted, got %u but only "
+			    "0x0 ('%s') or 0x1 ('%s') are valid values."),
+			    abfd, "comprehension", comprehension_raw,
+			    "required", "optional");
+      goto error;
+    }
+
+  uint8_t value_encoding_raw = bfd_get_8 (abfd, cursor);
+  ++cursor;
+  ++total_read;
+
+  /* Encoding cannot be greater than OA_ENC_MAX, otherwise it means that either
+     there is a new encoding that was introduced in the spec, and this
+     implementation in binutils is older, and not aware of it so does not
+     support it; or the stored value for encoding is garbage.  */
+  enum obj_attr_encoding_v2 value_encoding
+    = obj_attr_encoding_v2_from_u8 (value_encoding_raw);
+  if (value_encoding > OA_ENC_MAX)
+    {
+      _bfd_error_handler (_("%pB: error: attribute type seems corrupted, got"
+			    " %u but only 0x0 (ULEB128) or 0x1 (NTBS) are "
+			    "valid types."), abfd, value_encoding);
+      goto error;
+    }
+
+  const char *vendor_name = get_elf_backend_data (abfd)->obj_attrs_vendor;
+  obj_attr_subsection_scope_v2 scope
+    = (strncmp (subsection_name, vendor_name, strlen (vendor_name)) == 0
+      ? OA_SUBSEC_PUBLIC
+      : OA_SUBSEC_PRIVATE);
+
+  *subsec = _bfd_elf_obj_attr_subsection_v2_init
+    (subsection_name, scope, comprehension_raw, value_encoding);
+
+  /* A subsection can be empty, so 'cursor' can be equal to 'end' here.  */
+  bool err = false;
+  while (!err && cursor < end)
+    {
+      obj_attr_v2 *attr;
+      int read = oav2_parse_attr (abfd, cursor, end, value_encoding, &attr);
+      LINKED_LIST_APPEND(obj_attr_v2) (*subsec, attr);
+      total_read += read;
+      err |= (read < 0);
+      cursor += read;
+    }
+
+  if (err)
+    {
+      _bfd_elf_obj_attr_subsection_v2_free (*subsec);
+      *subsec = NULL;
+      return -1;
+    }
+
+  BFD_ASSERT (cursor == end);
+  return total_read;
+
+ error:
+  bfd_set_error (bfd_error_malformed_archive);
+  if (subsection_name)
+    free ((void *) subsection_name);
+  return -1;
+}
+
+/* Parse the list of subsections (object attributes v2 only).  */
+static void
+oav2_parse_section (bfd *abfd,
+		    Elf_Internal_Shdr *hdr,
+		    bfd_byte *cursor)
+{
+  obj_attr_subsection_list *subsecs = &elf_obj_attr_subsections (abfd);
+  int read = 0;
+  for (uint64_t remaining = hdr->sh_size - 1; /* Already read 'A'.  */
+       remaining > 0;
+       remaining -= read, cursor += read)
+    {
+      obj_attr_subsection_v2 *subsec = NULL;
+      read = oav2_parse_subsection (abfd, cursor, remaining, &subsec);
+      if (read < 0)
+	{
+	  _bfd_error_handler (_("%pB: error: could not parse subsection at "
+				"offset %lx"),
+			      abfd, hdr->sh_size - remaining);
+	  bfd_set_error (bfd_error_wrong_format);
+	  break;
+	}
+      else
+	LINKED_LIST_APPEND(obj_attr_subsection_v2) (subsecs, subsec);
+    }
+}
+
+/* Parse an object attributes section.
+   Note: The parsing setup is common between object attributes v1 and v2.  */
 void
 _bfd_elf_parse_attributes (bfd *abfd, Elf_Internal_Shdr * hdr)
 {
@@ -1004,7 +1242,17 @@ _bfd_elf_parse_attributes (bfd *abfd, Elf_Internal_Shdr * hdr)
   ++cursor;
 
   elf_obj_attr_version (abfd) = version;
-  bfd_elf_parse_attr_section_v1 (abfd, cursor, data + hdr->sh_size);
+  switch (version)
+    {
+    case OBJ_ATTR_V1:
+      oav1_parse_section (abfd, cursor, data + hdr->sh_size);
+      break;
+    case OBJ_ATTR_V2:
+      oav2_parse_section (abfd, hdr, cursor);
+      break;
+    default:
+      abort ();
+    }
 
  free_data:
   free (data);
-- 
2.51.0



More information about the Binutils mailing list