Bug #21952
openRuby::Box double free at process exit when `fiddle/import` is required in multiple boxes
Description
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:
require "rubygems"
$:.unshift(*Gem::Specification.find_by_name("fiddle").full_require_paths)
require "fiddle/import"
Then run:
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:
free(): double free detected in tcache 2
[BUG] Aborted
The C backtrace points into Ruby's Ruby::Box cleanup path, including:
free_classext_for_boxcleanup_all_local_extensionsbox_entry_freerb_class_classext_freecvar_table_free_iruby_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_icleanup path again throughfree_classext_for_boxandbox_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
fiddletogether with shared libraries, but shared library loading is not required for the crash. -
dlloadis not necessary. - Reusing the same Ruby module name is not necessary.
- As a control case, one box requiring
fiddle/importand another box requiring a plain Ruby file exits normally. - The explicit
$:adjustment above is only there to avoid the separateRuby::Box#requireissue whererequire "fiddle/import"may otherwise fail withLoadErrorunderRUBY_BOX=1.
So this seems to be a separate crash bug in Ruby::Box cleanup triggered by loading fiddle/import in multiple boxes.
Files
No data to display