From: "jamescook83 (James Cook) via ruby-core" Date: 2026-09-24T23:57:57+00:00 Subject: [ruby-core:126851] [Ruby Bug#22382] IO#write of a shareable String from multiple Ractors causes use-after-free Issue #22382 has been reported by jamescook83 (James Cook). ---------------------------------------- Bug #22382: IO#write of a shareable String from multiple Ractors causes use-after-free https://bugs.ruby-lang.org/issues/22382 * Author: jamescook83 (James Cook) * Status: Open * ruby -v: ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin25] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- When several Ractors `IO#write` the same shareable, heap-allocated (non-embedded) String at the same moment, Ruby frees the String's buffer while the String is still alive. A later GC sweep double-frees it and aborts. **Reproduction** ```ruby # frozen_string_literal: true require "tmpdir" dir = Dir.mktmpdir TRIES = 300 1.upto(TRIES) do |try| $stderr.puts "try #{try} of #{TRIES}" str = Ractor.make_shareable(Random.bytes(512 * 1024)) # Control: one prior write from a single Ractor prevents the crash. File.binwrite(File.join(dir, "first"), str) if ENV["WRITE_FIRST"] go = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 0.02 12.times.map do |n| Ractor.new(str, File.join(dir, "w#{n}"), go) do |s, path, at| Thread.pass until Process.clock_gettime(Process::CLOCK_MONOTONIC) >= at # start together File.binwrite(path, s) end end.each(&:join) GC.start end puts "finished #{TRIES} tries without crashing" ``` ``` try 9 of 300 :44: [BUG] Aborted at 0x000000018e37e5e8 ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin25] ... libruby.4.0.dylib(_rb_gc_impl_free+0x50) libruby.4.0.dylib(_rb_gc_obj_free+0x1e8) libruby.4.0.dylib(_gc_sweep_plane+0x150) ``` macOS reports the abort as `POINTER_BEING_FREED_WAS_NOT_ALLOCATED`. Crashes 5 runs out of 5. With `WRITE_FIRST=1` (each String written once, from one Ractor, before the race) it finishes without crashing. **Cause** The crash goes away when each String is written once from a single Ractor first (WRITE_FIRST=1), which points at rb_str_tmp_frozen_no_embed_acquire (io.c:2038): on a String's first write it hands the String's buffer to a new String and re-points the String at it (string.c:1573). The same function was changed for fstrings in #21671. A similar issue around fstrings was resolved in #21671. ---Files-------------------------------- ractor_shareable_write_repro.rb (704 Bytes) -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/