From 3a59d555f3c5da51a2168242936375134fac1876 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Tue, 29 Sep 2026 16:44:44 -0500 Subject: [PATCH 1/2] Export only Init functions from old openssl and digest extensions Ruby 1.8-2.3's openssl extension defines and exports stand-ins for functions its OpenSSL lacks (X509_STORE_set_ex_data, SSL_SESSION_cmp), and 1.8's digest/sha2 exports SHA256_Transform. With the pg gem required before openssl, the system libcrypto.so.3 is already loaded and those calls bind to OpenSSL 3's functions, which segfault on 1.x structs (seen on 1.8.7, 1.9.3, 2.0.0, 2.1.10). Link these extensions with a version script that exports only Init_*, and have check_openssl_hidden! fail if they export anything else. --- libexec/package.rb | 40 ++++++++++++++++++++++++++++++++++++---- recipes/series.yml | 17 +++++++++++++++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/libexec/package.rb b/libexec/package.rb index 94d5184..0d6538b 100755 --- a/libexec/package.rb +++ b/libexec/package.rb @@ -409,6 +409,25 @@ def apply_source_patches(source) # is declared inline without static, which C99 inline semantics (the default since # GCC 5) turn into an undefined reference at link time. inreplace(File.join(source, "lex.c"), "struct kwtable *\nrb_reserved_word", "static struct kwtable *\nrb_reserved_word") + when "init_only_exports" + # Old openssl extensions define stand-ins for functions their OpenSSL lacks + # (X509_STORE_set_ex_data, SSL_SESSION_cmp, ...), and 1.8's digest/sha2 has its own + # SHA256_Transform, all exported. When a system libcrypto.so.3 is already loaded + # (pg gem required first), calls to them bind to OpenSSL 3's functions, which crash + # on the old structs. Nothing links against these extensions, so they export only + # their Init function. The flag stays in their Makefiles, not rbconfig. + %w[openssl digest/md5 digest/sha1 digest/sha2 digest/rmd160].each do |name| + ext = File.join(source, "ext", name) + next unless File.exist?(File.join(ext, "extconf.rb")) + + File.write(File.join(ext, "exports.map"), "{\n global: Init_*;\n local: *;\n};\n") + extconf = File.join(ext, "extconf.rb") + content = File.read(extconf) + raise PackageError, "#{extconf}: create_makefile not found" unless content.match?(/^\s*create_makefile\(/) + File.write(extconf, content.sub(/^(\s*)create_makefile\(/) do + "#{$1}$DLDFLAGS << \" -Wl,--version-script=\#{File.expand_path(\"exports.map\", $srcdir)}\"\n#{$1}create_makefile(" + end) + end else raise PackageError, "unknown source patch: #{name}" end @@ -1063,12 +1082,25 @@ def check_abi!(root) end end - # See ruby_build_env: an exported OpenSSL symbol lets a system libpq bind to it. + # An exported symbol that the system OpenSSL also defines gets bound to it (or it to + # them) when both are loaded: see ruby_build_env and the init_only_exports patch. The + # bundled OpenSSL must be hidden everywhere, and the extensions that wrap or stand in + # for it may export nothing but their Init function. def check_openssl_hidden!(root) - exporters = Dir.glob(File.join(root, "**", "*.so")).select do |path| - capture("nm", "-D", "--defined-only", path, allow_failure: true).match?(/ (SSL_new|CRYPTO_malloc)$/) + offenders = Dir.glob(File.join(root, "**", "*.so")).filter_map do |path| + symbols = capture("nm", "-D", "--defined-only", path, allow_failure: true).lines.map { |line| line.split.last } + symbols = if init_only_exports? && path.match?(%r{/(openssl|digest/(md5|sha1|sha2|rmd160))\.so\z}) + symbols.grep_v(/\A(Init_\w+|_init|_fini|_edata|_end|__bss_start)\z/) + else + symbols.grep(/\A(SSL_new|CRYPTO_malloc)\z/) + end + "#{path}: #{symbols.first(5).join(", ")}" if symbols.any? end - raise PackageError, "OpenSSL symbols exported by:\n #{exporters.join("\n ")}" unless exporters.empty? + raise PackageError, "OpenSSL symbols exported by:\n #{offenders.join("\n ")}" unless offenders.empty? + end + + def init_only_exports? + Array(@series["patches"]).include?("init_only_exports") end def package! diff --git a/recipes/series.yml b/recipes/series.yml index 9b74979..dc1955e 100644 --- a/recipes/series.yml +++ b/recipes/series.yml @@ -36,7 +36,9 @@ series: # because their openssl extensions predate OpenSSL 3; -fno-strict-overflow because # their fixnum overflow checks rely on signed overflow wrapping, which GCC exploits from # -O2 up (a 1.8.7 built without it evaluates 2**64 to 0); readline through a bundled - # libedit; no bundled msgpack/bootsnap; a version-appropriate native gem as the test. + # libedit; no bundled msgpack/bootsnap; a version-appropriate native gem as the test; + # init_only_exports so their openssl and digest extensions can't collide with a system + # OpenSSL 3 that the pg gem loads into the same process. "1.8": openssl: openssl1.0 baseruby: none @@ -45,7 +47,7 @@ series: install_doc: false install_target: install-nodoc # 1.8 has no --disable-install-doc cflags: "-O3 -fno-strict-overflow" - patches: [lex_c99] + patches: [lex_c99, init_only_exports] freshen_config_guess: true extra_out_ext: [tk, tcltklib, gdbm, dbm] # removed from the tree: 1.8 has no --with-out-ext rubygems: rubygems1.8 @@ -56,6 +58,7 @@ series: test_native_gem: { name: json, version: 1.8.6 } "1.9": openssl: openssl1.0 + patches: [init_only_exports] baseruby: none use_libedit: true install_doc: false @@ -70,6 +73,7 @@ series: test_native_gem: { name: json, version: 1.8.6 } "2.0": openssl: openssl1.0 + patches: [init_only_exports] baseruby: none use_libedit: true install_doc: false @@ -83,6 +87,7 @@ series: test_native_gem: { name: json, version: 1.8.6 } "2.1": openssl: openssl1.0 + patches: [init_only_exports] baseruby: none use_libedit: true install_doc: false @@ -95,6 +100,7 @@ series: test_native_gem: { name: json, version: 1.8.6 } "2.2": openssl: openssl1.0 + patches: [init_only_exports] baseruby: none use_libedit: true install_doc: false @@ -107,6 +113,7 @@ series: test_native_gem: { name: json, version: 1.8.6 } "2.3": openssl: openssl1.0 + patches: [init_only_exports] baseruby: none use_libedit: true install_doc: false @@ -119,6 +126,7 @@ series: test_native_gem: { name: json, version: 1.8.6 } "2.4": openssl: openssl1.1 + patches: [init_only_exports] baseruby: none # 2.4's configure rejects --with-baseruby=no use_libedit: true install_doc: false @@ -131,6 +139,7 @@ series: test_native_gem: { name: msgpack, version: 1.4.2 } "2.5": openssl: openssl1.1 + patches: [init_only_exports] baseruby: "no" use_libedit: true install_doc: false @@ -143,6 +152,7 @@ series: test_native_gem: { name: msgpack, version: 1.4.2 } "2.6": openssl: openssl1.1 + patches: [init_only_exports] baseruby: "no" use_libedit: true install_doc: false @@ -156,6 +166,7 @@ series: test_native_gem: { name: msgpack, version: 1.4.2 } "2.7": openssl: openssl1.1 + patches: [init_only_exports] baseruby: "no" readline_ext: false # readline.rb falls back to reline from 2.7 install_doc: false @@ -169,6 +180,7 @@ series: test_native_gem: { name: msgpack, version: 1.4.2 } "3.0": openssl: openssl1.1 # 3.0's openssl extension predates OpenSSL 3 + patches: [init_only_exports] readline_ext: false install_doc: false extra_out_ext: [gdbm, dbm] @@ -179,6 +191,7 @@ series: test_native_gem: { name: msgpack, version: 1.4.2 } "3.1": openssl: openssl1.1 # OpenSSL 3 is supported but this keeps 2.4–3.1 uniform + patches: [init_only_exports] readline_ext: false install_doc: false extra_out_ext: [gdbm, dbm] From caf32045ba7eaec76d30daa6c394f1d420174208 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Tue, 29 Sep 2026 16:44:44 -0500 Subject: [PATCH 2/2] Add Ruby 2.7.0-2.7.7, 3.0.0-3.0.6, 3.1.0-3.1.6 and 3.2.0 Hatchbox's S3 bucket carries these, so the fork needs them before it replaces S3. 3.2.0 was dropped upstream for a header path bug; it now builds and compiles native gems (pg from source) in both variants. --- recipes/rubies.yml | 92 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/recipes/rubies.yml b/recipes/rubies.yml index 5e8dee4..f0cc08e 100644 --- a/recipes/rubies.yml +++ b/recipes/rubies.yml @@ -40,18 +40,110 @@ rubies: series: '2.6' url: https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.10.tar.bz2 sha256: 399e1f13e7fedc3c6ae2ff541bbf26c44dfb63b07b6c186fdd15b4e526e27e9c + 2.7.0: + series: '2.7' + url: https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.0.tar.gz + sha256: 8c99aa93b5e2f1bc8437d1bbbefd27b13e7694025331f77245d0c068ef1f8cbe + 2.7.1: + series: '2.7' + url: https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.1.tar.gz + sha256: d418483bdd0000576c1370571121a6eb24582116db0b7bb2005e90e250eae418 + 2.7.2: + series: '2.7' + url: https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.2.tar.gz + sha256: 6e5706d0d4ee4e1e2f883db9d768586b4d06567debea353c796ec45e8321c3d4 + 2.7.3: + series: '2.7' + url: https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.3.tar.gz + sha256: 8925a95e31d8f2c81749025a52a544ea1d05dad18794e6828709268b92e55338 + 2.7.4: + series: '2.7' + url: https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.4.tar.gz + sha256: 3043099089608859fc8cce7f9fdccaa1f53a462457e3838ec3b25a7d609fbc5b + 2.7.5: + series: '2.7' + url: https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.5.tar.gz + sha256: 2755b900a21235b443bb16dadd9032f784d4a88f143d852bc5d154f22b8781f1 + 2.7.6: + series: '2.7' + url: https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.6.tar.gz + sha256: e7203b0cc09442ed2c08936d483f8ac140ec1c72e37bb5c401646b7866cb5d10 + 2.7.7: + series: '2.7' + url: https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.7.tar.gz + sha256: e10127db691d7ff36402cfe88f418c8d025a3f1eea92044b162dd72f0b8c7b90 2.7.8: series: '2.7' url: https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.8.tar.gz sha256: c2dab63cbc8f2a05526108ad419efa63a67ed4074dbbcf9fc2b1ca664cb45ba0 + 3.0.0: + series: '3.0' + url: https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.0.tar.gz + sha256: a13ed141a1c18eb967aac1e33f4d6ad5f21be1ac543c344e0d6feeee54af8e28 + 3.0.1: + series: '3.0' + url: https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.1.tar.gz + sha256: 369825db2199f6aeef16b408df6a04ebaddb664fb9af0ec8c686b0ce7ab77727 + 3.0.2: + series: '3.0' + url: https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.2.tar.gz + sha256: 5085dee0ad9f06996a8acec7ebea4a8735e6fac22f22e2d98c3f2bc3bef7e6f1 + 3.0.3: + series: '3.0' + url: https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.3.tar.gz + sha256: 3586861cb2df56970287f0fd83f274bd92058872d830d15570b36def7f1a92ac + 3.0.4: + series: '3.0' + url: https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.4.tar.gz + sha256: 70b47c207af04bce9acea262308fb42893d3e244f39a4abc586920a1c723722b + 3.0.5: + series: '3.0' + url: https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.5.tar.gz + sha256: 9afc6380a027a4fe1ae1a3e2eccb6b497b9c5ac0631c12ca56f9b7beb4848776 + 3.0.6: + series: '3.0' + url: https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.6.tar.gz + sha256: 6e6cbd490030d7910c0ff20edefab4294dfcd1046f0f8f47f78b597987ac683e 3.0.7: series: '3.0' url: https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.7.tar.gz sha256: 2a3411977f2850431136b0fab8ad53af09fb74df2ee2f4fb7f11b378fe034388 + 3.1.0: + series: '3.1' + url: https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.0.tar.gz + sha256: 50a0504c6edcb4d61ce6b8cfdbddaa95707195fab0ecd7b5e92654b2a9412854 + 3.1.1: + series: '3.1' + url: https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.1.tar.gz + sha256: fe6e4782de97443978ddba8ba4be38d222aa24dc3e3f02a6a8e7701c0eeb619d + 3.1.2: + series: '3.1' + url: https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.2.tar.gz + sha256: 61843112389f02b735428b53bb64cf988ad9fb81858b8248e22e57336f24a83e + 3.1.3: + series: '3.1' + url: https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.3.tar.gz + sha256: 5ea498a35f4cd15875200a52dde42b6eb179e1264e17d78732c3a57cd1c6ab9e + 3.1.4: + series: '3.1' + url: https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.4.tar.gz + sha256: a3d55879a0dfab1d7141fdf10d22a07dbf8e5cdc4415da1bde06127d5cc3c7b6 + 3.1.5: + series: '3.1' + url: https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.5.tar.gz + sha256: 3685c51eeee1352c31ea039706d71976f53d00ab6d77312de6aa1abaf5cda2c5 + 3.1.6: + series: '3.1' + url: https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.6.tar.gz + sha256: 0d0dafb859e76763432571a3109d1537d976266be3083445651dc68deed25c22 3.1.7: series: '3.1' url: https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.7.tar.gz sha256: 0556acd69f141ddace03fa5dd8d76e7ea0d8f5232edf012429579bcdaab30e7b + 3.2.0: + series: '3.2' + url: https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0.tar.gz + sha256: daaa78e1360b2783f98deeceb677ad900f3a36c0ffa6e2b6b19090be77abc272 3.2.1: series: '3.2' url: https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.1.tar.xz