From 45087daac5d4fe661beeb3c8884c50343e8b29a8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Javier=20Rasc=C3=B3n=20Mesa?= Date: Wed, 10 Aug 2022 00:53:02 +0200 Subject: [PATCH] Added out of bounds read detection for corrups section headers (#434) * 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 --- elftools/elf/elffile.py | 22 ++++++++++++--- test/test_corrupt_files.py | 28 ++++++++++++++++++++ test/testfiles_for_unittests/corrupt_sh.elf | Bin 0 -> 4504 bytes 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 test/test_corrupt_files.py create mode 100755 test/testfiles_for_unittests/corrupt_sh.elf diff --git a/elftools/elf/elffile.py b/elftools/elf/elffile.py index e4ee5e9..d228db7 100644 --- a/elftools/elf/elffile.py +++ b/elftools/elf/elffile.py @@ -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 index 0000000..e276057 --- /dev/null +++ b/test/test_corrupt_files.py @@ -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 index 0000000000000000000000000000000000000000..b79ab5ef1105ea5e22f475f7091a73fdeb46abf1 GIT binary patch literal 4504 zcmeHLO-jRH5S^r7wJwC}$|8u%uCnSC)QyOsuESRn)J42iyQ1t-dW>sq`ihTm;>g3 zIbaT$1LlA^U=ElA=72e14wwVxK<~iEFHZ0sEw6aH17p0XcPV=CS-(%c(VzUtCC}$| zo!8NaC<}jINnRE{dW5PF$?EkY`Z;0@UA$9OEn}J8Ru9=KELUZSYsq5y7`gwSIK