Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SHA1_init missing when using PyInstaller on OSX #5583

Closed
fineusing opened this issue Feb 24, 2021 · 6 comments · Fixed by #5604
Closed

SHA1_init missing when using PyInstaller on OSX #5583

fineusing opened this issue Feb 24, 2021 · 6 comments · Fixed by #5604

Comments

@fineusing
Copy link

fineusing commented Feb 24, 2021

Description of the issue

There is no problem running the source code directly with python3 under OSX 11.1
When running the executable generated by PyInstaller, I get the following error message.

File "gpsoauth/__init__.py", line 97, in perform_master_login
File "gpsoauth/google.py", line 51, in signature
File "Cryptodome/Cipher/PKCS1_OAEP.py", line 117, in encrypt
lHash = self._hashObj.new(self._label).digest()
File "Cryptodome/Hash/SHA1.py", line 158, in new
return SHA1Hash().new(data)
File "Cryptodome/Hash/SHA1.py", line 74, in __init__
result = _raw_sha1_lib.SHA1_init(state.address_of())
File "ctypes/__init__.py", line 369, in __getattr__
File "ctypes/__init__.py", line 374, in __getitem__
AttributeError: dlsym(0x7fc07e7ee6d0, SHA1_init): symbol not found

After testing, it is found that the sha1_init symbol is in the source code Cryptodome/Hash/_SHA1.abi3.so library, and the _sha1.cpython-39m-darwin.so generated by Pyinstaller does not have the sha1_init symbol.

Context information (for bug reports)

  • pyinstaller 5.0dev
  • python 3.9.1
  • OSX 11.1
  • There is no problem with the executable file generated by pyinstaller under the win platform
@bwoodsend
Copy link
Member

Can you please fill out the issue template.

@fineusing
Copy link
Author

Can you please fill out the issue template.

ok, it has been changed

@bwoodsend
Copy link
Member

bwoodsend commented Feb 24, 2021

Can you try using collect_dynamic_libs()? Edit you .spec file to put:

from PyInstaller.utils.hooks import collect_dynamic_libs

at the top and change binaries=[] to binaries=collect_dynamic_libs("Cryptodome"). Then rebuild using:

pyinstaller your-project-name.spec

That at least ensures the right libraries are collected.

@rokm
Copy link
Member

rokm commented Feb 24, 2021

Uf, this is a nasty subtle bug...

There is a Cryptodome/Hash/_SHA1.abi3.so library that's properly collected and which is supposed to be loaded by pycryptodomex, but we end up with handle to _sha1.cpython-38-darwin.so that's collected in the root dist folder (and comes from python itself).

The loading is done via cffi or, if that's not available, via ctypes as a fallback.

In both cases, the library is loaded by calling utility function with fully-qualified name, e.g., Crypto.Hash._SHA1. This load_pycryptodome_raw_lib helper decomposes the fully-qualified name into directory components and filename, and then tries to find the library using different extensions.

The extension suffices that are tested come from importlib.machinery.EXTENSION_SUFFIXES, and are:

['.cpython-38-darwin.so', '.abi3.so', '.so']

This means than unfrozen version first tries to load

/Users/[...]/venv/lib/python3.8/site-packages/Cryptodome/Util/../Hash/_SHA1.cpython-38-darwin.so

which fails as it does not exist. Then it tries

/Users/[...]/venv/lib/python3.8/site-packages/Cryptodome/Util/../Hash/_SHA1.abi3.so

which succeeds.

In frozen version, however, the first call with _MEIPASS/Cryptodome/Util/../Hash/_SHA1.cpython-38-darwin.so actually succeeds, but it returns handle to _MEIPASS/_SHA1.cpython-38-darwin.so.

In ctypes codepath, this happens because our loader explicitly checks if the library with requested basename exists in _MEIPASS and if it does, returns it. I.e.,

def _frozen_name(name):
if name:
frozen_name = os.path.join(sys._MEIPASS, os.path.basename(name))
if os.path.exists(frozen_name) and not os.path.isdir(frozen_name):
name = frozen_name
return name

called from

class PyInstallerCDLL(ctypes.CDLL):
def __init__(self, name, *args, **kwargs):
name = _frozen_name(name)
try:
super(PyInstallerCDLL, self).__init__(name, *args, **kwargs)
except Exception as base_error:
raise PyInstallerImportError(name) from base_error

The same thing happens with cffi, although the mechanism is different there, but I suspect it involves this:

if sys.platform.startswith('darwin'):
try:
from ctypes.macholib import dyld
dyld.DEFAULT_LIBRARY_FALLBACK.insert(0, sys._MEIPASS)
except ImportError:
# Do nothing when module 'ctypes' is not available.
pass

So long story short, we find a library that should not be found. (Although on the other hand, I understand why also checking _MEIPASS might be needed in other scenarios).

@rokm rokm added the bug label Feb 24, 2021
@rokm
Copy link
Member

rokm commented Feb 24, 2021

@Robby0318, since your log indicates that you're using ctypes codepath, as a work-around you can try disabling that _frozen_name rewrite in your local PyInstaller installation (in site-packages/PyInstaller/loader/pyiboot01_bootstrap.py).

For example, try changing:

def _frozen_name(name): 
    if name: 
        [...]

to

def _frozen_name(name): 
    if name and not os.path.isabs(name):
        [...]

and rebuild your application. (Although whether this will work depends a lot on behavior of other packages that you're using...)

rokm added a commit to rokm/pyinstaller that referenced this issue Mar 5, 2021
…ion libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly into _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resultingin inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (pyinstaller#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 5, 2021
On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly into _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as pyinstaller#5583.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 5, 2021
…ion libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly in the _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resulting in inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (pyinstaller#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 5, 2021
On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly in the _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as in pyinstaller#5583.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 5, 2021
…ion libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly in the _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resulting in inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (pyinstaller#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 5, 2021
On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly in the _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as in pyinstaller#5583.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 5, 2021
…ion libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly in the _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resulting in inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (pyinstaller#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 5, 2021
On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly in the _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as in pyinstaller#5583.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 6, 2021
…ion libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly in the _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resulting in inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (pyinstaller#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 6, 2021
On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly in the _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as in pyinstaller#5583.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 18, 2021
…ion libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly in the _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resulting in inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (pyinstaller#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.
rokm added a commit to rokm/pyinstaller that referenced this issue Mar 18, 2021
On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly in the _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as in pyinstaller#5583.
@Legorooj Legorooj added the @high label Mar 21, 2021
rokm added a commit to rokm/pyinstaller that referenced this issue Apr 1, 2021
…ion libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly in the _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resulting in inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (pyinstaller#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.
rokm added a commit to rokm/pyinstaller that referenced this issue Apr 1, 2021
On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly in the _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as in pyinstaller#5583.
rokm added a commit to rokm/pyinstaller that referenced this issue Apr 8, 2021
…ion libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly in the _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resulting in inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (pyinstaller#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.
rokm added a commit to rokm/pyinstaller that referenced this issue Apr 8, 2021
On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly in the _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as in pyinstaller#5583.
rokm added a commit to rokm/pyinstaller that referenced this issue Apr 13, 2021
…ion libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly in the _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resulting in inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (pyinstaller#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.
rokm added a commit to rokm/pyinstaller that referenced this issue Apr 13, 2021
On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly in the _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as in pyinstaller#5583.
@tom-james-watson
Copy link

I can confirm that my issue (pyinstaller/pyinstaller-hooks-contrib#109) was fixed by #5604. Thanks, @rokm!

Legorooj added a commit that referenced this issue Apr 16, 2021
…5604)

* tests: add test for ctypes.CDLL incorrectly picking up builtin extension libraries

On linux and macOS, some of the built-ins are provided as extensions
(e.g., _sha1.cpython-39-x86_64-linux-gnu.so) that originally reside
in python3.X/lib-dynload directory. This directory is not in the
ctypes' library search path, therefore running
ctypes.CDLL('_sha1.cpython-39-x86_64-linux-gnu.so')
in python interpreter will come up empty.

In a frozen application, however, these extensions end up collected
directly in the _MEIPASS, which is searched by ctypes (because search
behavior is explicitly overriden by the hook). Therefore, trying to
load the extension's library file via ctypes.CDLL() will succeed,
resulting in inconsistent behavior between unfrozen and frozen
program.

On macOS, this causes issues with `pycryptodomex` (#5583), which,
due to its library search implementation, ends up with handle of
`_sha1` extension module instead of its private extension/library
 with the same name prefix.

* building: collect built-in extensions into lib-dynload sub-directory

On macOS and linux, some of the python's built-ins have extension
modules that originally reside in python3.X/lib-dynload directory.
This directory is in sys.path, therefore the collected extensions
have no parent directory and end up directly in the _MEIPASS.

This commit explicitly diverts such extensions into lib-dynload
sub-directory in the _MEIPASS.

In addition to decluttering the _MEIPASS on linux and macOS, this
also prevents ctypes.CDLL() from picking up the extensions'
shared libraries and causing inconsistent behavior between
frozen and unfrozen application, which in some corner cases
leads to issues with shadowing, such as in #5583.

* bootloader: add _MEIPASS/lib-dynload to the initial sys.path

* bootloader: ensure pypath and pypath_w buffers are of same size

Having pypath with greater size than pypath_w invites potential
trouble when path to executable is long enough that length of
pypath string exceeds PATH_MAX. And it makes little sense for the
two buffers to be of differently sized, anyway.

* rebuild bootloaders

Co-authored-by: Legorooj <legorooj@protonmail.com>
bwoodsend added a commit to bwoodsend/pyinstaller that referenced this issue Jun 3, 2021
Now `python setup.py wheel_darwin_64bit` will generate a wheel whose
platform (architecture and macOS deployment target) matches those of
the bootloaders currently present in `PyInstaller/bootloader/Darwin-64bit`.

An unfortunate, but minor, side-effect of this change is that PyInstaller
itself must be installed to run `setup.py wheel_darwin_64bit`. As
this command isn't needed to install PyInstaller, we avoid any need
for bootstrap-installing.
bwoodsend added a commit to bwoodsend/pyinstaller that referenced this issue Jun 3, 2021
…5583)

Doing so enables the sdist to serve as a fallback if pip is too old
to recognise the universal2 tagged wheel.
bwoodsend added a commit to bwoodsend/pyinstaller that referenced this issue Jun 3, 2021
Now `python setup.py wheel_darwin_64bit` will generate a wheel whose
platform (architecture and macOS deployment target) matches those of
the bootloaders currently present in `PyInstaller/bootloader/Darwin-64bit`.

An unfortunate, but minor, side-effect of this change is that PyInstaller
itself must be installed to run `setup.py wheel_darwin_64bit`. As
this command isn't needed to install PyInstaller, we avoid any need
for bootstrap-installing.
bwoodsend added a commit to bwoodsend/pyinstaller that referenced this issue Jun 3, 2021
…5583)

Doing so enables the sdist to serve as a fallback if pip is too old
to recognise the universal2 tagged wheel.
bwoodsend added a commit that referenced this issue Jun 3, 2021
Now `python setup.py wheel_darwin_64bit` will generate a wheel whose
platform (architecture and macOS deployment target) matches those of
the bootloaders currently present in `PyInstaller/bootloader/Darwin-64bit`.

An unfortunate, but minor, side-effect of this change is that PyInstaller
itself must be installed to run `setup.py wheel_darwin_64bit`. As
this command isn't needed to install PyInstaller, we avoid any need
for bootstrap-installing.
bwoodsend added a commit that referenced this issue Jun 3, 2021
Doing so enables the sdist to serve as a fallback if pip is too old
to recognise the universal2 tagged wheel.
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants