Correctly handle the "multiple string tables" case for string resolution in the dynam...
authorYann Rouillard <yann@pleiades.fr.eu.org>
Tue, 9 Jul 2013 18:22:27 +0000 (20:22 +0200)
committerYann Rouillard <yann@pleiades.fr.eu.org>
Tue, 7 Jan 2014 14:04:35 +0000 (15:04 +0100)
The index of the string table section used to resolve various strings in the
dynamic section is given by the sh_link field in the dynamic section header.
As several string tables with the same name can co-exist in an elf file
we must explicitely look for this specific string table instead of
looking for the first string table in the file.

elftools/elf/dynamic.py
test/test_double_dynstr_section.py [new file with mode: 0644]
test/testfiles_for_unittests/lib_with_two_dynstr_sections.so.1.elf [new file with mode: 0755]
test/testfiles_for_unittests/lib_with_two_dynstr_sections_reversed.so.1.elf [new file with mode: 0755]

index 60fb4500c3bb3aafd06fec910593a1d7bfd061a0..e36598e7636c75f767e069eb92956b1b0031b267 100644 (file)
@@ -28,12 +28,11 @@ class DynamicTag(object):
         ['DT_NEEDED', 'DT_RPATH', 'DT_RUNPATH', 'DT_SONAME',
          'DT_SUNW_FILTER'])
 
-    def __init__(self, entry, elffile):
+    def __init__(self, entry, stringtable):
         self.entry = entry
         if entry.d_tag in self._HANDLED_TAGS:
-            dynstr = elffile.get_section_by_name(b'.dynstr')
             setattr(self, entry.d_tag[3:].lower(),
-                    dynstr.get_string(self.entry.d_val))
+                    stringtable.get_string(self.entry.d_val))
 
     def __getitem__(self, name):
         """ Implement dict-like access to entries
@@ -54,13 +53,14 @@ class DynamicTag(object):
 class Dynamic(object):
     """ Shared functionality between dynamic sections and segments.
     """
-    def __init__(self, stream, elffile, position):
+    def __init__(self, stream, elffile, stringtable, position):
         self._stream = stream
         self._elffile = elffile
         self._elfstructs = elffile.structs
         self._num_tags = -1
         self._offset = position
         self._tagsize = self._elfstructs.Elf_Dyn.sizeof()
+        self._stringtable = stringtable
 
     def iter_tags(self, type=None):
         """ Yield all tags (limit to |type| if specified)
@@ -80,7 +80,7 @@ class Dynamic(object):
             self._elfstructs.Elf_Dyn,
             self._stream,
             stream_pos=offset)
-        return DynamicTag(entry, self._elffile)
+        return DynamicTag(entry, self._stringtable)
 
     def num_tags(self):
         """ Number of dynamic tags in the file
@@ -100,12 +100,24 @@ class DynamicSection(Section, Dynamic):
     """
     def __init__(self, header, name, stream, elffile):
         Section.__init__(self, header, name, stream)
-        Dynamic.__init__(self, stream, elffile, self['sh_offset'])
+        stringtable = elffile.get_section(header['sh_link'])
+        Dynamic.__init__(self, stream, elffile, stringtable, self['sh_offset'])
 
 
 class DynamicSegment(Segment, Dynamic):
     """ ELF dynamic table segment.  Knows how to process the list of tags.
     """
     def __init__(self, header, stream, elffile):
+        # The string table section to be used to resolve string names in
+        # the dynamic tag array is the one pointed at by the sh_link field
+        # of the dynamic section header.
+        # So we must look for the dynamic section contained in the dynamic
+        # segment, we do so by searching for the dynamic section whose content
+        # is located at the same offset as the dynamic segment
+        for section in elffile.iter_sections():
+            if (isinstance(section, DynamicSection) and
+                    section['sh_offset'] == header['p_offset']):
+                stringtable = elffile.get_section(section['sh_link'])
+                break
         Segment.__init__(self, header, stream)
-        Dynamic.__init__(self, stream, elffile, self['p_offset'])
+        Dynamic.__init__(self, stream, elffile, stringtable, self['p_offset'])
diff --git a/test/test_double_dynstr_section.py b/test/test_double_dynstr_section.py
new file mode 100644 (file)
index 0000000..c907928
--- /dev/null
@@ -0,0 +1,64 @@
+#------------------------------------------------------------------------------
+# elftools tests
+#
+# Yann Rouillard (yann@pleiades.fr.eu.org)
+# This code is in the public domain
+#------------------------------------------------------------------------------
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
+import os
+
+from utils import setup_syspath
+setup_syspath()
+from elftools.elf.elffile import ELFFile
+from elftools.elf.dynamic import DynamicSection, DynamicTag
+
+
+class TestDoubleDynstrSections(unittest.TestCase):
+    """ This test make sure than dynamic tags
+        are properly analyzed when two .dynstr
+        sections are present in an elf file
+    """
+
+    reference_data = [
+        b'libz.so.1',
+        b'libc.so.6',
+        b'lib_versioned.so.1',
+    ]
+
+    def _test_double_dynstr_section_generic(self, testfile):
+
+        with open(os.path.join('test', 'testfiles_for_unittests', testfile),
+                  'rb') as f:
+
+            elf = ELFFile(f)
+            for section in elf.iter_sections():
+                if isinstance(section, DynamicSection):
+                    d_tags = [getattr(x, x.entry.d_tag[3:].lower())
+                              for x in section.iter_tags()
+                              if x.entry.d_tag in DynamicTag._HANDLED_TAGS]
+                    self.assertListEqual(
+                            TestDoubleDynstrSections.reference_data,
+                            d_tags)
+                    return
+                
+            self.fail('No dynamic section found !!')
+
+
+    def test_double_dynstr_section(self):
+        """ First test with the good dynstr section first
+        """
+        self._test_double_dynstr_section_generic(
+                'lib_with_two_dynstr_sections.so.1.elf')
+
+    def test_double_dynstr_section_reverse(self):
+        """ Second test with the good dynstr section last
+        """
+        self._test_double_dynstr_section_generic(
+                'lib_with_two_dynstr_sections_reversed.so.1.elf')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/testfiles_for_unittests/lib_with_two_dynstr_sections.so.1.elf b/test/testfiles_for_unittests/lib_with_two_dynstr_sections.so.1.elf
new file mode 100755 (executable)
index 0000000..661950b
Binary files /dev/null and b/test/testfiles_for_unittests/lib_with_two_dynstr_sections.so.1.elf differ
diff --git a/test/testfiles_for_unittests/lib_with_two_dynstr_sections_reversed.so.1.elf b/test/testfiles_for_unittests/lib_with_two_dynstr_sections_reversed.so.1.elf
new file mode 100755 (executable)
index 0000000..8d07de1
Binary files /dev/null and b/test/testfiles_for_unittests/lib_with_two_dynstr_sections_reversed.so.1.elf differ