3.5 KiB
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<name, index>.
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<name, index> (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
- /* 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