diff --git a/src/serialize.h b/src/serialize.h index a945650d6f..fd6efb3a6e 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -29,6 +29,15 @@ static const unsigned int MAX_SIZE = 0x02000000; +/** + * Maximum element count accepted during deserialization of untrusted data. + * Set to 2 MiB (matching MAX_PROTOCOL_MESSAGE_LENGTH from net.h) since no + * single P2P message can carry more data than this on the wire. + * Disk serialization (SER_DISK) callers can bypass this via the original + * ReadCompactSize which still uses MAX_SIZE. + */ +static const unsigned int MAX_DESER_ELEMENTS = 2 * 1024 * 1024; + /** * Dummy data type to identify deserializing constructors. * @@ -164,7 +173,6 @@ inline float ser_uint32_to_float(uint32_t y) return tmp.x; } - ///////////////////////////////////////////////////////////////// // // Templates for serializing to anything that looks like a stream, @@ -227,11 +235,6 @@ template inline void Unserialize(Stream& s, double& a ) { a = template inline void Serialize(Stream& s, bool a) { char f=a; ser_writedata8(s, f); } template inline void Unserialize(Stream& s, bool& a) { char f=ser_readdata8(s); a=f; } - - - - - /** * Compact Size * size < 253 -- 1 byte @@ -305,6 +308,23 @@ uint64_t ReadCompactSize(Stream& is) return nSizeRet; } +/** + * ReadCompactSize with a caller-specified upper bound. + * Use this instead of bare ReadCompactSize() when deserializing containers + * from untrusted data (P2P network) to prevent memory exhaustion attacks. + * + * @param nMaxElements The maximum element count to accept. + * @throws std::ios_base::failure if the decoded size exceeds nMaxElements. + */ +template +uint64_t ReadCompactSizeWithLimit(Stream& is, uint64_t nMaxElements) +{ + uint64_t nSizeRet = ReadCompactSize(is); + if (nSizeRet > nMaxElements) + throw std::ios_base::failure("ReadCompactSize(): element count exceeds context limit"); + return nSizeRet; +} + /** * Variable-length integers: bytes are a MSB base-128 encoding of the number. * The high bit in each byte signifies whether another digit follows. To make @@ -366,8 +386,19 @@ template I ReadVarInt(Stream& is) { I n = 0; + // Maximum possible VarInt encoding length for type I: + // Each byte encodes 7 bits, so sizeof(I)*8 bits needs at most + // ceil(sizeof(I)*8/7) bytes. Add 1 for safety. + const unsigned int max_iters = (sizeof(I) * 8 + 6) / 7; + unsigned int count = 0; while(true) { + if (++count > max_iters) + throw std::ios_base::failure("ReadVarInt(): too many bytes"); unsigned char chData = ser_readdata8(is); + // Overflow check: if n already uses the top 7 bits, shifting + // left by 7 more would overflow type I. + if (n > (std::numeric_limits::max() >> 7)) + throw std::ios_base::failure("ReadVarInt(): overflow"); n = (n << 7) | (chData & 0x7F); if (chData & 0x80) n++; @@ -570,8 +601,6 @@ template void Unserialize(Stream& os, std::shared_p template void Serialize(Stream& os, const std::unique_ptr& p); template void Unserialize(Stream& os, std::unique_ptr& p); - - /** * If none of the specialized versions above matched, default to calling member function. */ @@ -587,10 +616,6 @@ inline void Unserialize(Stream& is, T& a) a.Unserialize(is); } - - - - /** * string */ @@ -605,14 +630,12 @@ void Serialize(Stream& os, const std::basic_string& str) template void Unserialize(Stream& is, std::basic_string& str) { - unsigned int nSize = ReadCompactSize(is); + unsigned int nSize = ReadCompactSizeWithLimit(is, MAX_DESER_ELEMENTS); str.resize(nSize); if (nSize != 0) is.read((char*)&str[0], nSize * sizeof(str[0])); } - - /** * prevector */ @@ -638,13 +661,12 @@ inline void Serialize(Stream& os, const prevector& v) Serialize_impl(os, v, T()); } - template void Unserialize_impl(Stream& is, prevector& v, const unsigned char&) { // Limit size per read so bogus size value won't cause out of memory v.clear(); - unsigned int nSize = ReadCompactSize(is); + unsigned int nSize = ReadCompactSizeWithLimit(is, MAX_DESER_ELEMENTS); unsigned int i = 0; while (i < nSize) { @@ -659,7 +681,7 @@ template void Unserialize_impl(Stream& is, prevector& v, const V&) { v.clear(); - unsigned int nSize = ReadCompactSize(is); + unsigned int nSize = ReadCompactSizeWithLimit(is, MAX_DESER_ELEMENTS); unsigned int i = 0; unsigned int nMid = 0; while (nMid < nSize) @@ -679,8 +701,6 @@ inline void Unserialize(Stream& is, prevector& v) Unserialize_impl(is, v, T()); } - - /** * vector */ @@ -706,13 +726,12 @@ inline void Serialize(Stream& os, const std::vector& v) Serialize_impl(os, v, T()); } - template void Unserialize_impl(Stream& is, std::vector& v, const unsigned char&) { // Limit size per read so bogus size value won't cause out of memory v.clear(); - unsigned int nSize = ReadCompactSize(is); + unsigned int nSize = ReadCompactSizeWithLimit(is, MAX_DESER_ELEMENTS); unsigned int i = 0; while (i < nSize) { @@ -727,7 +746,7 @@ template void Unserialize_impl(Stream& is, std::vector& v, const V&) { v.clear(); - unsigned int nSize = ReadCompactSize(is); + unsigned int nSize = ReadCompactSizeWithLimit(is, MAX_DESER_ELEMENTS); unsigned int i = 0; unsigned int nMid = 0; while (nMid < nSize) @@ -747,8 +766,6 @@ inline void Unserialize(Stream& is, std::vector& v) Unserialize_impl(is, v, T()); } - - /** * optional */ @@ -784,8 +801,6 @@ void Unserialize(Stream& is, boost::optional& item) } } - - /** * array */ @@ -805,7 +820,6 @@ void Unserialize(Stream& is, std::array& item) } } - /** * pair */ @@ -823,8 +837,6 @@ void Unserialize(Stream& is, std::pair& item) Unserialize(is, item.second); } - - /** * map */ @@ -840,7 +852,7 @@ template void Unserialize(Stream& is, std::map& m) { m.clear(); - unsigned int nSize = ReadCompactSize(is); + unsigned int nSize = ReadCompactSizeWithLimit(is, MAX_DESER_ELEMENTS); typename std::map::iterator mi = m.begin(); for (unsigned int i = 0; i < nSize; i++) { @@ -850,8 +862,6 @@ void Unserialize(Stream& is, std::map& m) } } - - /** * set */ @@ -867,7 +877,7 @@ template void Unserialize(Stream& is, std::set& m) { m.clear(); - unsigned int nSize = ReadCompactSize(is); + unsigned int nSize = ReadCompactSizeWithLimit(is, MAX_DESER_ELEMENTS); typename std::set::iterator it = m.begin(); for (unsigned int i = 0; i < nSize; i++) { @@ -877,8 +887,6 @@ void Unserialize(Stream& is, std::set& m) } } - - /** * list */ @@ -894,7 +902,7 @@ template void Unserialize(Stream& is, std::list& l) { l.clear(); - unsigned int nSize = ReadCompactSize(is); + unsigned int nSize = ReadCompactSizeWithLimit(is, MAX_DESER_ELEMENTS); typename std::list::iterator it = l.begin(); for (unsigned int i = 0; i < nSize; i++) { @@ -904,8 +912,6 @@ void Unserialize(Stream& is, std::list& l) } } - - /** * unique_ptr */ @@ -921,8 +927,6 @@ void Unserialize(Stream& is, std::unique_ptr& p) p.reset(new T(deserialize, is)); } - - /** * shared_ptr */ @@ -938,8 +942,6 @@ void Unserialize(Stream& is, std::shared_ptr& p) p = std::make_shared(deserialize, is); } - - /** * Support for ADD_SERIALIZE_METHODS and READWRITE macro */ @@ -964,14 +966,6 @@ inline void SerReadWrite(Stream& s, T& obj, CSerActionUnserialize ser_action) ::Unserialize(s, obj); } - - - - - - - - /* ::GetSerializeSize implementations * * Computing the serialized size of objects is done through a special stream