Project

General

Profile

Bug #22335

Updated by eapache_opslevel (Evan Huus) 11 days ago

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 >31 
 - running in a Ruby process that has refined or redefined `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_callee_setup_arg()` calls `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 in the ar_hint that small hashes use for a linear scan (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.

Back