Project

General

Profile

Actions

Bug #21212

open

IO::Buffer can be freed while its slice is locked

Added by hanazuki (Kasumi Hanazuki) 25 days ago. Updated 10 days ago.

Status:
Open
Target version:
-
ruby -v:
ruby 3.5.0dev (2025-04-01T16:11:01Z master 30e5e7c005) +PRISM [x86_64-linux]
[ruby-core:121507]

Description

buffer = IO::Buffer.new(100)
slice = buffer.slice

buffer.locked do
  buffer.free rescue p $!  #=> IO::Buffer::LockedError (expected)
end

slice.locked do
  p slice.locked?  #=> true (expected)
  p buffer.locked?  #=> false (what should this be?)

  slice.free rescue p $!  #=> IO::Buffer::LockedError (expected)

  buffer.free  # Should we allow this?

  slice.set_value(:U8, 0, 42)  # raises IO::Buffer::InvalidatedError (surprising!)
end

Updated by hanazuki (Kasumi Hanazuki) 10 days ago

I think the problem is that each IO::Buffer slice manages the lock state independently, and so the root IO::Buffer cannot know whether the memory is locked by one of its slices.

If the memory is being accessed by a native function like rb_io_buffer_read when the buffer is freed, this may be a loophole to trigger use-after-free.

# Assume this file is on a very slow device such as NFS.
io = File.open('/mnt/slowfs/slow')

buffer = IO::Buffer.new(100)
slice = buffer.slice

t1 = Thread.new do
  puts "start reading"
  slice.read(io)  # This takes too long.
  # If the memory backing the slice is freed already, read can write into invalid address.
  puts "finished reading"
end

t2 = Thread.new do
  sleep 0.5  # This waits for the read to begin.
  buffer.free  # This may free the buffer before the read finishes.
  puts "freed buffer"
end

t1.join
t2.join

Updated by byroot (Jean Boussier) 10 days ago

  • Assignee set to ioquatix (Samuel Williams)
Actions

Also available in: Atom PDF

Like0
Like0Like0