diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index cc93f0ad..306c20f8 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -322,6 +322,13 @@ if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_12}" ]; then patch -p1 -i ${ROOT}/patch-test-embed-prevent-segfault.patch fi +# Pad the install name with slashes. We'll replace this with NULs after the build. +if [ -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_11}" ]; then + patch -p1 -i ${ROOT}/patch-python-install-name-padding-3.11.patch +else + patch -p1 -i ${ROOT}/patch-python-install-name-padding.patch +fi + # Most bits look at CFLAGS. But setup.py only looks at CPPFLAGS. # So we need to set both. CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC -I${TOOLS_PATH}/deps/include -I${TOOLS_PATH}/deps/include/ncursesw" @@ -723,6 +730,11 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then -change /install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} @executable_path/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} \ ${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX} fi + + # For cleanness, replace the slash-based padding for the install name with NUL-based + # padding. install_name_tool will not accept trailing NULs so we do it ourselves. Do this + # after any calls to install_name_tool. + ${BUILD_PYTHON} ${ROOT}/repad_install_name.py ${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} else # (not macos) LIBPYTHON_SHARED_LIBRARY_BASENAME=libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.so.1.0 LIBPYTHON_SHARED_LIBRARY=${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} diff --git a/cpython-unix/patch-python-install-name-padding-3.12.patch b/cpython-unix/patch-python-install-name-padding-3.12.patch new file mode 100644 index 00000000..754ea5dd --- /dev/null +++ b/cpython-unix/patch-python-install-name-padding-3.12.patch @@ -0,0 +1,33 @@ +From 2861855c08df773eaa640f1fe354c6f792b649a6 Mon Sep 17 00:00:00 2001 +From: Geoffrey Thomas +Date: Fri, 25 Jul 2025 09:53:37 -0400 +Subject: [PATCH 1/1] Makefile.pre.in: Add padding to libpython.dylib + install_name + +This gives some room for later users to change the name by binary +editing, without requiring install_name_tool or some other Mach-O +writer. +--- + Makefile.pre.in | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/Makefile.pre.in b/Makefile.pre.in +index 8fbcd7ac170..ff88a390cab 100644 +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -760,8 +760,11 @@ libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS) + libpython3.so: libpython$(LDVERSION).so + $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ + ++PADDING_128 := /./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././. ++PADDING_512 := $(PADDING_128)$(PADDING_128)$(PADDING_128)$(PADDING_128) ++ + libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) +- $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ ++ $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)$(PADDING_512)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ + + + libpython$(VERSION).sl: $(LIBRARY_OBJS) +-- +2.39.5 (Apple Git-154) + diff --git a/cpython-unix/patch-python-install-name-padding.patch b/cpython-unix/patch-python-install-name-padding.patch new file mode 100644 index 00000000..f66950ae --- /dev/null +++ b/cpython-unix/patch-python-install-name-padding.patch @@ -0,0 +1,33 @@ +From 676e5422e4ee442508c8a8cb4f8c49123ddb5d66 Mon Sep 17 00:00:00 2001 +From: Geoffrey Thomas +Date: Fri, 25 Jul 2025 09:53:37 -0400 +Subject: [PATCH 1/1] Makefile.pre.in: Add padding to libpython.dylib + install_name + +This gives some room for later users to change the name by binary +editing, without requiring install_name_tool or some other Mach-O +writer. +--- + Makefile.pre.in | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/Makefile.pre.in b/Makefile.pre.in +index 538229220fd..dd0b044abf3 100644 +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -918,8 +918,11 @@ libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS) + libpython3.so: libpython$(LDVERSION).so + $(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^ + ++PADDING_128 := /./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././. ++PADDING_512 := $(PADDING_128)$(PADDING_128)$(PADDING_128)$(PADDING_128) ++ + libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) +- $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ ++ $(CC) -dynamiclib $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)$(PADDING_512)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ + + + libpython$(VERSION).sl: $(LIBRARY_OBJS) +-- +2.39.5 (Apple Git-154) + diff --git a/cpython-unix/repad_install_name.py b/cpython-unix/repad_install_name.py new file mode 100755 index 00000000..bd376d4d --- /dev/null +++ b/cpython-unix/repad_install_name.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# +# usage: repad_install_name.py +# Rewrites /usr/lib/././././libpython.dylib to /usr/lib/libpython.dylib, +# keeping NUL padding in the load command. +# +# Useful references: +# `xcrun --show-sdk-platform-path`/Developer/usr/include/mach-o/loader.h +# https://en.wikipedia.org/wiki/Mach-O (some factual inaccuracies) + +import re +import struct +import sys + + +def rewrite(f): + f.seek(0, 0) + mach_header = f.read(28) + magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = struct.unpack( + "=7I", mach_header + ) + if magic == 0xFEEDFACE: + pass + elif magic == 0xFEEDFACF: + _reserved = f.read(4) + elif magic in (0xCEFAEDFE, 0xCFFAEDFE): + raise RuntimeError("Wrong-endian Mach-O file") + else: + raise RuntimeError("Not a Mach-O file?") + + loadstart = f.tell() + + for i in range(ncmds): + load_command_header = f.read(8) + cmd, cmdsize = struct.unpack("=2I", load_command_header) + load_command_body = f.read(cmdsize - 8) + if cmd == 0xD: # LC_ID_DYLIB + name_offset, timestamp, current_version, compatbility_version = ( + struct.unpack_from("=4I", load_command_body) + ) + bufsize = cmdsize - 24 + if name_offset != 24 or bufsize <= 0: + raise RuntimeError("Malformed load command") + install_name = load_command_body[16:].rstrip(b"\0") + new_install_name, replacements = re.subn(b"/(\./)+", b"/", install_name) + if replacements > 0: + print(f"Rewriting install_name to {new_install_name}") + f.seek(-bufsize, 1) + f.write(new_install_name.ljust(bufsize, b"\0")) + + if f.tell() != loadstart + sizeofcmds: + raise RuntimeError("Unexpected end of load commands, is file corrupt?") + + +if __name__ == "__main__": + with open(sys.argv[1], "r+b") as f: + rewrite(f)