CVE-2020-6016
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:
- Send segments with negative offsets
- The lower_bound() search will return an empry iterator
- The do-while loop will use the end() “element” as if it was a legal entry in the hash map
- 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:
- Heap-Based Buffer Underflow - Using the negative offset and the too-small allocated buffer
- 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