Information

File: steamnetworkingsockets\clientlib\steamnetworkingsockets_snp.cpp
Function: CSteamNetworkConnectionBase::SNP_ReceiveUnreliableSegment()

The reassembly of unreliable segments into a single message is done using the following fields per segment:

SNP_ReceiveUnreliableSegment( int64 nMsgNum, int nOffset, const void *pSegmentData, int cbSegmentSize, bool bLastSegmentInMessage, ...)

However, when filtering for the segments that will be relevant for the current reassembly attempt, an offset of 0 is being used as the lower bound:

// Now check if that completed the message
key.m_nOffset = 0;
auto itMsgStart = m_receiverState.m_mapUnreliableSegments.lower_bound( key );
auto end = m_receiverState.m_mapUnreliableSegments.end();
Assert( itMsgStart != end );

Later on, the reassembly process will use the following do-while loop:

auto itMsgLast = itMsgStart;
int cbMessageSize = 0;
for (;;)
{
	// Is this the thing we expected?
	if ( itMsgLast->first.m_nMsgNum != nMsgNum || itMsgLast->first.m_nOffset > cbMessageSize )
		return; // We've got a gap.

	// Update.  This code looks more complicated than strictly necessary, but it works
	// if we have overlapping segments.
	// EI-DBG: We can cause this value to stay 0:
	// EI-DBG:           m_nOffset   = -0x280
	// EI-DBG: +
	// EI-DBG:           m_cbSegSize = +0x400
	// EI-DBG: -----------------------
	// EI-DBG:         cbMessageSize = max(0, 0x180) = 0x180.
	// EI-DBG: Leading to a buffer allocation that will be smaller than needed :)
	cbMessageSize = std::max( cbMessageSize, itMsgLast->first.m_nOffset + itMsgLast->second.m_cbSegSize );

	// Is that the end?
	if ( itMsgLast->second.m_bLast )
		break;

	// Still looking for the end
	++itMsgLast;
	if ( itMsgLast == end )
		return;
}

Combining these parts together, an attacker could do the following:

  1. Send segments with negative offsets
  2. The lower_bound() search will return an empry iterator
  3. The do-while loop will use the end() “element” as if it was a legal entry in the hash map
  4. Craft the memory so that end() will look like an entry with a negative offset

Once we passed the reassembly check, we continue on to the actual reassembly:

CSteamNetworkingMessage *pMsg = CSteamNetworkingMessage::New( this, cbMessageSize, nMsgNum, k_nSteamNetworkingSend_Unreliable, usecNow );
if ( !pMsg )
	return;

// OK, we have the complete message!  Gather the
// segments into a contiguous buffer
for (;;)
{
	Assert( itMsgStart->first.m_nMsgNum == nMsgNum );
	// EI-DBG: Variant #1 - Heap-Based Buffer Underflow - using the negative offset
	memcpy( (char *)pMsg->m_pData + itMsgStart->first.m_nOffset, itMsgStart->second.m_buf, itMsgStart->second.m_cbSegSize );

	// Done?
	if ( itMsgStart->second.m_bLast )
		break;

	// Remove entry from list, and move onto the next entry
	itMsgStart = m_receiverState.m_mapUnreliableSegments.erase( itMsgStart );
}

// Erase the last segment, and anything else we might have hanging around
// for this message (???)
do {
	// EI-DBG: Variant #2 - Going to erase the end "element" ==> free(m_receiverState)
	itMsgStart = m_receiverState.m_mapUnreliableSegments.erase( itMsgStart );
} while ( itMsgStart != end && itMsgStart->first.m_nMsgNum == nMsgNum );

The 2 exploit variants enable the following:

  1. Heap-Based Buffer Underflow - Using the negative offset and the too-small allocated buffer
  2. Invalid Free() - When erasing the end() “element” we free to the heap the main recv metadata structure - m_receiverState

Crash Trace - Variant #1:

=================================================================
==10656==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x629000004218 at pc 0x7f73b639f733 bp 0x7f73b10fb6a0 sp 0x7f73b10fae48
READ of size 1024 at 0x629000004218 thread T1
    #0 0x7f73b639f732  (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x79732)
    #1 0x7f73b5fd4983 in memcpy /usr/include/x86_64-linux-gnu/bits/string_fortified.h:34
    #2 0x7f73b5fd4983 in SteamNetworkingSocketsLib::CSteamNetworkConnectionBase::SNP_ReceiveUnreliableSegment(long long, int, void const*, int, bool, long long) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp:2477
    #3 0x7f73b5fd9cad in SteamNetworkingSocketsLib::CSteamNetworkConnectionBase::ProcessPlainTextDataChunk(int, SteamNetworkingSocketsLib::RecvPacketContext_t&) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp:636
    #4 0x7f73b5ff4a0e in SteamNetworkingSocketsLib::CConnectionTransportUDPBase::Received_Data(unsigned char const*, int, long long) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp:873
    #5 0x7f73b5ff7fb4 in SteamNetworkingSocketsLib::CConnectionTransportUDP::PacketReceived(void const*, int, netadr_t const&, SteamNetworkingSocketsLib::CConnectionTransportUDP*) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp:1464
    #6 0x7f73b5f97c4c in SteamNetworkingSocketsLib::CRecvPacketCallback::operator()(void const*, int, netadr_t const&) const ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.h:58
    #7 0x7f73b5f97c4c in PollRawUDPSockets ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp:1283
    #8 0x7f73b5f9be71 in SteamNetworkingSockets_InternalPoll ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp:1381
    #9 0x7f73b5f9c236 in SteamNetworkingThreadProc ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp:1495
    #10 0x7f73b5fa041a in void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) /usr/include/c++/7/bits/invoke.h:60
    #11 0x7f73b5fa041a in std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) /usr/include/c++/7/bits/invoke.h:95
    #12 0x7f73b5fa041a in decltype (__invoke((_S_declval<0ul>)())) std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/include/c++/7/thread:234
    #13 0x7f73b5fa041a in std::thread::_Invoker<std::tuple<void (*)()> >::operator()() /usr/include/c++/7/thread:243
    #14 0x7f73b5fa041a in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() /usr/include/c++/7/thread:186
    #15 0x7f73b5a20b9e in execute_native_thread_routine ../../../../../gcc-8.2.0/libstdc++-v3/src/c++11/thread.cc:80
    #16 0x7f73b5cef6da in start_thread (/lib/x86_64-linux-gnu/libpthread.so.0+0x76da)
    #17 0x7f73b547fa3e in __clone (/lib/x86_64-linux-gnu/libc.so.6+0x121a3e)

0x629000004218 is located 0 bytes to the right of 16408-byte region [0x629000000200,0x629000004218)
allocated by thread T1 here:
    #0 0x7f73b6406448 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xe0448)
    #1 0x7f73b5fefaad in SteamNetworkingSocketsLib::CSteamNetworkListenSocketDirectUDP::Received_ConnectRequest(CMsgSteamSockets_UDP_ConnectRequest const&, netadr_t const&, int, long long) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp:530
    #2 0x7f73b60d251f  (/home/eyalitki/Documents/Research/Steam/GameNetworkingSockets/build/src/libGameNetworkingSockets.so+0x1cb51f)

Thread T1 created by T0 here:
    #0 0x7f73b635dd2f in __interceptor_pthread_create (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x37d2f)
    #1 0x7f73b5a20e24 in __gthread_create /home/eyalitki/Documents/Research/Compilers/OptOut/build/x86_64-linux-gnu/libstdc++-v3/include/x86_64-linux-gnu/bits/gthr-default.h:662
    #2 0x7f73b5a20e24 in std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) ../../../../../gcc-8.2.0/libstdc++-v3/src/c++11/thread.cc:135
    #3 0x7f73b635172e  (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x2b72e)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x79732) 
Shadow bytes around the buggy address:
  0x0c527fff87f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c527fff8800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c527fff8810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c527fff8820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c527fff8830: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c527fff8840: 00 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c527fff8850: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c527fff8860: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c527fff8870: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c527fff8880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c527fff8890: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==10656==ABORTING

Crash Trace - Variant #2:

=================================================================
==11449==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0xf2f03e5c in thread T1
    #0 0xf7a01ea4 in operator delete(void*) (/usr/lib32/libasan.so.4+0xe7ea4)
    #1 0xf774c4ce in __gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> > >::deallocate(std::_Rb_tree_node<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >*, unsigned int) /usr/include/c++/7/ext/new_allocator.h:125
    #2 0xf774c4ce in std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> > > >::deallocate(std::allocator<std::_Rb_tree_node<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> > >&, std::_Rb_tree_node<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >*, unsigned int) /usr/include/c++/7/bits/alloc_traits.h:462
    #3 0xf774c4ce in std::_Rb_tree<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey, std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData>, std::_Select1st<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >, std::less<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey>, std::allocator<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> > >::_M_put_node(std::_Rb_tree_node<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >*) /usr/include/c++/7/bits/stl_tree.h:592
    #4 0xf774c4ce in std::_Rb_tree<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey, std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData>, std::_Select1st<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >, std::less<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey>, std::allocator<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> > >::_M_drop_node(std::_Rb_tree_node<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >*) /usr/include/c++/7/bits/stl_tree.h:659
    #5 0xf774c4ce in std::_Rb_tree<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey, std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData>, std::_Select1st<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >, std::less<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey>, std::allocator<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> > >::_M_erase_aux(std::_Rb_tree_const_iterator<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >) /usr/include/c++/7/bits/stl_tree.h:2477
    #6 0xf774c4ce in std::_Rb_tree<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey, std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData>, std::_Select1st<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >, std::less<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey>, std::allocator<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> > >::erase[abi:cxx11](std::_Rb_tree_iterator<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >) /usr/include/c++/7/bits/stl_tree.h:1125
    #7 0xf774c4ce in std::map<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData, std::less<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey>, std::allocator<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> > >::erase[abi:cxx11](std::_Rb_tree_iterator<std::pair<SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentKey const, SteamNetworkingSocketsLib::SSNPRecvUnreliableSegmentData> >) /usr/include/c++/7/bits/stl_map.h:1031
    #8 0xf774c4ce in SteamNetworkingSocketsLib::CSteamNetworkConnectionBase::SNP_ReceiveUnreliableSegment(long long, int, void const*, int, bool, long long) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp:2490
    #9 0xf7755304 in SteamNetworkingSocketsLib::CSteamNetworkConnectionBase::ProcessPlainTextDataChunk(int, SteamNetworkingSocketsLib::RecvPacketContext_t&) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp:636
    #10 0xf7772b82 in SteamNetworkingSocketsLib::CConnectionTransportUDPBase::Received_Data(unsigned char const*, int, long long) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp:873
    #11 0xf7776eae in SteamNetworkingSocketsLib::CConnectionTransportUDP::PacketReceived(void const*, int, netadr_t const&, SteamNetworkingSocketsLib::CConnectionTransportUDP*) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp:1464
    #12 0xf76fbb4b in SteamNetworkingSocketsLib::CRecvPacketCallback::operator()(void const*, int, netadr_t const&) const ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.h:58
    #13 0xf76fbb4b in PollRawUDPSockets ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp:1283
    #14 0xf7700781 in SteamNetworkingSockets_InternalPoll ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp:1381
    #15 0xf7700d2e in SteamNetworkingThreadProc ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp:1495
    #16 0xf7705d55 in void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) /usr/include/c++/7/bits/invoke.h:60
    #17 0xf7705d55 in std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) /usr/include/c++/7/bits/invoke.h:95
    #18 0xf7705d55 in decltype (__invoke((_S_declval<0u>)())) std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0u>(std::_Index_tuple<0u>) /usr/include/c++/7/thread:234
    #19 0xf7705d55 in std::thread::_Invoker<std::tuple<void (*)()> >::operator()() /usr/include/c++/7/thread:243
    #20 0xf7705d55 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() /usr/include/c++/7/thread:186
    #21 0xf75975cc  (/usr/lib/i386-linux-gnu/libstdc++.so.6+0xa35cc)
    #22 0xf7949610  (/usr/lib32/libasan.so.4+0x2f610)
    #23 0xf72d23bc in start_thread (/lib/i386-linux-gnu/libpthread.so.0+0x63bc)
    #24 0xf73f2fe5 in clone (/lib/i386-linux-gnu/libc.so.6+0xf8fe5)

0xf2f03e5c is located 15708 bytes inside of 15868-byte region [0xf2f00100,0xf2f03efc)
allocated by thread T1 here:
    #0 0xf7a010a4 in operator new(unsigned int) (/usr/lib32/libasan.so.4+0xe70a4)
    #1 0xf776ce48 in SteamNetworkingSocketsLib::CSteamNetworkListenSocketDirectUDP::Received_ConnectRequest(CMsgSteamSockets_UDP_ConnectRequest const&, netadr_t const&, int, long long) ../src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp:530

Thread T1 created by T0 here:
    #0 0xf79e80f6 in __interceptor_pthread_create (/usr/lib32/libasan.so.4+0xce0f6)
    #1 0xf75978f7 in std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) (/usr/lib/i386-linux-gnu/libstdc++.so.6+0xa38f7)
    #2 0xf786f6c7  (/home/eyalitki/Documents/Research/Steam/GameNetworkingSockets/build/src/libGameNetworkingSockets.so+0x1f56c7)

SUMMARY: AddressSanitizer: bad-free (/usr/lib32/libasan.so.4+0xe7ea4) in operator delete(void*)
==11449==ABORTING

Attachments:
CVE_2020_6016_PoC_32_bits.py
CVE_2020_6016_PoC_64_bits.py
steamnetworkingsockets_messages_certs_pb2.py
steamnetworkingsockets_messages_pb2.py
steamnetworkingsockets_messages_udp_pb2.py
steam_networking_sockets.py

References:
https://research.checkpoint.com/2020/game-on-finding-vulnerabilities-in-valves-steam-sockets
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-6016
https://github.com/ValveSoftware/GameNetworkingSockets/commit/e0c86dcb9139771db3db0cfdb1fb8bef0af19c43