Project

General

Profile

Actions

Bug #22382

open

IO#write of a shareable String from multiple Ractors causes use-after-free

Bug #22382: IO#write of a shareable String from multiple Ractors causes use-after-free

Added by jamescook83 (James Cook) about 10 hours ago. Updated about 5 hours ago.

Status:
Open
Assignee:
-
Target version:
-
ruby -v:
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin25]
[ruby-core:126851]

Description

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

# 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
<internal:gc>: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) ractor_shareable_write_repro.rb jamescook83 (James Cook), 09/24/2026 11:56 PM
Actions

Also available in: PDF Atom