Skip to content

Commit e89b5bc

Browse files
authored
Merge pull request #410 from Watson1978/fix/buffer-append-string-uaf
Fix use-after-free when packing a string into an IO
2 parents 314d65d + 8731b86 commit e89b5bc

2 files changed

Lines changed: 46 additions & 5 deletions

File tree

‎ext/msgpack/buffer.h‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,27 @@ void _msgpack_buffer_append_long_string(msgpack_buffer_t* b, VALUE string);
237237

238238
static inline size_t msgpack_buffer_append_string(msgpack_buffer_t* b, VALUE string)
239239
{
240-
size_t length;
241-
char *ptr;
242-
RSTRING_GETMEM(string, ptr, length);
240+
size_t length = RSTRING_LEN(string);
243241

244242
if(length > b->write_reference_threshold) {
245243
_msgpack_buffer_append_long_string(b, string);
246-
} else {
247-
msgpack_buffer_append(b, ptr, length);
244+
} else if(length > 0) {
245+
/* Making room can run io.write or trigger GC, either of which may free
246+
* or shrink the string, so reserve the space first and only then read
247+
* its pointer. */
248+
msgpack_buffer_ensure_writable(b, length);
249+
char *ptr;
250+
size_t current;
251+
RSTRING_GETMEM(string, ptr, current);
252+
if(current > length) {
253+
current = length;
254+
}
255+
memcpy(b->tail.last, ptr, current);
256+
if(current < length) {
257+
memset(b->tail.last + current, 0, length - current);
258+
}
259+
b->tail.last += length;
260+
RB_GC_GUARD(string);
248261
}
249262

250263
return length;

‎spec/packer_spec.rb‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,4 +616,32 @@ def to_msgpack_ext
616616
packer.write(top_hash)
617617
packer.full_pack
618618
end
619+
620+
it 'does not copy freed memory when a string is freed during io.write' do
621+
skip if IS_JRUBY
622+
623+
size = 100_000 # larger than the io buffer, smaller than the write reference threshold
624+
src = "A".b * size
625+
captured = "".b
626+
flipped = false
627+
io = Object.new
628+
io.define_singleton_method(:write) do |data|
629+
unless flipped
630+
flipped = true
631+
src.replace("".b) # free the buffer the packer still points at
632+
GC.start
633+
Array.new(20) { "Z".b * size } # reuse the freed region
634+
end
635+
captured << data.b
636+
data.bytesize
637+
end
638+
639+
packer = MessagePack::Packer.new(io)
640+
packer.write(src)
641+
packer.full_pack
642+
643+
body = MessagePack.unpack(captured)
644+
expect(body.bytesize).to eq(size)
645+
expect(body.b.count("Z".b)).to eq(0) # must not surface the reused heap region
646+
end
619647
end

0 commit comments

Comments
 (0)