From 289628a4236202463a3e3bee6a24b1a7b03fa1a1 Mon Sep 17 00:00:00 2001 From: Rok Mandeljc Date: Fri, 12 Feb 2021 21:50:13 +0100 Subject: [PATCH 1/2] archive: CArchiveReader: implement full back-to-front file search for MAGIC Allow archive-viewer to open binaries with more than 4096 bytes of additional data past the appended package (e.g., macOS binaries with code signature). --- PyInstaller/archive/readers.py | 38 +++++++++++++++++++++++++--------- news/2372.bugfix.rst | 3 +++ 2 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 news/2372.bugfix.rst diff --git a/PyInstaller/archive/readers.py b/PyInstaller/archive/readers.py index 810a6f2229..cec84bc2cc 100644 --- a/PyInstaller/archive/readers.py +++ b/PyInstaller/archive/readers.py @@ -17,6 +17,7 @@ # TODO clean up this module import struct +import os from PyInstaller.loader.pyimod02_archive import ArchiveReader @@ -142,19 +143,36 @@ def checkmagic(self): if self.length: self.lib.seek(self.start + self.length, 0) else: - self.lib.seek(0, 2) - filelen = self.lib.tell() - - self.lib.seek(max(0, filelen-4096)) - searchpos = self.lib.tell() - buf = self.lib.read(min(filelen, 4096)) - pos = buf.rfind(self.MAGIC) - if pos == -1: + self.lib.seek(0, os.SEEK_END) + end_pos = self.lib.tell() + + SEARCH_CHUNK_SIZE = 8192 + magic_offset = -1 + while end_pos >= len(self.MAGIC): + start_pos = max(end_pos - SEARCH_CHUNK_SIZE, 0) + chunk_size = end_pos - start_pos + # Is the remaining chunk large enough to hold the pattern? + if chunk_size < len(self.MAGIC): + break + # Read and scan the chunk + self.lib.seek(start_pos, os.SEEK_SET) + buf = self.lib.read(chunk_size) + pos = buf.rfind(self.MAGIC) + if pos != -1: + magic_offset = start_pos + pos + break + # Adjust search location for next chunk; ensure proper + # overlap + end_pos = start_pos + len(self.MAGIC) - 1 + if magic_offset == -1: raise RuntimeError("%s is not a valid %s archive file" % (self.path, self.__class__.__name__)) - filelen = searchpos + pos + self._cookie_size + filelen = magic_offset + self._cookie_size + # Read the whole cookie + self.lib.seek(magic_offset, os.SEEK_SET) + buf = self.lib.read(self._cookie_size) (magic, totallen, tocpos, toclen, pyvers, pylib_name) = struct.unpack( - self._cookie_format, buf[pos:pos+self._cookie_size]) + self._cookie_format, buf) if magic != self.MAGIC: raise RuntimeError("%s is not a valid %s archive file" % (self.path, self.__class__.__name__)) diff --git a/news/2372.bugfix.rst b/news/2372.bugfix.rst new file mode 100644 index 0000000000..0372dacec2 --- /dev/null +++ b/news/2372.bugfix.rst @@ -0,0 +1,3 @@ +``CArchiveReader`` now performs full back-to-front file search for +``MAGIC``, allowing ``pyi-archive_viewer`` to open binaries with extra +appended data after embedded package (e.g., digital signature). From 81ecf66453d489080d735fe558c481a8f9471d1a Mon Sep 17 00:00:00 2001 From: Rok Mandeljc Date: Fri, 12 Feb 2021 22:18:27 +0100 Subject: [PATCH 2/2] cliutils: archive_viewer: fix crash when quitting or moving up a level Calling arch.lib.close() when cleaning up the archive when either moving up a level or quitting the archive_viewer application triggers the assert in pyimod02_archive.ArchiveFile.__getattr__. This is because local file object is set only between __enter__ and __exit__ calls, i.e., while in the `with arch.lib:` block. Which also means that there's no need for attempting to close those file handles in the first place, and the error is probably a regression from when the thread-local file objects were introduced. --- PyInstaller/utils/cliutils/archive_viewer.py | 3 --- news/5554.bugfix.rst | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 news/5554.bugfix.rst diff --git a/PyInstaller/utils/cliutils/archive_viewer.py b/PyInstaller/utils/cliutils/archive_viewer.py index 687d716fe7..07846e2a36 100644 --- a/PyInstaller/utils/cliutils/archive_viewer.py +++ b/PyInstaller/utils/cliutils/archive_viewer.py @@ -65,7 +65,6 @@ def main(name, brief, debug, rec_debug, **unused_options): if cmd == 'U': if len(stack) > 1: arch = stack[-1][1] - arch.lib.close() del stack[-1] name, arch = stack[-1] show(name, arch) @@ -106,8 +105,6 @@ def main(name, brief, debug, rec_debug, **unused_options): def do_cleanup(): global stack, cleanup - for (name, arch) in stack: - arch.lib.close() stack = [] for filename in cleanup: try: diff --git a/news/5554.bugfix.rst b/news/5554.bugfix.rst new file mode 100644 index 0000000000..e787e1993c --- /dev/null +++ b/news/5554.bugfix.rst @@ -0,0 +1,2 @@ +Fix a crash in ``pyi-archive_viewer`` when quitting the application or +moving up a level.