Added out of bounds read detection for corrups section headers (#434)
authorJavier Rascón Mesa <jrm00018.tar.gz@gmail.com>
Tue, 9 Aug 2022 22:53:02 +0000 (00:53 +0200)
committerGitHub <noreply@github.com>
Tue, 9 Aug 2022 22:53:02 +0000 (15:53 -0700)
* Added test for files with corrupt e_shoff & e_shnum

* Added basic detection for reading out of the stream bounds due to corrupt header

* Added None check when accessing '_section_header_stringtable'

* Fix to work on python2

Co-authored-by: halos <halos@synth>
elftools/elf/elffile.py
test/test_corrupt_files.py [new file with mode: 0644]
test/testfiles_for_unittests/corrupt_sh.elf [new file with mode: 0755]

index e4ee5e96fcb8094c2eb1d7efc7dff3df296659c7..d228db7213e60da2105cc57f19ca73183553724e 100644 (file)
@@ -24,7 +24,7 @@ except ImportError:
         PAGESIZE = 4096
 
 from ..common.py3compat import BytesIO
-from ..common.exceptions import ELFError
+from ..common.exceptions import ELFError, ELFParseError
 from ..common.utils import struct_parse, elf_assert
 from .structs import ELFStructs
 from .sections import (
@@ -78,6 +78,9 @@ class ELFFile(object):
     """
     def __init__(self, stream, stream_loader=None):
         self.stream = stream
+        self.stream.seek(0, io.SEEK_END)
+        self.stream_len = self.stream.tell()
+
         self._identify_file()
         self.structs = ELFStructs(
             little_endian=self.little_endian,
@@ -607,15 +610,23 @@ class ELFFile(object):
     def _get_section_header(self, n):
         """ Find the header of section #n, parse it and return the struct
         """
+
+        stream_pos = self._section_offset(n)
+        if stream_pos > self.stream_len:
+            return None
+
         return struct_parse(
             self.structs.Elf_Shdr,
             self.stream,
-            stream_pos=self._section_offset(n))
+            stream_pos=stream_pos)
 
     def _get_section_name(self, section_header):
         """ Given a section header, find this section's name in the file's
             string table
         """
+        if self._section_header_stringtable is None:
+            raise ELFParseError("String Table not found")
+            
         name_offset = section_header['sh_name']
         return self._section_header_stringtable.get_string(name_offset)
 
@@ -750,8 +761,13 @@ class ELFFile(object):
             table.
         """
         stringtable_section_num = self.get_shstrndx()
+
+        stringtable_section_header = self._get_section_header(stringtable_section_num)
+        if stringtable_section_header is None:
+            return None
+
         return StringTableSection(
-                header=self._get_section_header(stringtable_section_num),
+                header=stringtable_section_header,
                 name='',
                 elffile=self)
 
diff --git a/test/test_corrupt_files.py b/test/test_corrupt_files.py
new file mode 100644 (file)
index 0000000..e276057
--- /dev/null
@@ -0,0 +1,28 @@
+"""
+Test that elftools does not fail to load corrupted ELF files
+"""
+import unittest
+import os
+
+from elftools.elf.elffile import ELFFile
+from elftools.common.exceptions import ELFParseError
+
+
+class TestCorruptFile(unittest.TestCase):
+    def test_elffile_init(self):
+        """ Test that ELFFile does not crash when parsing an ELF file with corrupt e_shoff and/or e_shnum
+        """
+        filepath = os.path.join('test', 'testfiles_for_unittests', 'corrupt_sh.elf')
+        with open(filepath, 'rb') as f:
+            elf = None
+
+            try:
+                elf = ELFFile(f)
+            except ELFParseError:
+                pass
+
+            self.assertIsInstance(elf, ELFFile, "ELFFile initialization should have detected the out of bounds read")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/testfiles_for_unittests/corrupt_sh.elf b/test/testfiles_for_unittests/corrupt_sh.elf
new file mode 100755 (executable)
index 0000000..b79ab5e
Binary files /dev/null and b/test/testfiles_for_unittests/corrupt_sh.elf differ