[ruby-core:121507] [Ruby Bug#21212] IO::Buffer can be freed while its slice is locked

Issue #21212 has been reported by hanazuki (Kasumi Hanazuki). ---------------------------------------- Bug #21212: IO::Buffer can be freed while its slice is locked https://bugs.ruby-lang.org/issues/21212 * Author: hanazuki (Kasumi Hanazuki) * Status: Open * ruby -v: ruby 3.5.0dev (2025-04-01T16:11:01Z master 30e5e7c005) +PRISM [x86_64-linux] * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- ```ruby 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 ``` -- https://bugs.ruby-lang.org/

Issue #21212 has been updated by hanazuki (Kasumi Hanazuki). 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. ```ruby # 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 ``` ---------------------------------------- Bug #21212: IO::Buffer can be freed while its slice is locked https://bugs.ruby-lang.org/issues/21212#change-112725 * Author: hanazuki (Kasumi Hanazuki) * Status: Open * ruby -v: ruby 3.5.0dev (2025-04-01T16:11:01Z master 30e5e7c005) +PRISM [x86_64-linux] * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- ```ruby 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 ``` -- https://bugs.ruby-lang.org/

Issue #21212 has been updated by byroot (Jean Boussier). Assignee set to ioquatix (Samuel Williams) ---------------------------------------- Bug #21212: IO::Buffer can be freed while its slice is locked https://bugs.ruby-lang.org/issues/21212#change-112727 * Author: hanazuki (Kasumi Hanazuki) * Status: Open * Assignee: ioquatix (Samuel Williams) * ruby -v: ruby 3.5.0dev (2025-04-01T16:11:01Z master 30e5e7c005) +PRISM [x86_64-linux] * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- ```ruby 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 ``` -- https://bugs.ruby-lang.org/
participants (2)
-
byroot (Jean Boussier)
-
hanazuki (Kasumi Hanazuki)