[ruby/psych] Clamp io_reader copy to libyaml's buffer size

io_reader trusted IO#read to honour its size argument and copied
RSTRING_LEN(string) bytes into libyaml's fixed-capacity buffer. An
IO-like object whose #read over-returns wrote past the buffer end, a
heap out-of-bounds write reachable from Psych.load/safe_load/parse.
Clamp the copied length to the requested size.

https://github.com/ruby/psych/commit/99ecd94560

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hiroshi SHIBATAandClaude Opus 4.8 authored and git committed 2026-06-02 02:10:54 +00:00
1 parent cabe750b80
commit 770ccaa6fb
2 files changed
+32 -2

No files matched your search

+9 -2
View File
@@ -33,8 +33,15 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read)
if(! NIL_P(string)) {
void * str = (void *)StringValuePtr(string);
*read = (size_t)RSTRING_LEN(string);
memcpy(buf, str, *read);
size_t len = (size_t)RSTRING_LEN(string);
/* IO#read(size) is documented to return at most `size` bytes, but a
* misbehaving IO-like object may return more. Clamp the copy to the
* buffer libyaml gave us to avoid writing past its end. */
if(len > size) len = size;
*read = len;
memcpy(buf, str, len);
}
return 1;
+23
View File
@@ -198,6 +198,29 @@ module Psych
assert_called :end_stream
end
def test_parse_io_returns_more_bytes_than_requested
# An IO-like source whose #read returns more bytes than the size it was
# asked for must not overflow libyaml's read buffer.
io = Object.new
def io.external_encoding; Encoding::UTF_8 end
def io.read len
return nil if @done
@done = true
"--- a\n" + ("#" * (len + (1 << 20)))
end
# CRuby clamps the over-read and parses; JRuby's parser rejects the
# over-reading IO with an IOError. Either way there is no overflow.
begin
@parser.parse io
rescue IOError
return
end
assert_called :start_stream
assert_called :scalar
assert_called :end_stream
end
def test_syntax_error
assert_raise(Psych::SyntaxError) do
@parser.parse("---\n\"foo\"\n\"bar\"\n")