From: "eapache_opslevel (Evan Huus) via ruby-core" Date: 2026-09-18T15:02:12+00:00 Subject: [ruby-core:126779] [Ruby Bug#22335] Stack corruption with >31 keyword args and refined Integer#== Issue #22335 has been reported by eapache_opslevel (Evan Huus). ---------------------------------------- Bug #22335: Stack corruption with >31 keyword args and refined Integer#== https://bugs.ruby-lang.org/issues/22335 * Author: eapache_opslevel (Evan Huus) * Status: Open * ruby -v: 4.0.7 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- This is a messy one that originally surfaced as a flaky test in our Rails app, and required a lot of AI-assisted debugging to track down. This bug appears in the latest 4.0.7, though all of the original investigation was done against 3.3.11. If you have: - a method that takes only keyword args - has >31 keyword args - has a call-time-evaluated default value (e.g. `{}`) at argument index >31 - running in a Ruby process that has refined `Integer#==` at any point Then you get inconsistent stack corruption depending on your process's hash salt. The mechanism is nasty: - In a method with only keyword args, `vm_call_iseq_setup_kwparm_kwarg()` call `args_setup_kw_parameters()` with `klocals = argv + kw_param->bits_start - kw_param->num`, i.e. **above `cfp->sp`**, without first extending `cfp->sp` over those locals - `args_setup_kw_parameters()` uses a full hash, keyed by integer, for arguments beyond index 31 - When `args_setup_kw_parameters()` sets a key in that hash, and that key collides with another (not uncommon for a tiny hash, but still salt-dependent), it triggers an integer comparison - The refinement on `Integer#==` prevents that comparison from taking the fast path and turns it into a real C frame - Which gets pushed onto the stack, when the stack pointer hasn't been extended - Thus overwriting the other args and producing garbage The net effect is that the called method sees garbage values for the overwritten arguments. I've attached a reproducing script, which is unfortunately complex, as it needs to do some work to intentionally hit the correct hash collision depending on the process's salt. A simpler static version doesn't reproduce reliably enough to be useful. Even this version will occasionally get a hash salt with no collisions and fail to reproduce, though that seems pretty rare on my machine. ---Files-------------------------------- repro.rb (1.51 KB) -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/