From b017075e26a1096a2f69354b410c6553a32e3f2a Mon Sep 17 00:00:00 2001 From: "Jonathan J. Helmus" Date: Fri, 23 Oct 2026 08:48:28 -0500 Subject: [PATCH 1/3] Avoid newer libgcc_s helper symbols in CPython 3.16 builds Statically link the helper functions used for half-precision conversions used in Python 3.16 from libgcc.a. This is not needed when cross-compiling as only an older libgcc is available in the sysroot. Native builds have the newer libgcc that is included in the LLVM bundle. --- cpython-unix/build-cpython.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 8dd0240b8..e383cf3b4 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -313,6 +313,24 @@ fi CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC -I${TOOLS_PATH}/deps/include -I${TOOLS_PATH}/deps/include/ncursesw" LDFLAGS="${EXTRA_TARGET_LDFLAGS} -L${TOOLS_PATH}/deps/lib" +# LTO can introduce these helpers after the static libgcc archive is scanned. +# Force them from libgcc.a to avoid newer libgcc_s symbol versions on old systems. +CPYTHON_RUNTIME_LDFLAGS="" +if [[ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_16}" ]]; then + case "${TARGET_TRIPLE}" in + aarch64-unknown-linux-gnu) + CPYTHON_RUNTIME_LDFLAGS="-Wl,-u,__extendhftf2" + ;; + x86_64-unknown-linux-gnu) + CPYTHON_RUNTIME_LDFLAGS="-Wl,-u,__extendhfsf2 -Wl,-u,__extendhfxf2 -Wl,-u,__truncdfhf2" + ;; + esac +fi +export CPYTHON_RUNTIME_LDFLAGS +if [[ -n "${CPYTHON_RUNTIME_LDFLAGS}" ]]; then + LDFLAGS+=" ${CPYTHON_RUNTIME_LDFLAGS}" +fi + if [[ -n "${LINUX_UAPI_INCLUDE_ARCH:-}" ]]; then if [[ ! -d "${TOOLS_PATH}/deps/linux-uapi/usr/include/${LINUX_UAPI_INCLUDE_ARCH}" ]]; then echo "missing linux-uapi package for ${LINUX_UAPI_INCLUDE_ARCH}" @@ -1101,6 +1119,10 @@ replace_in_all("-fdebug-default-version=4", "") # not have LLVM installed. replace_in_all("-fuse-ld=lld", "") +# These flags are only needed when linking CPython itself. +for flag in os.environ.get("CPYTHON_RUNTIME_LDFLAGS", "").split(): + replace_in_all(flag, "") + # Target sysroots and GCC installations only exist in the build container. # Downstream native extension builds should not inherit cross-compilation flags. for flag in os.environ.get("EXTRA_TARGET_CFLAGS", "").split(): From b63fed3877854e5c702fbe17071aecb315a7747a Mon Sep 17 00:00:00 2001 From: zaniebot <242828183+zaniebot@users.noreply.github.com> Date: Wed, 21 Oct 2026 19:00:31 +0000 Subject: [PATCH 2/3] Validate GCC runtime symbol versions in Linux distributions (cherry picked from commit f96a915828547cf6631a50dd46d5c96409d03249) --- src/validation.rs | 145 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 126 insertions(+), 19 deletions(-) diff --git a/src/validation.rs b/src/validation.rs index fa30a1d3e..715ddc51c 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -271,6 +271,27 @@ static GLIBC_MAX_VERSION_BY_TRIPLE: Lazy>> = + Lazy::new(|| { + // Runtime ABI ceilings for the manylinux2014 targets, as recorded by + // https://github.com/pypa/auditwheel/blob/main/src/auditwheel/policy/manylinux-policy.json + // RISC-V uses the GCC 8 runtime from the Buster sysroot, whose newest + // symbol version is `GCC_7.0.0`. + [ + ("aarch64-unknown-linux-gnu", "4.7.0"), + ("armv7-unknown-linux-gnueabi", "4.7.0"), + ("armv7-unknown-linux-gnueabihf", "4.7.0"), + ("riscv64-unknown-linux-gnu", "7.0.0"), + ("x86_64-unknown-linux-gnu", "4.8.0"), + ("x86_64_v2-unknown-linux-gnu", "4.8.0"), + ("x86_64_v3-unknown-linux-gnu", "4.8.0"), + ("x86_64_v4-unknown-linux-gnu", "4.8.0"), + ] + .into_iter() + .map(|(triple, version)| (triple, version_compare::Version::from(version).unwrap())) + .collect() + }); + static ELF_ALLOWED_LIBRARIES_BY_TRIPLE: Lazy>> = Lazy::new(|| { [ @@ -969,6 +990,33 @@ impl ValidationContext { } } +fn validate_elf_symbol_version( + target_triple: &str, + path: &Path, + name: &str, + version: &str, +) -> Option { + let (namespace, version) = version.split_once('_')?; + let (library, max_version) = match namespace { + "GLIBC" => ( + "glibc", + GLIBC_MAX_VERSION_BY_TRIPLE + .get(target_triple) + .expect("max glibc version not defined for target triple"), + ), + "GCC" => ("libgcc", GCC_MAX_VERSION_BY_TRIPLE.get(target_triple)?), + _ => return None, + }; + let version = version_compare::Version::from(version).expect("unable to parse version"); + + (&version > max_version).then(|| { + format!( + "{} references too new {library} symbol {name:?} ({version} > {max_version})", + path.display(), + ) + }) +} + fn validate_elf>( context: &mut ValidationContext, json: &PythonJsonMain, @@ -1087,10 +1135,6 @@ fn validate_elf>( } } - let wanted_glibc_max_version = GLIBC_MAX_VERSION_BY_TRIPLE - .get(target_triple) - .expect("max glibc version not defined for target triple"); - let sections = elf.sections(endian, data)?; let versions = sections.versions(endian, data)?; @@ -1226,21 +1270,10 @@ fn validate_elf>( } if let Some(version) = version_version { - let parts: Vec<&str> = version.splitn(2, '_').collect(); - - if parts.len() == 2 && parts[0] == "GLIBC" { - let v = version_compare::Version::from(parts[1]) - .expect("unable to parse version"); - - if &v > wanted_glibc_max_version { - context.errors.push(format!( - "{} references too new glibc symbol {:?} ({} > {})", - path.display(), - name, - v, - wanted_glibc_max_version, - )); - } + if let Some(error) = + validate_elf_symbol_version(target_triple, path, &name, &version) + { + context.errors.push(error); } } } @@ -2463,3 +2496,77 @@ pub fn command_validate_distribution(args: &ArgMatches) -> Result<()> { Err(anyhow!("errors found")) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn symbol_error(target: &str, symbol: &str, version: &str) -> Option { + validate_elf_symbol_version( + target, + Path::new("python/install/lib/libpython3.16.so.1.0"), + symbol, + version, + ) + } + + #[test] + fn libgcc_symbols_respect_target_runtime_limits() { + for (target, allowed, rejected) in [ + ("aarch64-unknown-linux-gnu", "GCC_4.7.0", "GCC_4.8.0"), + ("armv7-unknown-linux-gnueabi", "GCC_4.7.0", "GCC_4.8.0"), + ("armv7-unknown-linux-gnueabihf", "GCC_4.7.0", "GCC_4.8.0"), + ("x86_64-unknown-linux-gnu", "GCC_4.8.0", "GCC_7.0.0"), + ("x86_64_v2-unknown-linux-gnu", "GCC_4.8.0", "GCC_7.0.0"), + ("x86_64_v3-unknown-linux-gnu", "GCC_4.8.0", "GCC_7.0.0"), + ("x86_64_v4-unknown-linux-gnu", "GCC_4.8.0", "GCC_7.0.0"), + ("riscv64-unknown-linux-gnu", "GCC_7.0.0", "GCC_11.0"), + ] { + assert!(symbol_error(target, "runtime_symbol", "GCC_3.0").is_none()); + assert!(symbol_error(target, "runtime_symbol", allowed).is_none()); + assert!(symbol_error(target, "runtime_symbol", rejected).is_some()); + } + } + + #[test] + fn rejects_native_half_float_runtime_symbols() { + for symbol in ["__extendhfsf2", "__extendhfxf2", "__truncdfhf2"] { + assert_eq!( + symbol_error("x86_64-unknown-linux-gnu", symbol, "GCC_12.0.0").unwrap(), + format!( + "python/install/lib/libpython3.16.so.1.0 references too new libgcc symbol \ + {symbol:?} (12.0.0 > 4.8.0)" + ) + ); + } + assert_eq!( + symbol_error("aarch64-unknown-linux-gnu", "__extendhftf2", "GCC_11.0").unwrap(), + "python/install/lib/libpython3.16.so.1.0 references too new libgcc symbol \ + \"__extendhftf2\" (11.0 > 4.7.0)" + ); + } + + #[test] + fn glibc_symbols_respect_target_runtime_limits() { + assert!(symbol_error("x86_64-unknown-linux-gnu", "runtime_symbol", "GLIBC_2.17").is_none()); + assert!(symbol_error("x86_64-unknown-linux-gnu", "runtime_symbol", "GLIBC_2.18").is_some()); + assert!( + symbol_error("riscv64-unknown-linux-gnu", "runtime_symbol", "GLIBC_2.28").is_none() + ); + assert!( + symbol_error("riscv64-unknown-linux-gnu", "runtime_symbol", "GLIBC_2.29").is_some() + ); + assert!( + symbol_error("x86_64-unknown-linux-musl", "runtime_symbol", "GLIBC_2.2.5").is_some() + ); + } + + #[test] + fn every_target_allowing_libgcc_has_a_runtime_limit() { + for (target, libraries) in ELF_ALLOWED_LIBRARIES_BY_TRIPLE.iter() { + if libraries.contains(&"libgcc_s.so.1") { + assert!(GCC_MAX_VERSION_BY_TRIPLE.contains_key(target), "{target}"); + } + } + } +} From a8af9b12abb1128d116b611dae654bb20120e160 Mon Sep 17 00:00:00 2001 From: "Jonathan J. Helmus" Date: Fri, 23 Oct 2026 15:09:20 -0500 Subject: [PATCH 3/3] use LDFLAGS_NODIST --- cpython-unix/build-cpython.sh | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index e383cf3b4..33371339d 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -313,23 +313,19 @@ fi CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC -I${TOOLS_PATH}/deps/include -I${TOOLS_PATH}/deps/include/ncursesw" LDFLAGS="${EXTRA_TARGET_LDFLAGS} -L${TOOLS_PATH}/deps/lib" -# LTO can introduce these helpers after the static libgcc archive is scanned. -# Force them from libgcc.a to avoid newer libgcc_s symbol versions on old systems. -CPYTHON_RUNTIME_LDFLAGS="" +# Python 3.16+ can require half-float helpers with newer libgcc_s symbol versions. +# Request these helpers explicitly so the linker pulls them from libgcc.a. +LDFLAGS_NODIST="" if [[ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_16}" ]]; then case "${TARGET_TRIPLE}" in aarch64-unknown-linux-gnu) - CPYTHON_RUNTIME_LDFLAGS="-Wl,-u,__extendhftf2" + LDFLAGS_NODIST="-Wl,-u,__extendhftf2" ;; x86_64-unknown-linux-gnu) - CPYTHON_RUNTIME_LDFLAGS="-Wl,-u,__extendhfsf2 -Wl,-u,__extendhfxf2 -Wl,-u,__truncdfhf2" + LDFLAGS_NODIST="-Wl,-u,__extendhfsf2 -Wl,-u,__extendhfxf2 -Wl,-u,__truncdfhf2" ;; esac fi -export CPYTHON_RUNTIME_LDFLAGS -if [[ -n "${CPYTHON_RUNTIME_LDFLAGS}" ]]; then - LDFLAGS+=" ${CPYTHON_RUNTIME_LDFLAGS}" -fi if [[ -n "${LINUX_UAPI_INCLUDE_ARCH:-}" ]]; then if [[ ! -d "${TOOLS_PATH}/deps/linux-uapi/usr/include/${LINUX_UAPI_INCLUDE_ARCH}" ]]; then @@ -779,6 +775,7 @@ fi BOLT_COMMON_FLAGS="${BOLT_COMMON_FLAGS:-}" BOLT_APPLY_FLAGS="${BOLT_APPLY_FLAGS:-}" \ CFLAGS=$CFLAGS CPPFLAGS=$CFLAGS CFLAGS_JIT=$CFLAGS_JIT LDFLAGS=$LDFLAGS \ + LDFLAGS_NODIST="${LDFLAGS_NODIST}" \ ./configure ${CONFIGURE_FLAGS} # Supplement produced Makefile with our modifications. @@ -1119,10 +1116,6 @@ replace_in_all("-fdebug-default-version=4", "") # not have LLVM installed. replace_in_all("-fuse-ld=lld", "") -# These flags are only needed when linking CPython itself. -for flag in os.environ.get("CPYTHON_RUNTIME_LDFLAGS", "").split(): - replace_in_all(flag, "") - # Target sysroots and GCC installations only exist in the build container. # Downstream native extension builds should not inherit cross-compilation flags. for flag in os.environ.get("EXTRA_TARGET_CFLAGS", "").split():