Issue #21952 has been reported by katsyoshi (Katsuyoshi MATSUMOTO). ---------------------------------------- Bug #21952: Ruby::Box double free at process exit when `fiddle/import` is required in multiple boxes https://bugs.ruby-lang.org/issues/21952 * Author: katsyoshi (Katsuyoshi MATSUMOTO) * Status: Open * ruby -v: ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [x86_64-linux] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- I found what looks like a separate `Ruby::Box` bug from the existing `require` and `LoadError` issues such as `#21760`. This is not a `LoadError` case. I was able to reduce it to a reproducer where requiring `fiddle/import` from multiple boxes causes Ruby to abort at process exit with a double free. Environment: - ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [x86_64-linux] - Linux x86_64 - `RUBY_BOX=1` ## Reproducer Create `/tmp/fiddle_require.rb`: ```ruby require "rubygems" $:.unshift(*Gem::Specification.find_by_name("fiddle").full_require_paths) require "fiddle/import" ``` Then run: ```sh RUBY_BOX=1 ruby -e 'b1 = Ruby::Box.new; b1.require("/tmp/fiddle_require.rb"); b2 = Ruby::Box.new; b2.require("/tmp/fiddle_require.rb")' ``` ## Expected behavior Both `Ruby::Box#require` calls succeed, and Ruby exits normally. ## Actual behavior Ruby aborts at process exit with: ```text free(): double free detected in tcache 2 [BUG] Aborted ``` The C backtrace points into Ruby's `Ruby::Box` cleanup path, including: - `free_classext_for_box` - `cleanup_all_local_extensions` - `box_entry_free` - `rb_class_classext_free` - `cvar_table_free_i` - `ruby_sized_xfree` ## ASAN I also rebuilt Ruby with AddressSanitizer and reran the same reproducer. ASAN reports: - `AddressSanitizer: attempting to call malloc_usable_size() for pointer which is not owned` - the pointer was originally allocated by `rb_cvar_set` - it was then freed once via `cvar_table_free_i` - and later reached the same `cvar_table_free_i` cleanup path again through `free_classext_for_box` and `box_entry_free` This makes it look like a class-variable-related allocation created while loading `fiddle/import` is being freed twice during `Ruby::Box` cleanup. ## Notes - I first noticed this while testing `fiddle` together with shared libraries, but shared library loading is not required for the crash. - `dlload` is not necessary. - Reusing the same Ruby module name is not necessary. - As a control case, one box requiring `fiddle/import` and another box requiring a plain Ruby file exits normally. - The explicit `$:` adjustment above is only there to avoid the separate `Ruby::Box#require` issue where `require "fiddle/import"` may otherwise fail with `LoadError` under `RUBY_BOX=1`. So this seems to be a separate crash bug in `Ruby::Box` cleanup triggered by loading `fiddle/import` in multiple boxes. ---Files-------------------------------- asan-sample.txt (13.7 KB) -- https://bugs.ruby-lang.org/