Project

General

Profile

Actions

Bug #22426

open

ZJIT: keyword arguments swapped when passed out of declared order to a method with a block (please backport d81a11d4e6 to 4.0)

Bug #22426: ZJIT: keyword arguments swapped when passed out of declared order to a method with a block (please backport d81a11d4e6 to 4.0)

Added by incubus (Alexander Logvinov) about 11 hours ago.

Status:
Open
Assignee:
-
Target version:
-
ruby -v:
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [x86_64-linux]
[ruby-core:127067]

Description

Hello!

On Ruby 4.0.7 with ZJIT enabled, keyword arguments can reach the wrong parameters after the method is compiled. It happens when the caller passes keywords in a different order than the callee declares them, and the callee body contains a block. The values are swapped silently, so the result is wrong data, not a crash.

We hit this in a staging environment through the jwt gem (3.3.0). JWT::Encode#segments calls @token.sign!(algorithm: @algorithm, key: @key), while JWT::Token#sign! is declared as def sign!(key:, algorithm:) and calls .tap do ... end in its body. After about 30 calls, JWT::JWA.resolve receives the OpenSSL::PKey::EC key instead of the 'ES256' string and raises ArgumentError: Custom algorithms are required to include JWT::JWA::SigningAlgorithm.

Reproduction (no gems):

# frozen_string_literal: true
module JWA
  Signer = Struct.new(:algorithm, :key)
  def self.create_signer(algorithm:, key:)
    raise ArgumentError, "algorithm=#{algorithm.inspect}" unless algorithm.is_a?(String)
    Signer.new(algorithm, key)
  end
end

class Token
  def sign!(key:, algorithm:)
    JWA.create_signer(algorithm: algorithm, key: key).tap do |signer|
      @signature = signer.key
    end
    nil
  end
end

100.times do |i|
  Token.new.sign!(algorithm: 'ES256', key: :the_key)
rescue ArgumentError => e
  puts "FAIL at #{i}: #{e.message}"; exit 1
end
puts 'OK'

Results:

$ ruby test.rb            # 4.0.7, interpreter
OK
$ ruby --yjit test.rb     # 4.0.7
OK
$ ruby --zjit test.rb     # 4.0.7
FAIL at 29: algorithm=:the_key
$ ./miniruby --zjit test.rb   # master 46033eff6c (2026-10-11)
OK

The bug needs both conditions. With the same keyword order on both sides, or without the block in sign!, the script prints OK on 4.0.7 with --zjit too.

This looks like the bug reported in https://github.com/Shopify/ruby/issues/922 and fixed on master by https://github.com/ruby/ruby/pull/15827 ("ZJIT: Spill stack from direct send with args in callee order", commit d81a11d4e61f67b6fb0aaa44aaa7ead4022148dd). That commit is not in v4.0.7, and I could not find it on the ruby_4_0 branch.

Could d81a11d4e6 be backported to Ruby 4.0? The jwt failure was seen on x86_64-linux; the minimal reproduction above was run on arm64-darwin (ruby 4.0.7 +PRISM [arm64-darwin27]). Thank you!

No data to display

Actions

Also available in: PDF Atom