CVE-2023-42799 (published 2023-12-14T17:15Z)

Moonlight-common-c contains the core GameStream client code shared between Moonlight clients. Moonlight-common-c is vulnerable to buffer overflow starting in commit 50c0a51b10ecc5b3415ea78c21d96d679e2288f9 due to unmitigated usage of unsafe C functions and improper bounds checking. A malicious game streaming server could exploit a buffer overflow vulnerability to crash a moonlight client, or achieve remote code execution (RCE) on the client (with insufficient exploit mitigations or if mitigations can be bypassed). The bug was addressed in commit 02b7742f4d19631024bd766bd2bb76715780004e.

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-120
cwe_name: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
description: The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
extended_description: A buffer overflow condition exists when a product attempts to put more data in a buffer than it can hold, or when it attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the "classic" case in which the product copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.
         

Fix information:
repo_url: https://github.com/moonlight-stream/moonlight-common-c
commit_details: repo_url: https://github.com/moonlight-stream/moonlight-common-c
author: Keane O'Kelley
author_date: 2023-10-06 20:51:21-04:00
msg: Fix buffer overflow in parseUrlAddrFromRtspUrlString (CVE-2023-42799)
repository: repo_url: https://github.com/moonlight-stream/moonlight-common-c
repo_name: moonlight-stream/moonlight-common-c
description: Core implementation of Nvidia's GameStream protocol
repo_language: C
owner: moonlight-stream
file_changes: filename: RtspConnection.c
change_type: ModificationType.MODIFY
diff: @@ -657,7 +657,7 @@ static int parseOpusConfigurations(PRTSP_MESSAGE response) {
     return 0;
 }
 
-static bool parseUrlAddrFromRtspUrlString(const char* rtspUrlString, char* destination) {
+static bool parseUrlAddrFromRtspUrlString(const char* rtspUrlString, char* destination, size_t destinationLength) {
     char* rtspUrlScratchBuffer;
     char* portSeparator;
     char* v6EscapeEndChar;
@@ -701,7 +701,8 @@ static bool parseUrlAddrFromRtspUrlString(const char* rtspUrlString, char* desti
         *urlPathSeparator = 0;
     }
 
-    strcpy(destination, rtspUrlScratchBuffer + prefixLen);
+    PltSafeStrcpy(destination, destinationLength, rtspUrlScratchBuffer + prefixLen);
+    destination[destinationLength - 1] = '\0';
 
     free(rtspUrlScratchBuffer);
     return true;
@@ -774,7 +775,7 @@ int performRtspHandshake(PSERVER_INFORMATION serverInfo) {
             (AudioCallbacks.capabilities & CAPABILITY_SLOW_OPUS_DECODER) == 0 &&
             (StreamConfig.streamingRemotely != STREAM_CFG_REMOTE || CHANNEL_COUNT_FROM_AUDIO_CONFIGURATION(StreamConfig.audioConfiguration) <= 2)) {
         // If we have an RTSP URL string and it was successfully parsed, use that string
-        if (serverInfo->rtspSessionUrl != NULL && parseUrlAddrFromRtspUrlString(serverInfo->rtspSessionUrl, urlAddr)) {
+        if (serverInfo->rtspSessionUrl != NULL && parseUrlAddrFromRtspUrlString(serverInfo->rtspSessionUrl, urlAddr, sizeof(urlAddr))) {
             strcpy(rtspTargetUrl, serverInfo->rtspSessionUrl);
         }
         else {
@@ -784,12 +785,12 @@ int performRtspHandshake(PSERVER_INFORMATION serverInfo) {
             // audio since it only does that for local streaming normally. We can avoid this limitation,
             // but only if the caller gave us the RTSP session URL that it received from the host during launch.
             addrToUrlSafeString(&RemoteAddr, urlAddr, sizeof(urlAddr));
-            sprintf(rtspTargetUrl, "rtsp%s://%s:%u", useEnet ? "ru" : "", urlAddr, RtspPortNumber);
+			snprintf(rtspTargetUrl, sizeof(rtspTargetUrl), "rtsp%s://%s:%u", useEnet ? "ru" : "", urlAddr, RtspPortNumber);
         }
     }
     else {
         PltSafeStrcpy(urlAddr, sizeof(urlAddr), "0.0.0.0");
-        sprintf(rtspTargetUrl, "rtsp%s://%s:%u", useEnet ? "ru" : "", urlAddr, RtspPortNumber);
+		snprintf(rtspTargetUrl, sizeof(rtspTargetUrl), "rtsp%s://%s:%u", useEnet ? "ru" : "", urlAddr, RtspPortNumber);
     }
 
     switch (AppVersionQuad[0]) {

programming_language: C
repo_url: https://github.com/moonlight-stream/moonlight-common-c
commit_details: repo_url: https://github.com/moonlight-stream/moonlight-common-c
author: Cameron Gutman
author_date: 2022-11-04 21:44:07-05:00
msg: Improve high quality audio support

High quality audio now works on IPv6 and remote connections
repository: repo_url: https://github.com/moonlight-stream/moonlight-common-c
repo_name: moonlight-stream/moonlight-common-c
description: Core implementation of Nvidia's GameStream protocol
repo_language: C
owner: moonlight-stream
file_changes: filename: Connection.c
change_type: ModificationType.MODIFY
diff: @@ -399,7 +399,7 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
 
     Limelog("Starting RTSP handshake...");
     ListenerCallbacks.stageStarting(STAGE_RTSP_HANDSHAKE);
-    err = performRtspHandshake();
+    err = performRtspHandshake(serverInfo);
     if (err != 0) {
         Limelog("failed: %d\n", err);
         ListenerCallbacks.stageFailed(STAGE_RTSP_HANDSHAKE, err);

programming_language: C++
filename: Limelight-internal.h
change_type: ModificationType.MODIFY
diff: @@ -91,7 +91,7 @@ void connectionLostPackets(int lastReceivedPacket, int nextReceivedPacket);
 int sendInputPacketOnControlStream(unsigned char* data, int length);
 bool isControlDataInTransit(void);
 
-int performRtspHandshake(void);
+int performRtspHandshake(PSERVER_INFORMATION serverInfo);
 
 void initializeVideoDepacketizer(int pktSize);
 void destroyVideoDepacketizer(void);

programming_language: C
filename: RtspConnection.c
change_type: ModificationType.MODIFY
diff: @@ -657,32 +657,101 @@ static int parseOpusConfigurations(PRTSP_MESSAGE response) {
     return 0;
 }
 
-// Perform RTSP Handshake with the streaming server machine as part of the connection process
-int performRtspHandshake(void) {
-    int ret;
+static bool parseUrlAddrFromRtspUrlString(const char* rtspUrlString, char* destination) {
+    char* rtspUrlScratchBuffer;
+    char* portSeparator;
+    char* v6EscapeEndChar;
+    char* urlPathSeparator;
+    int prefixLen;
+
+    // Create a copy that we can modify
+    rtspUrlScratchBuffer = strdup(rtspUrlString);
+    if (rtspUrlScratchBuffer == NULL) {
+        return false;
+    }
 
-    // HACK: In order to get GFE to respect our request for a lower audio bitrate, we must
-    // fake our target address so it doesn't match any of the PC's local interfaces. It seems
-    // that the only way to get it to give you "low quality" stereo audio nowadays is if it
-    // thinks you are remote (target address != any local address).
-    if (OriginalVideoBitrate >= HIGH_AUDIO_BITRATE_THRESHOLD &&
-            (AudioCallbacks.capabilities & CAPABILITY_SLOW_OPUS_DECODER) == 0) {
-        addrToUrlSafeString(&RemoteAddr, urlAddr);
+    // If we have a v6 address, we want to stop one character after the closing ]
+    // If we have a v4 address, we want to stop at the port separator
+    portSeparator = strrchr(rtspUrlScratchBuffer, ':');
+    v6EscapeEndChar = strchr(rtspUrlScratchBuffer, ']');
+
+    // Count the prefix length to skip past the initial rtsp:// or rtspru:// part
+    for (prefixLen = 2; rtspUrlScratchBuffer[prefixLen - 2] != 0 && (rtspUrlScratchBuffer[prefixLen - 2] != '/' || rtspUrlScratchBuffer[prefixLen - 1] != '/'); prefixLen++);
+
+    // If we hit the end of the string prior to parsing the prefix, we cannot proceed
+    if (rtspUrlScratchBuffer[prefixLen - 2] == 0) {
+        free(rtspUrlScratchBuffer);
+        return false;
     }
-    else {
-        strcpy(urlAddr, "0.0.0.0");
+
+    // Look for a slash at the end of the host portion of the URL (may not be present)
+    urlPathSeparator = strchr(rtspUrlScratchBuffer + prefixLen, '/');
+
+    // Check for a v6 address first since they also have colons
+    if (v6EscapeEndChar) {
+        // Terminate the string at the next character
+        *(v6EscapeEndChar + 1) = 0;
+    }
+    else if (portSeparator) {
+        // Terminate the string prior to the port separator
+        *portSeparator = 0;
+    }
+    else if (urlPathSeparator) {
+        // Terminate the string prior to the path separator
+        *urlPathSeparator = 0;
     }
 
+    strcpy(destination, rtspUrlScratchBuffer + prefixLen);
+
+    free(rtspUrlScratchBuffer);
+    return true;
+}
+
+// Perform RTSP Handshake with the streaming server machine as part of the connection process
+int performRtspHandshake(PSERVER_INFORMATION serverInfo) {
+    int ret;
+
     LC_ASSERT(RtspPortNumber != 0);
 
     // Initialize global state
     useEnet = (AppVersionQuad[0] >= 5) && (AppVersionQuad[0] <= 7) && (AppVersionQuad[2] < 404);
-    sprintf(rtspTargetUrl, "rtsp%s://%s:%u", useEnet ? "ru" : "", urlAddr, RtspPortNumber);
     currentSeqNumber = 1;
     hasSessionId = false;
     controlStreamId = APP_VERSION_AT_LEAST(7, 1, 431) ? "streamid=control/13/0" : "streamid=control/1/0";
     AudioEncryptionEnabled = false;
 
+    // HACK: In order to get GFE to respect our request for a lower audio bitrate, we must
+    // fake our target address so it doesn't match any of the PC's local interfaces. It seems
+    // that the only way to get it to give you "low quality" stereo audio nowadays is if it
+    // thinks you are remote (target address != any local address).
+    //
+    // We will enable high quality audio if the following are all true:
+    // 1. Video bitrate is higher than 15 Mbps (to ensure most bandwidth is reserved for video)
+    // 2. The audio decoder has not declared that it is slow
+    // 3. The stream is either local or not surround sound (to prevent MTU issues over the Internet)
+    LC_ASSERT(StreamConfig.streamingRemotely != STREAM_CFG_AUTO);
+    if (OriginalVideoBitrate >= HIGH_AUDIO_BITRATE_THRESHOLD &&
+            (AudioCallbacks.capabilities & CAPABILITY_SLOW_OPUS_DECODER) == 0 &&
+            (StreamConfig.streamingRemotely != STREAM_CFG_REMOTE || CHANNEL_COUNT_FROM_AUDIO_CONFIGURATION(StreamConfig.audioConfiguration) <= 2)) {
+        // If we have an RTSP URL string and it was successfully parsed, use that string
+        if (serverInfo->rtspSessionUrl != NULL && parseUrlAddrFromRtspUrlString(serverInfo->rtspSessionUrl, urlAddr)) {
+            strcpy(rtspTargetUrl, serverInfo->rtspSessionUrl);
+        }
+        else {
+            // If an RTSP URL string was not provided or failed to parse, we will construct one now as best we can.
+            //
+            // NB: If the remote address is not a LAN address, the host will likely not enable high quality
+            // audio since it only does that for local streaming normally. We can avoid this limitation,
+            // but only if the caller gave us the RTSP session URL that it received from the host during launch.
+            addrToUrlSafeString(&RemoteAddr, urlAddr);
+            sprintf(rtspTargetUrl, "rtsp%s://%s:%u", useEnet ? "ru" : "", urlAddr, RtspPortNumber);
+        }
+    }
+    else {
+        strcpy(urlAddr, "0.0.0.0");
+        sprintf(rtspTargetUrl, "rtsp%s://%s:%u", useEnet ? "ru" : "", urlAddr, RtspPortNumber);
+    }
+
     switch (AppVersionQuad[0]) {
         case 3:
             rtspClientVersion = 10;

programming_language: C