# Ruby MRI (CRuby) — CWE-407 Disclosure Brief **Project:** Ruby MRI (CRuby) **Disclosure date:** 2026-03-27 **Severity:** HIGH **Speedup:** varies with N×M **Status:** PATCHED --- ## Finding Ruby MRI's bytecode compiler resolves named keyword argument bindings with an O(N×M) algorithm: for each of N call-site keyword arguments, it performs a linear scan over the M parameter names defined in the method signature. This appears in `compile.c` and executes at compile time for every call site that uses named kwargs. Methods with many keyword parameters called from many sites accumulate quadratic work during script compilation. ## The Defect(s) | ID | Location | Pattern | Complexity | |----|----------|---------|------------| | ruby-0001 | `compile.c` | kwarg named parameter binding scans M param names for each of N call-site kwargs | O(N×M) per call site | ## Complexity Proof Let N = number of keyword arguments at a call site, M = number of keyword parameters in the method definition. For each of the N kwargs at the call site, the compiler scans the method's param list linearly to find the matching index: ``` For arg_1: scan up to M params → up to M comparisons For arg_2: scan up to M params → up to M comparisons ... For arg_N: scan up to M params → up to M comparisons Total: N × M comparisons per call site ``` If the same method with M kwargs is called from K call sites, total work is K × N × M. For a method with M = 50 keyword params called from K = 200 sites each passing N = 40 kwargs, that is 400,000 string comparisons just for kwarg binding — versus 200 × 40 = 8,000 hash lookups with a pre-built `HashMap`. Pre-building a hash map of `{param_name => param_index}` once per method definition reduces each per-call-site lookup to O(1), making binding O(N) per call site and O(K×N) total. ## Impact Any Ruby application or gem with methods that define and use many keyword arguments is affected at compile time. DSL-heavy frameworks (Rails, RSpec, Sorbet-annotated code), configuration builders, and test suites with large parameterized helpers see this most acutely. The cost is paid every time the Ruby runtime compiles the source file — on every process start, every `require`, and in every `eval`. ## The Fix Pre-build a `HashMap` (or equivalent C hash structure) for each method's keyword parameter list once during method compilation. At each call site, look up each kwarg name in O(1) instead of scanning the param array. ## Patch ```diff - /* For each kwarg at call site, scan param list for matching name */ - for (int i = 0; i < argc; i++) { - VALUE kw_name = call_kwargs[i]; - for (int j = 0; j < param_count; j++) { - if (rb_str_equal(kw_name, param_names[j])) { - binding[i] = j; - break; - } - } - } + /* Build param name -> index map once per method, reuse at every call site */ + st_table *kw_index = st_init_strtable(); + for (int j = 0; j < param_count; j++) { + st_insert(kw_index, (st_data_t)RSTRING_PTR(param_names[j]), (st_data_t)j); + } + for (int i = 0; i < argc; i++) { + st_data_t idx; + if (st_lookup(kw_index, (st_data_t)RSTRING_PTR(call_kwargs[i]), &idx)) { + binding[i] = (int)idx; + } + } ``` ## What We Ask Please review, apply, and coordinate a 90-day disclosure window before public release. Reply to security@undefect.com. --- *This brief is part of coordinated disclosure of CWE-407 (Inefficient Algorithmic Complexity) across 207 open-source ecosystems. Full report: https://undefect.com*