Skip to content

Commit f794daa

Browse files
committed
Add an rpath to bin/python3 on glibc platforms to fix astral-sh#619
Even though the Python interpreter no longer needs libpython3.x.so, it turns out some extension modules (incorrectly) do, and miraculously, allowing them to find libpython3.x.so doesn't actually break things, for reasons detailed in the comment. So set an rpath that allows libpython3.x.so to be loaded if needed by some other library, even though we won't use that ourselves. We are already doing this on musl; do it on glibc too. Note that this change does not risk users who want to make bin/python3 setuid or setcap (e.g. astral-sh#576); while the rpath is presumably ignored for privileged binaries, there is no error message, and the binary launches fine, and _because_ we do not need the rpath in order for the interpreter to work, everything (except these misbuilt extension modules) works.
1 parent 784ea01 commit f794daa

File tree

1 file changed

+83
-23
lines changed

1 file changed

+83
-23
lines changed

cpython-unix/build-cpython.sh

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -688,45 +688,105 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then
688688
-change /install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} @executable_path/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} \
689689
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
690690
fi
691-
else
691+
else # (not macos)
692692
LIBPYTHON_SHARED_LIBRARY_BASENAME=libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.so.1.0
693693
LIBPYTHON_SHARED_LIBRARY=${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}
694694

695-
if [ "${CC}" == "musl-clang" ]; then
696-
# musl does not support $ORIGIN in DT_NEEDED, so we use RPATH instead. This could be
697-
# problematic, i.e., we could load the shared library from the wrong location if
698-
# `LD_LIBRARY_PATH` is set, but there's not a clear alternative at this time. The
699-
# long term solution is probably to statically link to libpython instead.
695+
# Although we are statically linking libpython, some extension
696+
# modules link against libpython.so even though they are not
697+
# supposed to do that. If you try to import them on an
698+
# interpreter statically linking libpython, all the symbols they
699+
# need are resolved from the main program (because neither glibc
700+
# nor musl has two-level namespaces), so there is hopefully no
701+
# correctness risk, but they need to be able to successfully
702+
# find libpython.so in order to load the module. To allow such
703+
# extensions to load, we set an rpath to point at our lib
704+
# directory, so that if anyone ever tries to find a libpython,
705+
# they successfully find one. See
706+
# https://github.com/astral-sh/python-build-standalone/issues/619
707+
# for some reports of extensions that need this workaround.
708+
#
709+
# Note that this matches the behavior of Debian/Ubuntu/etc.'s
710+
# interpreter (if package libpython3.x is installed, which it
711+
# usually is thanks to gdb, vim, etc.), because libpython is in
712+
# the system lib directory, as well as the behavior in practice
713+
# on conda-forge miniconda and probably other Conda-family
714+
# Python distributions, which too set an rpath.
715+
#
716+
# There is a downside of making this libpython locatable: some user
717+
# code might do e.g.
718+
# ctypes.CDLL(f"libpython3.{sys.version_info.minor}.so.1.0")
719+
# to get at things in the CPython API not exposed to pure
720+
# Python. This code may _silently misbehave_ on a
721+
# static-libpython interpreter, because you are actually using
722+
# the second copy of libpython. For loading static data or using
723+
# accessors, you might get lucky and things will work, with the
724+
# full set of dangers of C undefined behavior being possible.
725+
# However, there are a few reasons we think this risk is
726+
# tolerable. First, we can't actually fix it by not setting the
727+
# rpath - user code may well find a system libpython3.x.so or
728+
# something which is even more likely to break. Second, this
729+
# exact problem happens with Debian, Conda, etc., so it is very
730+
# unlikely (compared to the extension modules case above) that
731+
# any widely-used code has this problem; the risk is largely
732+
# backwards incompatibility of our own builds. Also, it's quite
733+
# easy for users to fix: simply do
734+
# ctypes.CDLL(None)
735+
# (i.e., dlopen(NULL)), to use symbols already in the process;
736+
# this will work reliably on all interpreters regardless of
737+
# whether they statically or dynamically link libpython. Finally,
738+
# we can (and should, at some point) add a warning, error, or
739+
# silent fix to ctypes for user code that does this, which will
740+
# also cover the case of other libpython3.x.so files on the
741+
# library search path that we cannot suppress.
742+
#
743+
# In the past, when we dynamically linked libpython, we avoided
744+
# using an rpath and instead used a DT_NEEDED entry with
745+
# $ORIGIN/../lib/libpython.so, because LD_LIBRARY_PATH takes
746+
# precedence over DT_RUNPATH, and it's not uncommon to have an
747+
# LD_LIBRARY_PATH that points to some sort of unwanted libpython
748+
# (e.g., actions/setup-python does this as of May 2025).
749+
# Now, though, because we're not actually using code from the
750+
# libpython that's loaded and just need _any_ file of that name
751+
# to satisfy the link, that's not a problem. (This also implies
752+
# another approach to the problem: ensure that libraries find an
753+
# empty dummy libpython.so, which allows the link to succeed but
754+
# ensures they do not use any unwanted symbols. That might be
755+
# worth doing at some point.)
756+
patchelf --set-rpath "\$ORIGIN/../lib" \
757+
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
758+
759+
if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
700760
patchelf --set-rpath "\$ORIGIN/../lib" \
701-
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
761+
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
762+
fi
702763

764+
# For libpython3.so (the ABI3 library for embedders), we do
765+
# still dynamically link libpython3.x.so.1.0 (the
766+
# version-specific library), because there is no particular
767+
# speedup/benefit in statically linking libpython into
768+
# libpython3.so, and we'd just be shipping a third copy of the
769+
# libpython code. Therefore we use the old logic for that and
770+
# set an $ORIGIN-relative DT_NEEDED, at least for glibc.
771+
# Unfortunately, musl does not (as of May 2025) support $ORIGIN
772+
# in DT_NEEDED, only in DT_RUNPATH/RPATH, so we did set an rpath
773+
# for bin/python3, and still do for libpython3.so. In both
774+
# cases, we have no concerns/need no workarounds for code
775+
# referencing libpython3.x.so.1.0, because we are actually
776+
# dynamically linking it and so all code will get the real
777+
# libpython3.x.so.1.0 that they want.
778+
if [ "${CC}" == "musl-clang" ]; then
703779
# libpython3.so isn't present in debug builds.
704780
if [ -z "${CPYTHON_DEBUG}" ]; then
705781
patchelf --set-rpath "\$ORIGIN/../lib" \
706782
${ROOT}/out/python/install/lib/libpython3.so
707783
fi
708-
709-
if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
710-
patchelf --set-rpath "\$ORIGIN/../lib" \
711-
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
712-
fi
713784
else
714-
# If we simply set DT_RUNPATH via --set-rpath, LD_LIBRARY_PATH would be used before
715-
# DT_RUNPATH, which could result in confusion at run-time. But if DT_NEEDED contains a
716-
# slash, the explicit path is used.
717-
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
718-
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
719-
720785
# libpython3.so isn't present in debug builds.
721786
if [ -z "${CPYTHON_DEBUG}" ]; then
722787
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
723788
${ROOT}/out/python/install/lib/libpython3.so
724789
fi
725-
726-
if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
727-
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
728-
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
729-
fi
730790
fi
731791
fi
732792
fi

0 commit comments

Comments
 (0)