CVE-2023-42799: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow') (CWE-120)
value: 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.
CWE 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.
CVSS v3 base score: 8.8
Published: 2023-12-14T17:15Z
Repository: https://github.com/moonlight-stream/moonlight-common-c
Language: C
Files changed: src/Connection.c, src/Limelight-internal.h, src/RtspConnection.c
Commit date: 2022-11-04 21:44:07 -0500
Commit message: Improve high quality audio support

High quality audio now works on IPv6 and remote connections

Diff: diff --git a/src/Connection.c b/src/Connection.c
index 1998641..b37201d 100644
--- a/src/Connection.c
+++ b/src/Connection.c
@@ -397,11 +397,11 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
     ListenerCallbacks.stageComplete(STAGE_AUDIO_STREAM_INIT);
     Limelog("done\n");
 
     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);
         goto Cleanup;
     }
diff --git a/src/Limelight-internal.h b/src/Limelight-internal.h
index 10cfc2d..15fd9d1 100644
--- a/src/Limelight-internal.h
+++ b/src/Limelight-internal.h
@@ -89,11 +89,11 @@ void connectionReceivedCompleteFrame(int frameIndex);
 void connectionSawFrame(int frameIndex);
 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);
 void queueRtpPacket(PRTPV_QUEUE_ENTRY queueEntry);
 void stopVideoDepacketizer(void);
diff --git a/src/RtspConnection.c b/src/RtspConnection.c
index cceaa12..87f5086 100644
--- a/src/RtspConnection.c
+++ b/src/RtspConnection.c
@@ -655,36 +655,105 @@ 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;
             break;
         case 4:

Vulnerable code:     err = performRtspHandshake();
int performRtspHandshake(void);
// Perform RTSP Handshake with the streaming server machine as part of the connection process
int performRtspHandshake(void) {
    int ret;
    // 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);
    else {
        strcpy(urlAddr, "0.0.0.0");
    sprintf(rtspTargetUrl, "rtsp%s://%s:%u", useEnet ? "ru" : "", urlAddr, RtspPortNumber);
Fixed code:     err = performRtspHandshake(serverInfo);
int performRtspHandshake(PSERVER_INFORMATION serverInfo);
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;
    }
    // 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;

    // 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;

    // 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);
    }