A Memory Exhaustion Attack Against the Steem Blockchain

Steem limits the maximum size of the message to 2MB, not including the message header, but the test messages that triggered the memory allocation failures were much smaller.One site that triggered an out-of-memory condition was this function:template<typename Stream> inline void unpack( Stream& s, std::vector<char>& value ) { unsigned_int size; fc::raw::unpack( s, size ); FC_ASSERT( size.value < MAX_ARRAY_ALLOC_SIZE ); value.resize(size.value); if ( value.size() ) s.read( value.data(), value.size() ); }MAX_ARRAY_ALLOC_SIZE is set to 10MB, permitting an array larger than what would actually fit in the 2MB message limit..The code resizes first, then detects end-of-message later while filling the array, via an exception from the stream.Exploiting the Memory AllocationThe FC library provides a union-like type called variant..This member of the hello_message structure is a keyed collection of variants:fc::variant_object user_data;One of the types supported by variant is a vector of variants..A test showed that the library supported arbitrary nesting of vectors within a top-level variant_object:std::vector<fc::variant> va1( 0xaa );std::vector<fc::variant> va2( 0xbb );std::vector<fc::variant> va3( 0xcc );std::vector<fc::variant> va4( 0xdd );std::vector<fc::variant> va5( 0xee );fc::to_variant( va5, va4[0] );fc::to_variant( va4, va3[0] );fc::to_variant( va3, va2[0] );fc::to_variant( va2, va1[0] );fc::mutable_variant_object mut;mut.set( “aaaaaa”, va1 );hm.user_data = mut;message m( hm );On the wire, each nested array is represented by an object type (06 for array) and a length (packed to between one to four bytes), followed by the contents of the array.00000000 0a 75 73 65 72 5f 61 67 65 6e 74 03 00 00 00 0000000010 00 00 00 90 1f 7a 00 00 00 00 00 00 00 00 00 0000000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00*00000070 00 00 00 00 00 00 00 00 00 01 06 61 61 61 61 6100000080 61 06 aa 01 06 bb 01 06 cc 01 06 dd 01 06 ee 01 ^^ array_type ^^ ^^ packed lengthThus, after a prelude which contains the fixed fields of the hello_message, we can create a variant_object which contains a max-length array A1..The first field of this array is also a max-length array, A2..The first field of A2 in turn contains another max-length array A3, etc..for as deeply as we can nest in the maximum message size..The fc::raw::unpack functions will recurse down the first element of each array, preallocating the specified array size each time.The prelude shown here is 129 bytes, and each level of nesting requires four bytes to encode a maximum-sized array..This permits us to nest 131063 levels deep..Either the process will terminate due to stack overflow, or the total memory allocation will be 10MB per level, totaling 1.3 terabytes.AnalysisNormally, the absence of hand-written demarshalling code is a good sign!.Repetitive hand-written code frequently contains unnoticed errors and vulnerabilities.In this case, though, the generality of the introspection-based demarshalling code is itself a vulnerability..The programmers did not mean to permit arbitrarily-deeply-nested structures, but it is difficult to forbid it once some level of recursion has been implemented.. More details

Leave a Reply