Bug #22214
closedSEGV when struct ractor_port.r is referenced after Ractor::Port.allocate
Description
Ractor::Port has a public allocator, so Ractor::Port.allocate returns a port whose owner Ractor (struct ractor_port.r) is still NULL; only ractor_port_init() fills it in. Methods dereference it without a check.
<internal:ractor>:829: [BUG] Segmentation fault at 0x0000000000000060
ruby 4.1.0dev (2026-07-26T18:55:18Z master eb9d72aea1) +PRISM [arm64-darwin25]
-- Ruby level backtrace information ----------------------------------------
-e:1:in '<main>'
<internal:ractor>:829:in 'closed?'
-- C level backtrace information -------------------------------------------
ruby(rb_native_mutex_lock+0xc) [0x104f922fc] ../thread_pthread.c:127
ruby(ractor_port_closed_p+0x30) [0x104efc2f8] ../ractor.c:74
ruby(builtin_inline_class_829) ../ractor.rb:830
ruby(builtin_invoker0+0x8) [0x104fd76ac] ../vm_insnhelper.c:7392
#send and Ractor#monitor / #unmonitor fault at the same address, #inspect at 0x8 in rb_ractor_id().
#receive and #close happen to raise Ractor::Error, but only because their rp->r != current ractor check catches NULL by accident, so they report the port as belonging to another Ractor.
Ractor.select does not crash, because ractor_try_receive() keys only on rp->id_. That field is 0 for an uninitialized port, which is also the id of the current Ractor's default port, so the port takes messages from it:
Updated by himura467 (Akito Shitara) 7 days ago
Updated by luke-gru (Luke Gruber) 7 days ago
- Assignee set to luke-gru (Luke Gruber)
Updated by himura467 (Akito Shitara) 7 days ago
- Status changed from Open to Closed
Applied in changeset git|d3aee406de6693a06b87d95fdafa258be1ee1681.
Raise TypeError for uninitialized Ractor::Port
Ractor::Port has a public allocator, so Ractor::Port.allocate returns a port whose owner Ractor is still NULL; only ractor_port_init() fills it in. The methods dereferenced it unchecked:
Ractor::Port.allocate.closed? # [BUG] Segmentation fault at 0x60
Route the paths reachable from Ruby through ractor_port_ptr_check(), which raises TypeError as other uninitialized typed data objects do. The self of #initialize_copy keeps the unchecked accessor because it is legitimately uninitialized there; only orig is checked.
Ractor.select did not crash, but such a port has id 0, the same as the current Ractor's default port, so it took messages from it.
Port#closed? also loses Primitive.attr! :leaf. It can raise now, and it was not a leaf before either, since the foreign Ractor path takes RACTOR_LOCK().
[Bug #22214]