summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhimura467 <akitoshitara@gmail.com>2026-05-17 19:31:18 +0900
committerSutou Kouhei <kou@cozmixng.org>2026-05-18 10:31:29 +0900
commit95626e3a9c59cd073221c08ed013ed0f2d655b6f (patch)
tree3500ea32efaffd802e3879f09259e47399874314
parent3f51506cff134c2642ebb3fbf7a9b00f05b2472d (diff)
Fix UAF in IO::Buffer#| when self or mask is an invalidated slice
`io_buffer_or` accessed `buffer->base` and `mask_buffer->base` directly without validating that the buffers were still live. A slice whose parent had been freed retained its stale base pointer, so calling `|` on it caused a UAF. Use `io_buffer_get_bytes_for_reading` for both operands, which raises `IO::Buffer::InvalidatedError` before any memory access if either buffer has been invalidated.
-rw-r--r--io_buffer.c16
-rw-r--r--test/ruby/test_io_buffer.rb6
2 files changed, 16 insertions, 6 deletions
diff --git a/io_buffer.c b/io_buffer.c
index c64a9b7fe7..dca30f362f 100644
--- a/io_buffer.c
+++ b/io_buffer.c
@@ -3433,7 +3433,7 @@ io_buffer_and(VALUE self, VALUE mask)
}
static void
-memory_or(unsigned char * restrict output, unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size)
+memory_or(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size)
{
for (size_t offset = 0; offset < size; offset += 1) {
output[offset] = base[offset] | mask[offset % mask_size];
@@ -3461,13 +3461,21 @@ io_buffer_or(VALUE self, VALUE mask)
struct rb_io_buffer *mask_buffer = NULL;
TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);
- io_buffer_check_mask_size(mask_buffer->size);
+ const void *base;
+ size_t size;
+ io_buffer_get_bytes_for_reading(buffer, &base, &size);
- VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
+ const void *mask_base;
+ size_t mask_size;
+ io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
+
+ io_buffer_check_mask_size(mask_size);
+
+ VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer *output_buffer = NULL;
TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);
- memory_or(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size);
+ memory_or(output_buffer->base, base, size, mask_base, mask_size);
return output;
}
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
index 50eb1a08e3..92a81ddd97 100644
--- a/test/ruby/test_io_buffer.rb
+++ b/test/ruby/test_io_buffer.rb
@@ -702,22 +702,24 @@ class TestIOBuffer < Test::Unit::TestCase
assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not!
end
- def test_and_raises_on_freed_self
+ def test_operators_raise_on_freed_self
inner = IO::Buffer.new(IO::Buffer::PAGE_SIZE)
slice = inner.slice(0, 8)
inner.free
mask = IO::Buffer.for("ABCDEFGH")
assert_raise(IO::Buffer::InvalidatedError) { slice & mask }
+ assert_raise(IO::Buffer::InvalidatedError) { slice | mask }
end
- def test_and_raises_on_freed_mask
+ def test_operators_raise_on_freed_mask
inner = IO::Buffer.new(IO::Buffer::PAGE_SIZE)
mask_slice = inner.slice(0, 8)
inner.free
source = IO::Buffer.for("ABCDEFGH")
assert_raise(IO::Buffer::InvalidatedError) { source & mask_slice }
+ assert_raise(IO::Buffer::InvalidatedError) { source | mask_slice }
end
def test_bit_count