From 3ecee76056e04a0d42648aed55ab9e6b34e27842 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 29 May 2026 14:49:49 +0900 Subject: [PATCH 01/10] [ruby/rubygems] Rename Windows tag groups to test shards These groups now drive the macOS Intel split as well as Windows, so the "windows" naming no longer fits. Rename the module to Spec::Shards and the tags to shard_a..d. https://github.com/ruby/rubygems/commit/139d8b7232 Co-Authored-By: Claude Opus 4.8 (1M context) --- spec/bundler/spec_helper.rb | 10 +++++----- .../support/{windows_tag_group.rb => shards.rb} | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) rename spec/bundler/support/{windows_tag_group.rb => shards.rb} (96%) diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index 877deca66340c2..27ddc6a77125a2 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -38,7 +38,7 @@ require_relative "support/matchers" require_relative "support/permissions" require_relative "support/platforms" -require_relative "support/windows_tag_group" +require_relative "support/shards" begin raise LoadError if File.exist?(File.expand_path("../../lib/bundler/bundler.gemspec", __dir__)) @@ -88,7 +88,7 @@ def self.ruby=(ruby) config.include Spec::Path config.include Spec::Platforms config.include Spec::Permissions - config.include Spec::WindowsTagGroup + config.include Spec::Shards # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" @@ -175,7 +175,7 @@ def self.ruby=(ruby) reset! end - Spec::WindowsTagGroup::EXAMPLE_MAPPINGS.each do |tag, file_paths| + Spec::Shards::EXAMPLE_MAPPINGS.each do |tag, file_paths| file_pattern = Regexp.union(file_paths.map {|path| Regexp.new(Regexp.escape(path) + "$") }) config.define_derived_metadata(file_path: file_pattern) do |metadata| @@ -185,8 +185,8 @@ def self.ruby=(ruby) config.before(:context) do |example| metadata = example.class.metadata - if metadata[:type] != :aruba && !metadata[:realworld] && metadata.keys.none? {|k| Spec::WindowsTagGroup::EXAMPLE_MAPPINGS.keys.include?(k) } - warn "#{metadata[:file_path]} is not assigned to any Windows runner group. see spec/support/windows_tag_group.rb for details." + if metadata[:type] != :aruba && !metadata[:realworld] && metadata.keys.none? {|k| Spec::Shards::EXAMPLE_MAPPINGS.keys.include?(k) } + warn "#{metadata[:file_path]} is not assigned to any shard. see spec/support/shards.rb for details." end end unless Spec::Path.ruby_core? end diff --git a/spec/bundler/support/windows_tag_group.rb b/spec/bundler/support/shards.rb similarity index 96% rename from spec/bundler/support/windows_tag_group.rb rename to spec/bundler/support/shards.rb index fb9c0811493f2c..580997eb72df69 100644 --- a/spec/bundler/support/windows_tag_group.rb +++ b/spec/bundler/support/shards.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -# This group classifies test files into 4 groups by running `bin/rspec --profile 10000` +# This classifies test files into 4 shards by running `bin/rspec --profile 10000` # to ensure balanced execution times. When adding new test files, it is recommended to -# re-aggregate and adjust the groups to keep them balanced. -# For now, please add new files to group 'windows_d'. +# re-aggregate and adjust the shards to keep them balanced. +# For now, please add new files to shard 'shard_d'. module Spec - module WindowsTagGroup + module Shards EXAMPLE_MAPPINGS = { - windows_a: [ + shard_a: [ "spec/runtime/setup_spec.rb", "spec/commands/install_spec.rb", "spec/commands/add_spec.rb", @@ -53,7 +53,7 @@ module WindowsTagGroup "spec/bundler/plugin/source_list_spec.rb", "spec/bundler/source/path_spec.rb", ], - windows_b: [ + shard_b: [ "spec/install/gemfile/git_spec.rb", "spec/install/gems/standalone_spec.rb", "spec/commands/lock_spec.rb", @@ -97,7 +97,7 @@ module WindowsTagGroup "spec/bundler/index_spec.rb", "spec/other/cli_man_pages_spec.rb", ], - windows_c: [ + shard_c: [ "spec/commands/newgem_spec.rb", "spec/commands/exec_spec.rb", "spec/commands/clean_spec.rb", @@ -142,7 +142,7 @@ module WindowsTagGroup "spec/bundler/cli_common_spec.rb", "spec/bundler/ci_detector_spec.rb", ], - windows_d: [ + shard_d: [ "spec/commands/outdated_spec.rb", "spec/commands/update_spec.rb", "spec/lock/lockfile_spec.rb", From 6edac27906a33f6de13878c9908bb578070a2c6a Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 29 May 2026 09:05:01 +0200 Subject: [PATCH 02/10] [ruby/json] Prevent buffer over-read when generating EOF error When reaching EOS right after an escape the cursor is one byte too far. Reported-By: Yuhang Wu https://github.com/ruby/json/commit/3f44b26cf3 --- ext/json/parser/parser.c | 14 ++++++++++++++ test/json/json_parser_test.rb | 12 ++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 363f109d3a04f6..503bed1fd477d3 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -385,6 +385,13 @@ static inline char peek(JSON_ParserState *state) static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out) { + JSON_ASSERT(state->cursor <= state->end); + + // Redundant but helpful for hardening + if (RB_UNLIKELY(state->cursor > state->end)) { + state->cursor = state->end; + } + const char *cursor = state->cursor; long column = 0; long line = 1; @@ -1022,6 +1029,13 @@ ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state) } state->cursor++; } + + // If the string ended with an unterminated escape sequence, we might + // have gone past the end. + if (RB_UNLIKELY(state->cursor > state->end)) { + state->cursor = state->end; + } + return false; } diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 1969e79b31efaf..292ca1a6701147 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -545,22 +545,26 @@ def test_nesting end def test_backslash + assert_raise(JSON::ParserError) do + JSON.parse('"\\') + end + data = [ '\\.(?i:gif|jpe?g|png)$' ] json = '["\\\\.(?i:gif|jpe?g|png)$"]' assert_equal data, parse(json) - # + data = [ '\\"' ] json = '["\\\\\""]' assert_equal data, parse(json) - # + json = '["/"]' data = [ '/' ] assert_equal data, parse(json) - # + json = '["\""]' data = ['"'] assert_equal data, parse(json) - # + json = '["\\/"]' data = ["/"] assert_equal data, parse(json) From 0cc81cf0b80eb9f8167939115553a42978c06f85 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 29 May 2026 15:59:46 +0900 Subject: [PATCH 03/10] Make rb_hash_st_table_set static It is not used outside of hash.c so we can make it static. --- hash.c | 8 ++++---- internal/hash.h | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/hash.c b/hash.c index bac1b4abb5bb9e..9ba8c3d4fe1e79 100644 --- a/hash.c +++ b/hash.c @@ -528,7 +528,7 @@ hash_st_table_init(VALUE hash, const struct st_hash_type *type, st_index_t size) RHASH_SET_ST_FLAG(hash); } -void +static void rb_hash_st_table_set(VALUE hash, st_table *st) { HASH_ASSERT(st != NULL); @@ -730,7 +730,7 @@ ar_force_convert_table(VALUE hash, const char *file, int line) st_init_existing_table_with_size(new_tab, &objhash, size); ar_each_key(ar, bound, ar_each_key_insert, NULL, new_tab, hashes); hash_ar_free_and_clear_table(hash); - RHASH_ST_TABLE_SET(hash, new_tab); + rb_hash_st_table_set(hash, new_tab); return RHASH_ST_TABLE(hash); } } @@ -1986,7 +1986,7 @@ rb_hash_rehash(VALUE hash) rb_hash_foreach(hash, rb_hash_rehash_i, (VALUE)tmp); hash_st_free(hash); - RHASH_ST_TABLE_SET(hash, tbl); + rb_hash_st_table_set(hash, tbl); RHASH_ST_CLEAR(tmp); } hash_verify(hash); @@ -4669,7 +4669,7 @@ rb_hash_compare_by_id(VALUE hash) // We know for sure `identtable` is an st table, // so we can skip `ar_force_convert_table` here. - RHASH_ST_TABLE_SET(hash, identtable); + rb_hash_st_table_set(hash, identtable); RHASH_ST_CLEAR(tmp); } diff --git a/internal/hash.h b/internal/hash.h index baf5af9abd09e3..0386a5009c08ea 100644 --- a/internal/hash.h +++ b/internal/hash.h @@ -70,7 +70,6 @@ struct RHash { #endif /* hash.c */ -void rb_hash_st_table_set(VALUE hash, st_table *st); VALUE rb_hash_default_value(VALUE hash, VALUE key); VALUE rb_hash_set_default(VALUE hash, VALUE ifnone); VALUE rb_hash_set_default_proc(VALUE hash, VALUE proc); @@ -197,7 +196,6 @@ RHASH_AR_TABLE_SIZE_RAW(VALUE h) ((unsigned int)((RBASIC(h)->flags >> RHASH_AR_TABLE_BOUND_SHIFT) & \ (RHASH_AR_TABLE_BOUND_MASK >> RHASH_AR_TABLE_BOUND_SHIFT))) -#define RHASH_ST_TABLE_SET(h, s) rb_hash_st_table_set(h, s) #define RHASH_TYPE(hash) (RHASH_AR_TABLE_P(hash) ? &objhash : RHASH_ST_TABLE(hash)->type) static inline unsigned int From be8828d252c97a4a3fc87fc73354aa11e216a1d4 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 29 May 2026 08:06:43 +0000 Subject: [PATCH 04/10] [DOC] Update bundled gems list at 0cc81cf0b80eb9f8167939115553a4 --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index bc7e59914fa05f..038f1a63c6a67b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -83,7 +83,7 @@ releases. * ipaddr 1.2.9 * 1.2.8 to [v1.2.9][ipaddr-v1.2.9] * json 2.19.7 - * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0], [v2.19.1][json-v2.19.1], [v2.19.2][json-v2.19.2], [v2.19.3][json-v2.19.3], [v2.19.4][json-v2.19.4], [v2.19.5][json-v2.19.5], [v2.19.6][json-v2.19.6] + * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0], [v2.19.1][json-v2.19.1], [v2.19.2][json-v2.19.2], [v2.19.3][json-v2.19.3], [v2.19.4][json-v2.19.4], [v2.19.5][json-v2.19.5], [v2.19.6][json-v2.19.6], [v2.19.7][json-v2.19.7] * openssl 4.0.2 * 4.0.0 to [v4.0.1][openssl-v4.0.1], [v4.0.2][openssl-v4.0.2] * prism 1.9.0 @@ -223,6 +223,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [json-v2.19.4]: https://github.com/ruby/json/releases/tag/v2.19.4 [json-v2.19.5]: https://github.com/ruby/json/releases/tag/v2.19.5 [json-v2.19.6]: https://github.com/ruby/json/releases/tag/v2.19.6 +[json-v2.19.7]: https://github.com/ruby/json/releases/tag/v2.19.7 [openssl-v4.0.1]: https://github.com/ruby/openssl/releases/tag/v4.0.1 [openssl-v4.0.2]: https://github.com/ruby/openssl/releases/tag/v4.0.2 [prism-v1.8.0]: https://github.com/ruby/prism/releases/tag/v1.8.0 From 31c37abd069569baabe109da176488e7f71fdd7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Tue, 19 May 2026 16:24:39 +0200 Subject: [PATCH 05/10] [ruby/openssl] Make OpenSSL::Digest classes Ractor-safe Use class_eval with a string so the initialize instance method, and the digest and hexdigest class methods are not defined via define_method with a Proc (which is not Ractor-safe). https://github.com/ruby/openssl/commit/502bc6c378 --- ext/openssl/lib/openssl/digest.rb | 24 ++++++++++++++---------- test/openssl/test_digest.rb | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/ext/openssl/lib/openssl/digest.rb b/ext/openssl/lib/openssl/digest.rb index 46ddfd6021b7ea..4e6dea8d03824c 100644 --- a/ext/openssl/lib/openssl/digest.rb +++ b/ext/openssl/lib/openssl/digest.rb @@ -27,17 +27,21 @@ def self.digest(name, data) end %w(MD4 MD5 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512).each do |name| - klass = Class.new(self) { - define_method(:initialize, ->(data = nil) {super(name, data)}) - } - - singleton = (class << klass; self; end) - - singleton.class_eval{ - define_method(:digest) {|data| new.digest(data)} - define_method(:hexdigest) {|data| new.hexdigest(data)} - } + klass = Class.new(self) + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def initialize(data = nil) + super("#{name}", data) + end + RUBY + klass.singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def digest(data) + new.digest(data) + end + def hexdigest(data) + new.hexdigest(data) + end + RUBY const_set(name.tr('-', '_'), klass) end diff --git a/test/openssl/test_digest.rb b/test/openssl/test_digest.rb index 91ed2474142c5e..bc1f680df57120 100644 --- a/test/openssl/test_digest.rb +++ b/test/openssl/test_digest.rb @@ -155,6 +155,22 @@ def test_digests assert_include digests, "sha256" assert_include digests, "sha512" end + + if respond_to?(:ractor) && defined?(Ractor.shareable_proc) + ractor + + def test_ractor + assert_nothing_raised do + Ractor.new { + [ + OpenSSL::Digest::SHA256.new(""), + OpenSSL::Digest::SHA256.hexdigest(""), + OpenSSL::Digest::SHA256.digest(""), + ] + }.value + end + end + end end end From 3c121ae9455d5108ae9831a882c001492597a180 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Tue, 19 May 2026 19:35:17 +0100 Subject: [PATCH 06/10] [ruby/openssl] Fix test_kdf.rb in FIPS. * PBKDF2 salt >= 16 bytes (128 bits) and iterations >= 1000 are required in FIPS. SP 800-132: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf * 5.1 The Salt (S) https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L235-L240 * 5.2 The Iteration Count (C) https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L247-L252 * scrypt (RFC 7914) is not available in FIPS. EVP_KDF_fetch(ctx, OSSL_KDF_NAME_SCRYPT, propq) returns NULL in FIPS. https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/crypto/evp/pbe_scrypt.c#L67-L71 * Keep only one RFC 6070 test (test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25). Remove other RFC 6070 tests (test_*_rfc6070_*) don't work in FIPS. Keeping one RFC 6070 test is good enough. * Remove test_hkdf_rfc5869_test_case_4 in favor of the new test_hkdf_rfc5869_test_case_5. * We want to avoid conditional tests that don't work in FIPS as much as possible. https://github.com/ruby/openssl/commit/450721cc5d --- test/openssl/test_kdf.rb | 135 +++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 77 deletions(-) diff --git a/test/openssl/test_kdf.rb b/test/openssl/test_kdf.rb index 6a12a25aa8305a..708d1883afee5d 100644 --- a/test/openssl/test_kdf.rb +++ b/test/openssl/test_kdf.rb @@ -5,64 +5,31 @@ class OpenSSL::TestKDF < OpenSSL::TestCase def test_pkcs5_pbkdf2_hmac_compatibility - expected = OpenSSL::KDF.pbkdf2_hmac("password", salt: "salt", iterations: 1, length: 20, hash: "sha1") - assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("password", "salt", 1, 20, "sha1")) - assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac_sha1("password", "salt", 1, 20)) + # PBKDF2 salt >= 16 bytes (128 bits) and iterations >= 1000 are required in + # FIPS. + # SP 800-132. + # https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf + # * 5.1 The Salt (S) + # * 5.2 The Iteration Count (C) + # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L235-L240 + # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L247-L252 + # Use the same parameters with test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25. + expected = OpenSSL::KDF.pbkdf2_hmac("passwordPASSWORDpassword", + salt: "saltSALTsaltSALTsaltSALTsaltSALTsalt", + iterations: 4096, + length: 25, + hash: "sha1") + assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("passwordPASSWORDpassword", + "saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, + 25, + "sha1")) + assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac_sha1("passwordPASSWORDpassword", + "saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, + 25)) end - def test_pbkdf2_hmac_sha1_rfc6070_c_1_len_20 - p ="password" - s = "salt" - c = 1 - dk_len = 20 - raw = %w{ 0c 60 c8 0f 96 1f 0e 71 - f3 a9 b5 24 af 60 12 06 - 2f e0 37 a6 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") - assert_equal(expected, value) - end - - def test_pbkdf2_hmac_sha1_rfc6070_c_2_len_20 - p ="password" - s = "salt" - c = 2 - dk_len = 20 - raw = %w{ ea 6c 01 4d c7 2d 6f 8c - cd 1e d9 2a ce 1d 41 f0 - d8 de 89 57 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") - assert_equal(expected, value) - end - - def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_20 - p ="password" - s = "salt" - c = 4096 - dk_len = 20 - raw = %w{ 4b 00 79 01 b7 65 48 9a - be ad 49 d9 26 f7 21 d0 - 65 a4 29 c1 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") - assert_equal(expected, value) - end - -# takes too long! -# def test_pbkdf2_hmac_sha1_rfc6070_c_16777216_len_20 -# p ="password" -# s = "salt" -# c = 16777216 -# dk_len = 20 -# raw = %w{ ee fe 3d 61 cd 4d a4 e4 -# e9 94 5b 3d 6b a2 15 8c -# 26 34 e9 84 } -# expected = [raw.join('')].pack('H*') -# value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") -# assert_equal(expected, value) -# end - def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25 p ="passwordPASSWORDpassword" s = "saltSALTsaltSALTsaltSALTsaltSALTsalt" @@ -78,18 +45,6 @@ def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25 assert_equal(expected, value) end - def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_16 - p ="pass\0word" - s = "sa\0lt" - c = 4096 - dk_len = 16 - raw = %w{ 56 fa 6a a7 55 48 09 9d - cc 37 d7 f0 34 25 e0 c3 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") - assert_equal(expected, value) - end - def test_pbkdf2_hmac_sha256_c_20000_len_32 #unfortunately no official test vectors available yet for SHA-2 p ="password" @@ -103,6 +58,11 @@ def test_pbkdf2_hmac_sha256_c_20000_len_32 def test_scrypt_rfc7914_first pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0 + # scrypt is not available in FIPS. + # EVP_KDF_fetch(ctx, OSSL_KDF_NAME_SCRYPT, propq) returns NULL in FIPS. + # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/crypto/evp/pbe_scrypt.c#L67-L71 + omit_on_fips + pass = "" salt = "" n = 16 @@ -118,6 +78,9 @@ def test_scrypt_rfc7914_first def test_scrypt_rfc7914_second pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0 + # scrypt is not available in FIPS. + omit_on_fips + pass = "password" salt = "NaCl" n = 1024 @@ -131,6 +94,7 @@ def test_scrypt_rfc7914_second assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen)) end + # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.1 def test_hkdf_rfc5869_test_case_1 hash = "sha256" ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") @@ -144,6 +108,7 @@ def test_hkdf_rfc5869_test_case_1 assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) end + # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.3 def test_hkdf_rfc5869_test_case_3 hash = "sha256" ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") @@ -157,16 +122,32 @@ def test_hkdf_rfc5869_test_case_3 assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) end - def test_hkdf_rfc5869_test_case_4 + # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.5 + def test_hkdf_rfc5869_test_case_5 hash = "sha1" - ikm = B("0b0b0b0b0b0b0b0b0b0b0b") - salt = B("000102030405060708090a0b0c") - info = B("f0f1f2f3f4f5f6f7f8f9") - l = 42 - - okm = B("085a01ea1b10f36933068b56efa5ad81" \ - "a4f14b822f5b091568a9cdd4f155fda2" \ - "c22e422478d305f3f896") + ikm = B("000102030405060708090a0b0c0d0e0f" \ + "101112131415161718191a1b1c1d1e1f" \ + "202122232425262728292a2b2c2d2e2f" \ + "303132333435363738393a3b3c3d3e3f" \ + "404142434445464748494a4b4c4d4e4f") + salt = B("606162636465666768696a6b6c6d6e6f" \ + "707172737475767778797a7b7c7d7e7f" \ + "808182838485868788898a8b8c8d8e8f" \ + "909192939495969798999a9b9c9d9e9f" \ + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf") + info = B("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" \ + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" \ + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" \ + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" \ + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") + l = 82 + + okm = B("0bd770a74d1160f7c9f12cd5912a06eb" \ + "ff6adcae899d92191fe4305673ba2ffe" \ + "8fa3f1a4e5ad79f3f334b3b202b2173c" \ + "486ea37ce3d397ed034c7f9dfeb15c5e" \ + "927336d0441f4c4300e2cff0d0900b52" \ + "d3b4") assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) end From 24df92edbacfd081a6e123c5f3b8e8beb2fd8117 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 29 May 2026 17:26:19 +0900 Subject: [PATCH 07/10] [ruby/openssl] pkcs7: avoid using strcmp() with Ruby strings We should not rely on the NUL terminator of Ruby strings. Use memcmp(). https://github.com/ruby/openssl/commit/0b35e1a810 --- ext/openssl/ossl_pkcs7.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 3cf5820c36ad5b..44e8cb305b9138 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -453,7 +453,7 @@ ossl_pkcs7_sym2typeid(VALUE sym) if(i == numberof(p7_type_tab)) ossl_raise(ePKCS7Error, "unknown type \"%"PRIsVALUE"\"", sym); if(strlen(p7_type_tab[i].name) != l) continue; - if(strcmp(p7_type_tab[i].name, s) == 0){ + if(memcmp(p7_type_tab[i].name, s, l) == 0){ ret = p7_type_tab[i].nid; break; } From c3ddba2fc7b706b149f40fa11d2f31124e634843 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 30 Apr 2026 01:09:46 +0900 Subject: [PATCH 08/10] [ruby/openssl] ts: do not use the "reuse" behavior of d2i_*{,_bio}() functions The man page discourages using this behavior because it may leave the object in an inconsistent state on error paths. This fixes a potential memory leak reported at . https://github.com/ruby/openssl/commit/01e5602581 --- ext/openssl/ossl_ts.c | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index 393e08acff0c1e..317f7786aa7516 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -168,7 +168,7 @@ ossl_ts_req_alloc(VALUE klass) static VALUE ossl_ts_req_initialize(int argc, VALUE *argv, VALUE self) { - TS_REQ *ts_req = DATA_PTR(self); + TS_REQ *req, *req_orig = DATA_PTR(self); BIO *in; VALUE arg; @@ -178,13 +178,14 @@ ossl_ts_req_initialize(int argc, VALUE *argv, VALUE self) arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); - ts_req = d2i_TS_REQ_bio(in, &ts_req); + req = d2i_TS_REQ_bio(in, NULL); BIO_free(in); - if (!ts_req) { - DATA_PTR(self) = NULL; - ossl_raise(eTimestampError, "Error when decoding the timestamp request"); + if (!req) { + ossl_raise(eTimestampError, + "Error when decoding the timestamp request"); } - DATA_PTR(self) = ts_req; + TS_REQ_free(req_orig); + RTYPEDDATA_DATA(self) = req; return self; } @@ -522,18 +523,19 @@ ossl_ts_resp_alloc(VALUE klass) static VALUE ossl_ts_resp_initialize(VALUE self, VALUE der) { - TS_RESP *ts_resp = DATA_PTR(self); + TS_RESP *resp, *resp_orig = DATA_PTR(self); BIO *in; der = ossl_to_der_if_possible(der); - in = ossl_obj2bio(&der); - ts_resp = d2i_TS_RESP_bio(in, &ts_resp); + in = ossl_obj2bio(&der); + resp = d2i_TS_RESP_bio(in, NULL); BIO_free(in); - if (!ts_resp) { - DATA_PTR(self) = NULL; - ossl_raise(eTimestampError, "Error when decoding the timestamp response"); + if (!resp) { + ossl_raise(eTimestampError, + "Error when decoding the timestamp response"); } - DATA_PTR(self) = ts_resp; + TS_RESP_free(resp_orig); + RTYPEDDATA_DATA(self) = resp; return self; } @@ -876,18 +878,19 @@ ossl_ts_token_info_alloc(VALUE klass) static VALUE ossl_ts_token_info_initialize(VALUE self, VALUE der) { - TS_TST_INFO *info = DATA_PTR(self); + TS_TST_INFO *info, *info_orig = DATA_PTR(self); BIO *in; der = ossl_to_der_if_possible(der); - in = ossl_obj2bio(&der); - info = d2i_TS_TST_INFO_bio(in, &info); + in = ossl_obj2bio(&der); + info = d2i_TS_TST_INFO_bio(in, NULL); BIO_free(in); if (!info) { - DATA_PTR(self) = NULL; - ossl_raise(eTimestampError, "Error when decoding the timestamp token info"); + ossl_raise(eTimestampError, + "Error when decoding the timestamp token info"); } - DATA_PTR(self) = info; + TS_TST_INFO_free(info_orig); + RTYPEDDATA_DATA(self) = info; return self; } From f995e2266abbedc7a6c1e7e0cf314e842bf49446 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 30 Apr 2026 01:31:41 +0900 Subject: [PATCH 09/10] [ruby/openssl] pkcs12: add missing error check for d2i_PKCS12_bio() Also, avoid using the "reuse" behavior of d2i_*{,_bio}() functions. https://github.com/ruby/openssl/commit/3f1a7a377c --- ext/openssl/ossl_pkcs12.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c index 32b82a881c361f..a47c81354c8437 100644 --- a/ext/openssl/ossl_pkcs12.c +++ b/ext/openssl/ossl_pkcs12.c @@ -191,6 +191,7 @@ ossl_x509_sk2ary_i(VALUE arg) static VALUE ossl_pkcs12_initialize(int argc, VALUE *argv, VALUE self) { + PKCS12 *p12, *p12_orig = DATA_PTR(self); BIO *in; VALUE arg, pass, pkey, cert, ca; char *passphrase; @@ -198,17 +199,19 @@ ossl_pkcs12_initialize(int argc, VALUE *argv, VALUE self) X509 *x509; STACK_OF(X509) *x509s = NULL; int st = 0; - PKCS12 *pkcs = DATA_PTR(self); if(rb_scan_args(argc, argv, "02", &arg, &pass) == 0) return self; passphrase = NIL_P(pass) ? NULL : StringValueCStr(pass); in = ossl_obj2bio(&arg); - d2i_PKCS12_bio(in, &pkcs); - DATA_PTR(self) = pkcs; + p12 = d2i_PKCS12_bio(in, NULL); BIO_free(in); + if (!p12) + ossl_raise(ePKCS12Error, "d2i_PKCS12_bio"); + PKCS12_free(p12_orig); + RTYPEDDATA_DATA(self) = p12; pkey = cert = ca = Qnil; - if(!PKCS12_parse(pkcs, passphrase, &key, &x509, &x509s)) + if (!PKCS12_parse(p12, passphrase, &key, &x509, &x509s)) ossl_raise(ePKCS12Error, "PKCS12_parse"); if (key) { pkey = rb_protect(ossl_pkey_wrap_i, (VALUE)key, &st); From 7d1be0888a3b83be8508f437d0628601f21c1275 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 29 May 2026 19:40:45 +0900 Subject: [PATCH 10/10] test/ruby/test_io.rb: do not set RLIMIT_NPROC On my computer, setting RLIMIT_NPROC to the low value of 2048 prevents the forked process from creating any new native threads and it causes make test-all to hang forever. Remove it, as it does not seem to serve any purpose here. --- test/ruby/test_io.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 1adf47ac51a2d6..a78527d40eff80 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -1393,10 +1393,6 @@ def ruby(*args) args = ['-e', '$>.write($<.read)'] if args.empty? ruby = EnvUtil.rubybin opts = {} - if defined?(Process::RLIMIT_NPROC) - lim = Process.getrlimit(Process::RLIMIT_NPROC)[1] - opts[:rlimit_nproc] = [lim, 2048].min - end f = IO.popen([ruby] + args, 'r+', opts) pid = f.pid yield(f)