diff options
| author | himura467 <akitoshitara@gmail.com> | 2026-05-17 19:31:18 +0900 |
|---|---|---|
| committer | Sutou Kouhei <kou@cozmixng.org> | 2026-05-18 10:31:29 +0900 |
| commit | 95626e3a9c59cd073221c08ed013ed0f2d655b6f (patch) | |
| tree | 3500ea32efaffd802e3879f09259e47399874314 | |
| parent | 3f51506cff134c2642ebb3fbf7a9b00f05b2472d (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.c | 16 | ||||
| -rw-r--r-- | test/ruby/test_io_buffer.rb | 6 |
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 |
