Binutils: Always skip only 1 byte for CIE version 1's return address register.
authorTamar Christina <tamar.christina@arm.com>
Fri, 1 Mar 2019 11:37:51 +0000 (11:37 +0000)
committerTamar Christina <tamar.christina@arm.com>
Fri, 1 Mar 2019 11:38:22 +0000 (11:38 +0000)
According to the specification for the CIE entries, when the CIE version is 1 then
the return address register field is always 1 byte.  Readelf does this correctly in
read_cie in dwarf.c but ld does this incorrectly and always tries to read a
skip_leb128.  If the value here has the top bit set then ld will incorrectly read
at least another byte, causing either an assert failure or an incorrect address to
be used in eh_frame.

I'm not sure how to generate a generic test for this as I'd need to write assembly,
and it's a bit hard to trigger. Essentially the relocated value needs to start with
something that & 0x70 != 0x10 while trying to write a personality.

bfd/ChangeLog:

* elf-eh-frame.c (_bfd_elf_write_section_eh_frame): Correct CIE parse.

bfd/ChangeLog
bfd/elf-eh-frame.c

index 100c453eae7cd1c2710432fe1ae4d4f23b36da03..68004eab088f0367adb5e848b872b085bbbf2fc1 100644 (file)
@@ -1,3 +1,7 @@
+2019-02-28  Tamar Christina  <tamar.christina@arm.com>
+
+       * elf-eh-frame.c (_bfd_elf_write_section_eh_frame): Correct CIE parse.
+
 2019-02-28  Nick Clifton  <nickc@redhat.com>
 
        PR 24273
index a13e81ebb861129a50ac92390ffb2980111f06c1..6919ac3031bedf8b9748f643ddcdbfc7d0ffe8a2 100644 (file)
@@ -1993,7 +1993,7 @@ _bfd_elf_write_section_eh_frame (bfd *abfd,
              || ent->u.cie.per_encoding_relative)
            {
              char *aug;
-             unsigned int action, extra_string, extra_data;
+             unsigned int version, action, extra_string, extra_data;
              unsigned int per_width, per_encoding;
 
              /* Need to find 'R' or 'L' augmentation's argument and modify
@@ -2004,13 +2004,17 @@ _bfd_elf_write_section_eh_frame (bfd *abfd,
              extra_string = extra_augmentation_string_bytes (ent);
              extra_data = extra_augmentation_data_bytes (ent);
 
-             /* Skip length, id and version.  */
-             buf += 9;
+             /* Skip length, id.  */
+             buf += 8;
+             version = *buf++;
              aug = (char *) buf;
              buf += strlen (aug) + 1;
              skip_leb128 (&buf, end);
              skip_leb128 (&buf, end);
-             skip_leb128 (&buf, end);
+             if (version == 1)
+               skip_bytes (&buf, end, 1);
+             else
+               skip_leb128 (&buf, end);
              if (*aug == 'z')
                {
                  /* The uleb128 will always be a single byte for the kind