diff --git a/ext/psych/psych_parser.c b/ext/psych/psych_parser.c index d973496284f..41637713930 100644 --- a/ext/psych/psych_parser.c +++ b/ext/psych/psych_parser.c @@ -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; diff --git a/test/psych/test_parser.rb b/test/psych/test_parser.rb index c1e0abb89d0..b5db4e96151 100644 --- a/test/psych/test_parser.rb +++ b/test/psych/test_parser.rb @@ -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")