Ned Deily <n...@python.org> added the comment:

Thanks for the report! I've spent some time investigating it and the story 
behind it turns out to be a bit complicated, so bear with me. It's all tied in 
to Apple's attempts to improve the security of macOS.

As of macOS 10.15 Catalina, Apple introduced new requirements for downloadable 
installer packages, like those we provide for macOS on python.org; in order for 
such packages to be installed with the macOS installer, they would now have to 
be "notarized" by Apple. The notarization process is somewhat similar in 
concept to the process that an app has to go through to be submitted to the Mac 
App Store but with less stringent requirements. In particular, the installer 
package is automatically inspected to ensure that all executables are 
codesigned, are linked with the more-secure "hardened runtime", and do not 
request certain less-secure entitlements. Although originally announced for 
enforcement starting with the release of Catalina in the fall of 2019, Apple 
delayed the enforcement until February 2020 to give application developers more 
time to modify their packages to meet the new requirements. (See, for example, 
https://developer.apple.com/news/?id=12232019a).

The first python.org macOS installers that conformed to the new requirements 
and were notarized were for the 3.8.2 and 3.7.7 releases, staring in 2020-02. 
In those first releases, we used two entitlements:

com.apple.security.cs.disable-library-validation
com.apple.security.cs.disable-executable-page-protection

Some issues were reported (like Issue40198) when using those first releases, so 
we added two additional entitlements for subsequent releases:

com.apple.security.automation.apple-events
com.apple.security.cs.allow-dyld-environment-variables

While we didn't realize it until your issue, that did have an effect on ctype's 
behavior when trying to find shared libraries and frameworks. Using the 
hardened runtime, as now required for notarization, causes the system dlopen() 
interface, which ctypes uses, to no longer search relative paths. The dlopen 
man page (for macOS 11, at least) includes this warning:

  Note: If the main executable is a set[ug]id binary or codesigned with
  entitlements, then all environment variables are ignored, and only a
  full path can be used.

After some experimentation, it looks like that statement isn't exactly correct 
at least on macOS 11 Big Sur: unprefixed paths can still be used to find 
libraries and frameworks in system locations, i.e. /usr/lib and 
/System/Library/Frameworks. But it is true that, when using the hardened 
runtime, dlopen() no longer searches the user-controlled paths /usr/local/lib 
or /Library/Frameworks by default. And there apparently is no way for a 
notarized executable to revert to the previous behavior by adding other 
entitlements (https://developer.apple.com/forums/thread/666066).

With the allow-dyld-environment-variables entitlement included after the 
initial releases, it *is* now possible to change this behavior externally, if 
necessary, by explicitly setting the DYLD_LIBRARY_PATH and/or 
DYLD_FRAMEWORK_PATH environment variables (see man 1 dyld).

To demonstrate using a third-party library in /usr/local/lib (similar results 
with third-party frameworks in /Library/Frameworks):

# ------ python.org 3.7.6, not codesigned ------
$ /usr/local/bin/python3.7
Python 3.7.6 (v3.7.6:43364a7ae0, Dec 18 2019, 14:18:50)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> ctypes.cdll.LoadLibrary('libsodium.dylib')
<CDLL 'libsodium.dylib', handle 7ff62c505750 at 0x7ff620061350>

# ------ python.org 3.7.7, first notarized release ------
$ /usr/local/bin/python3.7
Python 3.7.7 (v3.7.7:d7c567b08f, Mar 10 2020, 02:56:16)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> ctypes.cdll.LoadLibrary('libsodium.dylib')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File 
"/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ctypes/__init__.py",
 line 442, in LoadLibrary
    return self._dlltype(name)
  File 
"/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ctypes/__init__.py",
 line 364, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: dlopen(libsodium.dylib, 6): no suitable image found.  Did find:
        file system relative paths not allowed in hardened programs

$ DYLD_LIBRARY_PATH=/usr/local/lib /usr/local/bin/python3.7
Python 3.7.7 (v3.7.7:d7c567b08f, Mar 10 2020, 02:56:16)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> ctypes.cdll.LoadLibrary('libsodium.dylib')
[...]
OSError: dlopen(libsodium.dylib, 6): no suitable image found.  Did find:
        file system relative paths not allowed in hardened programs


# ------ python.org 3.7.8 and beyond including 3.9.x ------
$ /usr/local/bin/python3.7
Python 3.7.8 (v3.7.8:4b47a5b6ba, Jun 27 2020, 04:47:50)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> ctypes.cdll.LoadLibrary('libsodium.dylib')
[...]
OSError: dlopen(libsodium.dylib, 6): image not found

$ DYLD_LIBRARY_PATH=/usr/local/lib /usr/local/bin/python3.7
Python 3.7.8 (v3.7.8:4b47a5b6ba, Jun 27 2020, 04:47:50)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> ctypes.cdll.LoadLibrary('libsodium.dylib')
<CDLL 'libsodium.dylib', handle 7f9a9fc58ae0 at 0x7f9a90321310>


It is rather unfortunate that, when including the 
allow-dyld-environment-variables entitlement, you now receive a less helpful 
error message "image not found" rather than "file system relative paths not 
allowed in hardened programs".

So, what to do?

1. Remove the use of the hardened runtime?

That would mean that python.org installer packages could not be notarized and 
thus would no longer be installable without going through various onerous steps 
to override Gatekeeper protections (and would presumably also reduce somewhat 
system security). Since the primary target of python.org installers is for 
inexpert users, this option seems unacceptable.

2. Add code to ctypes to try to mimic the previous behavior?

So something like: if the path passed to ctypes is not absolute and the initial 
call to dlopen() fails with an "image not found" error, try again by making an 
absolute path by prepending '/usr/local/lib' and/or '/Library/Frameworks' and 
retrying. I assume we could make that work somehow but the question is should 
we?  That seems very kludgey and would be re-opening a potential security hole 
that Apple clearly thinks is significant and is trying to discourage.

3. Update the ctypes documentation?

In particular, document this change in behavior for codesigned installations 
using the hardened runtime (like python.org installers) and suggest avoid using 
relative paths on macOS or, if necessary, have the DYLD_LIBRARY_PATH or 
DYLD_FRAMEWORK_PATH environment variables set appropriately when launching the 
interpreter. (I did a quick experiment and it seemed that it was not possible 
to change dlopen()'s behavior by setting those variables from inside the 
running interpreter process by using OS.ENVIRON, an unsurprising result.)


It seems to me that changing the ctypes documentation (and adding a changelog 
blurb), option 3, is the least bad of the available options. One would hope 
that this does not affect *too* many third-party packages and user 
applications. Obviously it does some :(

@Ronald, what do you think?

----------
versions: +Python 3.10, Python 3.11 -Python 3.7, Python 3.8

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue43964>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to