Rapidjson: Rapidjson crash when insufficient memory

Created on 28 May 2018  ·  4Comments  ·  Source: Tencent/rapidjson

We are running into a crash problem, when allocator.Malloc can't allocate any more memory, the following code doesn't check the allocator.Malloc return. So following std::memcpy access 0x0 and cause a segmentation fault.

1966│     //! Initialize this value as copy string with initial data, without calling destructor.
1967│     void SetStringRaw(StringRefType s, Allocator& allocator) {
1968│         Ch* str = 0;
1969│         if (ShortString::Usable(s.length)) {
1970│             data_.f.flags = kShortStringFlag;
1971│             data_.ss.SetLength(s.length);
1972│             str = data_.ss.str;
1973│         } else {
1974│             data_.f.flags = kCopyStringFlag;
1975│             data_.s.length = s.length;
1976│             str = static_cast<Ch *>(allocator.Malloc((s.length + 1) * sizeof(Ch)));
1977│             SetStringPointer(str);
1978│         }
1979├>        std::memcpy(str, s, s.length * sizeof(Ch));
1980│         str[s.length] = '\0';
1981│     }

The call stack related to this issue is:

(gdb) bt
#0  0x00007f2a763297fb in memcpy () from /lib64/libc.so.6
#1  0x0000000000637b71 in rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> >::SetStringRaw (this=0x7f2a71f9adf0, s=..., allocator=...) at /home/deps/dest/usr/local/inc
lude/rapidjson/document.h:1979
#2  0x000000000063db65 in rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> >::GenericValue (this=0x7f2a71f9adf0, s=0x7f2a72624000 "\333\333\333", <incomplete sequence
\333>, length=8835056, allocator=...) at /home/deps/dest/usr/local/include/rapidjson/document.h:682
#3  0x000000000063b832 in rapidjson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator>::String (this=0x7f2a71ea7070, str=0x7f2a72624000 "\333\333\333"
, <incomplete sequence \333>, length=8835056, copy=true) at /home/deps/dest/usr/local/include/rapidjson/document.h:2351
#4  0x000000000064404e in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseString<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rap
idjson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=..., isKey=false) at /home/deps/dest/usr/local/inclu
de/rapidjson/reader.h:831
#5  0x00000000006401f4 in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseValue<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rapi
djson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/r
eader.h:1397
#6  0x0000000000644423 in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseObject<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rap
idjson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/
reader.h:621
#7  0x000000000064020d in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseValue<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rapi
djson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/reader.h:1398
#8  0x0000000000644423 in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseObject<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rap
idjson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/
reader.h:621
#9  0x000000000064020d in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseValue<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rapi
djson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/r
eader.h:1398
#10 0x00000000006447b4 in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseArray<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rapi
djson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/r
eader.h:674
#11 0x0000000000640226 in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseValue<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rapi
djson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/r
eader.h:1399
#12 0x0000000000644423 in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseObject<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rap
idjson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/
reader.h:621
#13 0x000000000064020d in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::ParseValue<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rapi
djson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/reader.h:1398
#14 0x000000000063ea43 in rapidjson::GenericReader<rapidjson::UTF8<char>, rapidjson::UTF8<char>, rapidjson::CrtAllocator>::Parse<0u, rapidjson::EncodedInputStream<rapidjson::UTF8<char>, rapidjson::MemoryStream>, rapidjson
::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator> > (this=0x7f2a70402840, is=..., handler=...) at /home/deps/dest/usr/local/include/rapidjson/reader
.h:501
#15 0x000000000063cc41 in rapidjson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator>::ParseStream<0u, rapidjson::UTF8<char>, rapidjson::EncodedInput
Stream<rapidjson::UTF8<char>, rapidjson::MemoryStream> > (this=0x7f2a71ea7070, is=...) at /home/deps/dest/usr/local/include/rapidjson/document.h:2159
#16 0x0000000000639bd3 in rapidjson::GenericDocument<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>, rapidjson::CrtAllocator>::Parse<0u, rapidjson::UTF8<char> > (this=0x7f2a71ea7070, str=0x7f2a5ab07840 "...", length=68013790) at /home/deps/dest/usr/local/include/rapidjson/document.h:2248

Most helpful comment

If RapidJSON calls an allocator function which can return NULL, then it's RapidJSON's duty to check for the NULL value before writing to it.

All 4 comments

There are probably many other places where a failing allocation will not be checked. What should RapidJSON do other than asserting?

If you have meaningful actions to do on memory exhaustion in your application, you can replace the default allocator by a custom one and add the corresponding measures there.

@pah Yes, I think RapidJSON should throw an exception so caller can catch it. I think it's not a very big change, since you only need to change the default allocator's Malloc behavior.

If RapidJSON calls an allocator function which can return NULL, then it's RapidJSON's duty to check for the NULL value before writing to it.

Unfortunately the whole library seems to be written with the assumption that Allocator::Malloc will never return NULL _and never throw_.

E.g. GenericValues copy-ctor for array/object will leak if one of the placement-new calls fails because Allocator::Malloc throws.

GenericPointer::CopyFromRaw and GenericPointer::Parse set the tokenCount_ before trying to allocate the new tokens_ array, thereby leaving the object in a corrupted state. It's still possible to destroy the corrupted GenericPointer object without anything bad happening, because tokenCount_ isn't touched in the dtor. So it's _possible_ that this will not lead to a crash/UB if the library user just destroys all RapidJSON objects immediately (without trying to do anything else with them) after an exception was thrown. But I wouldn't bet on it.

Those are just some examples that I found without an extensive search. I'm sure there are more. So patching RapidJSON to throw from Allocator::Malloc or supplying a user-defined allocator that throws doesn't seem like a viable solution.

Was this page helpful?
0 / 5 - 0 ratings