CVE-2020-6016: Out-of-bounds Write (CWE-787)
value: Valve's Game Networking Sockets prior to version v1.2.0 improperly handles unreliable segments with negative offsets in function SNP_ReceiveUnreliableSegment(), leading to a Heap-Based Buffer Underflow and a free() of memory not from the heap, resulting in a memory corruption and probably even a remote code execution.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 9.8
Published: 2020-11-18T15:15Z
Repository: https://github.com/ValveSoftware/GameNetworkingSockets
Language: C++
Files changed: src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp, src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.h
Commit date: 2020-09-03 11:01:54 -0700
Commit message: Drop unreliable segments with weird offset/size.

And be more deliberate about limits of unreliable message/segment sizes.

Diff: diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp
index 69ab1f1..673f62d 100644
--- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp
+++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp
@@ -232,11 +232,11 @@ int64 CSteamNetworkConnectionBase::SNP_SendMessage( CSteamNetworkingMessage *pSe
 		pSendMessage->Release();
 		return -k_EResultLimitExceeded; 
 	}
 
 	// Check if they try to send a really large message
-	if ( cbData > k_cbMaxUnreliableMsgSize && !( pSendMessage->m_nFlags & k_nSteamNetworkingSend_Reliable )  )
+	if ( cbData > k_cbMaxUnreliableMsgSizeSend && !( pSendMessage->m_nFlags & k_nSteamNetworkingSend_Reliable )  )
 	{
 		SpewWarningRateLimited( usecNow, "Trying to send a very large (%d bytes) unreliable message.  Sending as reliable instead.\n", cbData );
 		pSendMessage->m_nFlags |= k_nSteamNetworkingSend_Reliable;
 	}
 
@@ -575,15 +575,27 @@ bool CSteamNetworkConnectionBase::ProcessPlainTextDataChunk( int usecTimeSinceLa
 
 			//
 			// Decode size, locate segment data
 			//
 			READ_SEGMENT_DATA_SIZE( unreliable )
-			Assert( cbSegmentSize > 0 ); // !TEST! Bogus assert, zero byte messages are OK.  Remove after testing
 
-			// Receive the segment
-			bool bLastSegmentInMessage = ( nFrameType & 0x20 ) != 0;
-			SNP_ReceiveUnreliableSegment( nCurMsgNum, nOffset, pSegmentData, cbSegmentSize, bLastSegmentInMessage, usecNow );
+			// Check if offset+size indicates a message larger than what we support.  (Also,
+			// protect against malicious sender sending *extremely* large offset causing overflow.)
+			if ( (int64)nOffset + cbSegmentSize > k_cbMaxUnreliableMsgSizeRecv || cbSegmentSize > k_cbMaxUnreliableSegmentSizeRecv )
+			{
+
+				// Since this is unreliable data, we can just ignore the segment.
+				SpewWarningRateLimited( usecNow, "[%s] Ignoring unreliable segment with invalid offset %u size %d\n",
+					GetDescription(), nOffset, cbSegmentSize );
+			}
+			else
+			{
+
+				// Receive the segment
+				bool bLastSegmentInMessage = ( nFrameType & 0x20 ) != 0;
+				SNP_ReceiveUnreliableSegment( nCurMsgNum, nOffset, pSegmentData, cbSegmentSize, bLastSegmentInMessage, usecNow );
+			}
 		}
 		else if ( ( nFrameType & 0xe0 ) == 0x40 )
 		{
 
 			//
diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.h b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.h
index 4f7d9b3..9fb00a3 100644
--- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.h
+++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.h
@@ -88,11 +88,18 @@ constexpr int k_nMaxPacketGaps = 62; // Don't bother tracking more than N gaps.
 constexpr int k_nMaxBufferedUnreliableSegments = 20;
 
 // If app tries to send a message larger than N bytes unreliably,
 // complain about it, and automatically convert to reliable.
 // About 15 segments.
-constexpr int k_cbMaxUnreliableMsgSize = 15*1100;
+constexpr int k_cbMaxUnreliableMsgSizeSend = 15*1100;
+
+// Max possible size of an unreliable segment we could receive.
+constexpr int k_cbMaxUnreliableSegmentSizeRecv = k_cbSteamNetworkingSocketsMaxPlaintextPayloadRecv;
+
+// Largest possible total unreliable message we can receive, based on the constraints above
+constexpr int k_cbMaxUnreliableMsgSizeRecv = k_nMaxBufferedUnreliableSegments*k_cbMaxUnreliableSegmentSizeRecv;
+COMPILE_TIME_ASSERT( k_cbMaxUnreliableMsgSizeRecv > k_cbMaxUnreliableMsgSizeSend + 4096 ); // Postel's law; confirm how much slack we have here
 
 class CSteamNetworkConnectionBase;
 class CConnectionTransport;
 struct SteamNetworkingMessageQueue;
 
@@ -466,11 +473,11 @@ struct SSNPRecvUnreliableSegmentKey
 
 struct SSNPRecvUnreliableSegmentData
 {
 	int m_cbSegSize = -1;
 	bool m_bLast = false;
-	char m_buf[ k_cbSteamNetworkingSocketsMaxPlaintextPayloadRecv ];
+	char m_buf[ k_cbMaxUnreliableSegmentSizeRecv ];
 };
 
 struct SSNPPacketGap
 {
 	int64 m_nEnd; // just after the last packet received

Vulnerable code: 	if ( cbData > k_cbMaxUnreliableMsgSize && !( pSendMessage->m_nFlags & k_nSteamNetworkingSend_Reliable )  )
			Assert( cbSegmentSize > 0 ); // !TEST! Bogus assert, zero byte messages are OK.  Remove after testing
			// Receive the segment
			bool bLastSegmentInMessage = ( nFrameType & 0x20 ) != 0;
			SNP_ReceiveUnreliableSegment( nCurMsgNum, nOffset, pSegmentData, cbSegmentSize, bLastSegmentInMessage, usecNow );
constexpr int k_cbMaxUnreliableMsgSize = 15*1100;
	char m_buf[ k_cbSteamNetworkingSocketsMaxPlaintextPayloadRecv ];
Fixed code: 	if ( cbData > k_cbMaxUnreliableMsgSizeSend && !( pSendMessage->m_nFlags & k_nSteamNetworkingSend_Reliable )  )
			// Check if offset+size indicates a message larger than what we support.  (Also,
			// protect against malicious sender sending *extremely* large offset causing overflow.)
			if ( (int64)nOffset + cbSegmentSize > k_cbMaxUnreliableMsgSizeRecv || cbSegmentSize > k_cbMaxUnreliableSegmentSizeRecv )
			{

				// Since this is unreliable data, we can just ignore the segment.
				SpewWarningRateLimited( usecNow, "[%s] Ignoring unreliable segment with invalid offset %u size %d\n",
					GetDescription(), nOffset, cbSegmentSize );
			}
			else
			{

				// Receive the segment
				bool bLastSegmentInMessage = ( nFrameType & 0x20 ) != 0;
				SNP_ReceiveUnreliableSegment( nCurMsgNum, nOffset, pSegmentData, cbSegmentSize, bLastSegmentInMessage, usecNow );
			}
constexpr int k_cbMaxUnreliableMsgSizeSend = 15*1100;

// Max possible size of an unreliable segment we could receive.
constexpr int k_cbMaxUnreliableSegmentSizeRecv = k_cbSteamNetworkingSocketsMaxPlaintextPayloadRecv;

// Largest possible total unreliable message we can receive, based on the constraints above
constexpr int k_cbMaxUnreliableMsgSizeRecv = k_nMaxBufferedUnreliableSegments*k_cbMaxUnreliableSegmentSizeRecv;
COMPILE_TIME_ASSERT( k_cbMaxUnreliableMsgSizeRecv > k_cbMaxUnreliableMsgSizeSend + 4096 ); // Postel's law; confirm how much slack we have here
	char m_buf[ k_cbMaxUnreliableSegmentSizeRecv ];