CVE-2023-42801: 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 f57bd745b4cbed577ea654fad4701bea4d38b44c. A malicious game streaming server could exploit a buffer overflow vulnerability to crash a moonlight client. Achieving RCE is possible but unlikely, due to stack canaries in use by modern compiler toolchains. The published binaries for official clients Qt, Android, iOS/tvOS, and Embedded are built with stack canaries, but some unofficial clients may not use stack canaries. This vulnerability takes place after the pairing process, so it requires the client to be tricked into pairing to a malicious host. It is not possible to perform using a man-in-the-middle due to public key pinning that takes place during the pairing process. The bug was addressed in commit b2497a3918a6d79808d9fd0c04734786e70d5954.
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: 7.6
Published: 2023-12-14T17:15Z
Repository: https://github.com/moonlight-stream/moonlight-common-c
Language: C
Files changed: src/Connection.c, src/ControlStream.c, src/InputStream.c, src/Limelight-internal.h, src/Limelight.h, src/Misc.c, src/RtspConnection.c, src/SdpGenerator.c, src/VideoDepacketizer.c, src/VideoStream.c
Commit date: 2016-10-05 17:23:59 -0700
Commit message: API update to provide enough information to support GFE 3.0.7

Diff: diff --git a/src/Connection.c b/src/Connection.c
index dfc849e..6efe357 100644
--- a/src/Connection.c
+++ b/src/Connection.c
@@ -9,11 +9,11 @@ static long terminationCallbackErrorCode;
 
 // Common globals
 char* RemoteAddrString;
 struct sockaddr_storage RemoteAddr;
 SOCKADDR_LEN RemoteAddrLen;
-int ServerMajorVersion;
+int AppVersionQuad[4];
 STREAM_CONFIGURATION StreamConfig;
 CONNECTION_LISTENER_CALLBACKS ListenerCallbacks;
 DECODER_RENDERER_CALLBACKS VideoCallbacks;
 AUDIO_RENDERER_CALLBACKS AudioCallbacks;
 int NegotiatedVideoFormat;
@@ -204,19 +204,24 @@ static int resolveHostName(const char* host)
     return 0;
 #endif
 }
 
 // Starts the connection to the streaming machine
-int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
-    PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks,
-    void* renderContext, int drFlags, int _serverMajorVersion) {
+int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
+    PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags) {
     int err;
 
     NegotiatedVideoFormat = 0;
-    ServerMajorVersion = _serverMajorVersion;
     memcpy(&StreamConfig, streamConfig, sizeof(StreamConfig));
-    RemoteAddrString = strdup(host);
+    RemoteAddrString = strdup(serverInfo->address);
+    
+    // Extract the appversion from the supplied string
+    if (extractVersionQuadFromString(serverInfo->serverInfoAppVersion,
+                                     AppVersionQuad) < 0) {
+        Limelog("Invalid appversion string: %s\n", serverInfo->serverInfoAppVersion);
+        return -1;
+    }
 
     // Replace missing callbacks with placeholders
     fixupMissingCallbacks(&drCallbacks, &arCallbacks, &clCallbacks);
     memcpy(&VideoCallbacks, drCallbacks, sizeof(VideoCallbacks));
     memcpy(&AudioCallbacks, arCallbacks, sizeof(AudioCallbacks));
@@ -242,11 +247,11 @@ int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCON
     ListenerCallbacks.stageComplete(STAGE_PLATFORM_INIT);
     Limelog("done\n");
 
     Limelog("Resolving host name...");
     ListenerCallbacks.stageStarting(STAGE_NAME_RESOLUTION);
-    err = resolveHostName(host);
+    err = resolveHostName(serverInfo->address);
     if (err != 0) {
         Limelog("failed: %d\n", err);
         ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err);
         goto Cleanup;
     }
diff --git a/src/ControlStream.c b/src/ControlStream.c
index 07f8abd..b11119b 100644
--- a/src/ControlStream.c
+++ b/src/ControlStream.c
@@ -149,21 +149,21 @@ static char**preconstructedPayloads;
 int initializeControlStream(void) {
     stopping = 0;
     PltCreateEvent(&invalidateRefFramesEvent);
     LbqInitializeLinkedBlockingQueue(&invalidReferenceFrameTuples, 20);
 
-    if (ServerMajorVersion == 3) {
+    if (AppVersionQuad[0] == 3) {
         packetTypes = (short*)packetTypesGen3;
         payloadLengths = (short*)payloadLengthsGen3;
         preconstructedPayloads = (char**)preconstructedPayloadsGen3;
     }
-    else if (ServerMajorVersion == 4) {
+    else if (AppVersionQuad[0] == 4) {
         packetTypes = (short*)packetTypesGen4;
         payloadLengths = (short*)payloadLengthsGen4;
         preconstructedPayloads = (char**)preconstructedPayloadsGen4;
     }
-    else if (ServerMajorVersion == 5) {
+    else if (AppVersionQuad[0] == 5) {
         packetTypes = (short*)packetTypesGen5;
         payloadLengths = (short*)payloadLengthsGen5;
         preconstructedPayloads = (char**)preconstructedPayloadsGen5;
     }
     else {
@@ -288,11 +288,11 @@ static int sendMessageEnet(short ptype, short paylen, const void* payload) {
     PNVCTL_ENET_PACKET_HEADER packet;
     ENetPacket* enetPacket;
     ENetEvent event;
     int err;
 
-    LC_ASSERT(ServerMajorVersion >= 5);
+    LC_ASSERT(AppVersionQuad[0] >= 5);
     
     // We may be trying to disconnect, so our peer could be gone.
     // This check is safe because we're guaranteed to be holding enetMutex.
     if (peer == NULL) {
         return 0;
@@ -345,11 +345,11 @@ static int sendMessageEnet(short ptype, short paylen, const void* payload) {
 
 static int sendMessageTcp(short ptype, short paylen, const void* payload) {
     PNVCTL_TCP_PACKET_HEADER packet;
     SOCK_RET err;
 
-    LC_ASSERT(ServerMajorVersion < 5);
+    LC_ASSERT(AppVersionQuad[0] < 5);
 
     packet = malloc(sizeof(*packet) + paylen);
     if (packet == NULL) {
         return 0;
     }
@@ -371,11 +371,11 @@ static int sendMessageTcp(short ptype, short paylen, const void* payload) {
 static int sendMessageAndForget(short ptype, short paylen, const void* payload) {
     int ret;
 
     // Unlike regular sockets, ENet sockets aren't safe to invoke from multiple
     // threads at once. We have to synchronize them with a lock.
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         PltLockMutex(&enetMutex);
         ret = sendMessageEnet(ptype, paylen, payload);
         PltUnlockMutex(&enetMutex);
     }
     else {
@@ -385,11 +385,11 @@ static int sendMessageAndForget(short ptype, short paylen, const void* payload)
     return ret;
 }
 
 static int sendMessageAndDiscardReply(short ptype, short paylen, const void* payload) {
     // Discard the response
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         ENetEvent event;
 
         PltLockMutex(&enetMutex);
 
         if (!sendMessageEnet(ptype, paylen, payload)) {
@@ -467,11 +467,11 @@ static void lossStatsThreadFunc(void* context) {
 }
 
 static void requestIdrFrame(void) {
     long long payload[3];
 
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         // Form the payload
         if (lastSeenFrame < 0x20) {
             payload[0] = 0;
             payload[1] = 0x20;
         }
@@ -608,11 +608,11 @@ int stopControlStream(void) {
     return 0;
 }
 
 // Called by the input stream to send a packet for Gen 5+ servers
 int sendInputPacketOnControlStream(unsigned char* data, int length) {
-    LC_ASSERT(ServerMajorVersion >= 5);
+    LC_ASSERT(AppVersionQuad[0] >= 5);
 
     // Send the input data (no reply expected)
     if (sendMessageAndForget(packetTypes[IDX_INPUT_DATA], length, data) == 0) {
         return -1;
     }
@@ -624,11 +624,11 @@ int sendInputPacketOnControlStream(unsigned char* data, int length) {
 int startControlStream(void) {
     int err;
 
     PltCreateMutex(&enetMutex);
 
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         ENetAddress address;
         ENetEvent event;
         
         enet_address_set_address(&address, (struct sockaddr *)&RemoteAddr, RemoteAddrLen);
         enet_address_set_port(&address, 47999);
diff --git a/src/InputStream.c b/src/InputStream.c
index b3ce360..f88e034 100644
--- a/src/InputStream.c
+++ b/src/InputStream.c
@@ -114,11 +114,11 @@ static int addPkcs7PaddingInPlace(unsigned char* plaintext, int plaintextLen) {
 static int encryptData(const unsigned char* plaintext, int plaintextLen,
                        unsigned char* ciphertext, int* ciphertextLen) {
     int ret;
     int len;
     
-    if (ServerMajorVersion >= 7) {
+    if (AppVersionQuad[0] >= 7) {
         EVP_CIPHER_CTX_init(&cipherContext);
 
         // Gen 7 servers use 128-bit AES GCM
         if (EVP_EncryptInit_ex(&cipherContext, EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) {
             ret = -1;
@@ -330,11 +330,11 @@ static void inputSendThreadProc(void* context) {
 
         // Prepend the length to the message
         encryptedLengthPrefix = htonl((unsigned long)encryptedSize);
         memcpy(&encryptedBuffer[0], &encryptedLengthPrefix, 4);
 
-        if (ServerMajorVersion < 5) {
+        if (AppVersionQuad[0] < 5) {
             // Send the encrypted payload
             err = send(inputSock, (const char*) encryptedBuffer,
                 (int) (encryptedSize + sizeof(encryptedLengthPrefix)), 0);
             if (err <= 0) {
                 Limelog("Input: send() failed: %d\n", (int) LastSocketError());
@@ -345,11 +345,11 @@ static void inputSendThreadProc(void* context) {
         else {
             // For reasons that I can't understand, NVIDIA decides to use the last 16
             // bytes of ciphertext in the most recent game controller packet as the IV for
             // future encryption. I think it may be a buffer overrun on their end but we'll have
             // to mimic it to work correctly.
-            if (ServerMajorVersion >= 7 && encryptedSize >= 16 + sizeof(currentAesIv)) {
+            if (AppVersionQuad[0] >= 7 && encryptedSize >= 16 + sizeof(currentAesIv)) {
                 memcpy(currentAesIv,
                        &encryptedBuffer[4 + encryptedSize - sizeof(currentAesIv)],
                        sizeof(currentAesIv));
             }
             
@@ -367,11 +367,11 @@ static void inputSendThreadProc(void* context) {
 // Begin the input stream
 int startInputStream(void) {
     int err;
 
     // After Gen 5, we send input on the control stream
-    if (ServerMajorVersion < 5) {
+    if (AppVersionQuad[0] < 5) {
         inputSock = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
             35043, INPUT_STREAM_TIMEOUT_SEC);
         if (inputSock == INVALID_SOCKET) {
             return LastSocketFail();
         }
@@ -424,11 +424,11 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) {
 
     holder->packetLength = sizeof(NV_MOUSE_MOVE_PACKET);
     holder->packet.mouseMove.header.packetType = htonl(PACKET_TYPE_MOUSE_MOVE);
     holder->packet.mouseMove.magic = MOUSE_MOVE_MAGIC;
     // On Gen 5 servers, the header code is incremented by one
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         holder->packet.mouseMove.magic++;
     }
     holder->packet.mouseMove.deltaX = htons(deltaX);
     holder->packet.mouseMove.deltaY = htons(deltaY);
 
@@ -455,11 +455,11 @@ int LiSendMouseButtonEvent(char action, int button) {
     }
 
     holder->packetLength = sizeof(NV_MOUSE_BUTTON_PACKET);
     holder->packet.mouseButton.header.packetType = htonl(PACKET_TYPE_MOUSE_BUTTON);
     holder->packet.mouseButton.action = action;
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         holder->packet.mouseButton.action++;
     }
     holder->packet.mouseButton.button = htonl(button);
 
     err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
@@ -513,11 +513,11 @@ static int sendControllerEventInternal(short controllerNumber, short buttonFlags
     holder = malloc(sizeof(*holder));
     if (holder == NULL) {
         return -1;
     }
 
-    if (ServerMajorVersion == 3) {
+    if (AppVersionQuad[0] == 3) {
         // Generation 3 servers don't support multiple controllers so we send
         // the legacy packet
         holder->packetLength = sizeof(NV_CONTROLLER_PACKET);
         holder->packet.controller.header.packetType = htonl(PACKET_TYPE_CONTROLLER);
         holder->packet.controller.headerA = C_HEADER_A;
@@ -536,11 +536,11 @@ static int sendControllerEventInternal(short controllerNumber, short buttonFlags
         // Generation 4+ servers support passing the controller number
         holder->packetLength = sizeof(NV_MULTI_CONTROLLER_PACKET);
         holder->packet.multiController.header.packetType = htonl(PACKET_TYPE_MULTI_CONTROLLER);
         holder->packet.multiController.headerA = MC_HEADER_A;
         // On Gen 5 servers, the header code is decremented by one
-        if (ServerMajorVersion >= 5) {
+        if (AppVersionQuad[0] >= 5) {
             holder->packet.multiController.headerA--;
         }
         holder->packet.multiController.headerB = MC_HEADER_B;
         holder->packet.multiController.controllerNumber = controllerNumber;
         holder->packet.multiController.midA = MC_ACTIVE_CONTROLLER_FLAGS;
@@ -596,11 +596,11 @@ int LiSendScrollEvent(signed char scrollClicks) {
 
     holder->packetLength = sizeof(NV_SCROLL_PACKET);
     holder->packet.scroll.header.packetType = htonl(PACKET_TYPE_SCROLL);
     holder->packet.scroll.magicA = MAGIC_A;
     // On Gen 5 servers, the header code is incremented by one
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         holder->packet.scroll.magicA++;
     }
     holder->packet.scroll.zero1 = 0;
     holder->packet.scroll.zero2 = 0;
     holder->packet.scroll.scrollAmt1 = htons(scrollClicks * 120);
diff --git a/src/Limelight-internal.h b/src/Limelight-internal.h
index f8712aa..efd5e96 100644
--- a/src/Limelight-internal.h
+++ b/src/Limelight-internal.h
@@ -10,19 +10,20 @@
 
 // Common globals
 extern char* RemoteAddrString;
 extern struct sockaddr_storage RemoteAddr;
 extern SOCKADDR_LEN RemoteAddrLen;
-extern int ServerMajorVersion;
+extern int AppVersionQuad[4];
 extern STREAM_CONFIGURATION StreamConfig;
 extern CONNECTION_LISTENER_CALLBACKS ListenerCallbacks;
 extern DECODER_RENDERER_CALLBACKS VideoCallbacks;
 extern AUDIO_RENDERER_CALLBACKS AudioCallbacks;
 extern int NegotiatedVideoFormat;
 
 int isBeforeSignedInt(int numA, int numB, int ambiguousCase);
 int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs);
+int extractVersionQuadFromString(const char* string, int* quad);
 
 void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks,
     PCONNECTION_LISTENER_CALLBACKS* clCallbacks);
 
 char* getSdpPayloadForStreamConfig(int rtspClientVersion, int* length);
diff --git a/src/Limelight.h b/src/Limelight.h
index 52fdd97..f8579ae 100644
--- a/src/Limelight.h
+++ b/src/Limelight.h
@@ -212,19 +212,32 @@ typedef struct _CONNECTION_LISTENER_CALLBACKS {
 } CONNECTION_LISTENER_CALLBACKS, *PCONNECTION_LISTENER_CALLBACKS;
 
 // Use this function to zero the connection callbacks when allocated on the stack or heap
 void LiInitializeConnectionCallbacks(PCONNECTION_LISTENER_CALLBACKS clCallbacks);
 
+
+typedef struct _SERVER_INFORMATION {
+    // Server host name or IP address in text form
+    const char* address;
+    
+    // Text inside 'appversion' tag in /serverinfo
+    const char* serverInfoAppVersion;
+    
+    // Text inside 'GfeVersion' tag in /serverinfo (if present)
+    const char* serverInfoGfeVersion;
+} SERVER_INFORMATION, *PSERVER_INFORMATION;
+
+// Use this function to zero the server information when allocated on the stack or heap
+void LiInitializeServerInformation(PSERVER_INFORMATION serverInfo);
+
 // This function begins streaming.
 //
 // Callbacks are all optional. Pass NULL for individual callbacks within each struct or pass NULL for the entire struct
 // to use the defaults for all callbacks.
 //
-// _serverMajorVersion is the major version number of the 'appversion' tag in the /serverinfo request
-//
-int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
-    PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags, int _serverMajorVersion);
+int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
+    PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags);
 
 // This function stops streaming.
 void LiStopConnection(void);
 
 // Use to get a user-visible string to display initialization progress
diff --git a/src/Misc.c b/src/Misc.c
index 1e69468..5c26a6e 100644
--- a/src/Misc.c
+++ b/src/Misc.c
@@ -35,10 +35,42 @@ int isBeforeSignedInt(int numA, int numB, int ambiguousCase) {
         // The sign switch is ambiguous
         return ambiguousCase;
     }
 }
 
+int extractVersionQuadFromString(const char* string, int* quad) {
+    char versionString[128];
+    char* nextDot;
+    char* nextNumber;
+    int i;
+    
+    strcpy(versionString, string);
+    nextNumber = versionString;
+    
+    for (i = 0; i < 4; i++) {
+        if (i == 3) {
+            nextDot = strchr(nextNumber, '\0');
+        }
+        else {
+            nextDot = strchr(nextNumber, '.');
+        }
+        if (nextDot == NULL) {
+            return -1;
+        }
+        
+        // Cut the string off at the next dot
+        *nextDot = '\0';
+        
+        quad[i] = atoi(nextNumber);
+        
+        // Move on to the next segment
+        nextNumber = nextDot + 1;
+    }
+    
+    return 0;
+}
+
 void LiInitializeStreamConfiguration(PSTREAM_CONFIGURATION streamConfig) {
     memset(streamConfig, 0, sizeof(*streamConfig));
 }
 
 void LiInitializeVideoCallbacks(PDECODER_RENDERER_CALLBACKS drCallbacks) {
@@ -49,6 +81,10 @@ void LiInitializeAudioCallbacks(PAUDIO_RENDERER_CALLBACKS arCallbacks) {
     memset(arCallbacks, 0, sizeof(*arCallbacks));
 }
 
 void LiInitializeConnectionCallbacks(PCONNECTION_LISTENER_CALLBACKS clCallbacks) {
     memset(clCallbacks, 0, sizeof(*clCallbacks));
-}
\ No newline at end of file
+}
+
+void LiInitializeServerInformation(PSERVER_INFORMATION serverInfo) {
+    memset(serverInfo, 0, sizeof(*serverInfo));
+}
diff --git a/src/RtspConnection.c b/src/RtspConnection.c
index 38e1453..d1960c2 100644
--- a/src/RtspConnection.c
+++ b/src/RtspConnection.c
@@ -271,11 +271,11 @@ Exit:
     return ret;
 }
 
 static int transactRtspMessage(PRTSP_MESSAGE request, PRTSP_MESSAGE response, int expectingPayload, int* error) {
     // Gen 5+ does RTSP over ENet not TCP
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         return transactRtspMessageEnet(request, response, expectingPayload, error);
     }
     else {
         return transactRtspMessageTcp(request, response, expectingPayload, error);
     }
@@ -347,11 +347,11 @@ static int setupStream(PRTSP_MESSAGE response, char* target, int* error) {
                 ret = 0;
                 goto FreeMessage;
             }
         }
 
-        if (ServerMajorVersion >= 6) {
+        if (AppVersionQuad[0] >= 6) {
             // It looks like GFE doesn't care what we say our port is but
             // we need to give it some port to successfully complete the
             // handshake process.
             transportValue = "unicast;X-GS-ClientPort=50000-50001";
         }
@@ -444,11 +444,11 @@ int performRtspHandshake(void) {
     addrToUrlSafeString(&RemoteAddr, urlAddr);
     sprintf(rtspTargetUrl, "rtsp://%s", urlAddr);
     currentSeqNumber = 1;
     hasSessionId = 0;
 
-    switch (ServerMajorVersion) {
+    switch (AppVersionQuad[0]) {
         case 3:
             rtspClientVersion = 10;
             break;
         case 4:
             rtspClientVersion = 11;
@@ -465,11 +465,11 @@ int performRtspHandshake(void) {
             rtspClientVersion = 14;
             break;
     }
     
     // Gen 5 servers use ENet to do the RTSP handshake
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         ENetAddress address;
         ENetEvent event;
         
         enet_address_set_address(&address, (struct sockaddr *)&RemoteAddr, RemoteAddrLen);
         enet_address_set_port(&address, 48010);
@@ -560,11 +560,11 @@ int performRtspHandshake(void) {
         RTSP_MESSAGE response;
         char* sessionId;
         int error = -1;
 
         if (!setupStream(&response,
-                         ServerMajorVersion >= 5 ? "streamid=audio/0/0" : "streamid=audio",
+                         AppVersionQuad[0] >= 5 ? "streamid=audio/0/0" : "streamid=audio",
                          &error)) {
             Limelog("RTSP SETUP streamid=audio request failed: %d\n", error);
             ret = error;
             goto Exit;
         }
@@ -592,11 +592,11 @@ int performRtspHandshake(void) {
     {
         RTSP_MESSAGE response;
         int error = -1;
 
         if (!setupStream(&response,
-                         ServerMajorVersion >= 5 ? "streamid=video/0/0" : "streamid=video",
+                         AppVersionQuad[0] >= 5 ? "streamid=video/0/0" : "streamid=video",
                          &error)) {
             Limelog("RTSP SETUP streamid=video request failed: %d\n", error);
             ret = error;
             goto Exit;
         }
@@ -609,11 +609,11 @@ int performRtspHandshake(void) {
         }
 
         freeMessage(&response);
     }
     
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         RTSP_MESSAGE response;
         int error = -1;
 
         if (!setupStream(&response, "streamid=control/1/0", &error)) {
             Limelog("RTSP SETUP streamid=control request failed: %d\n", error);
@@ -693,11 +693,11 @@ int performRtspHandshake(void) {
     
     ret = 0;
     
 Exit:
     // Cleanup the ENet stuff
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         if (peer != NULL) {
             enet_peer_disconnect_now(peer, 0);
             peer = NULL;
         }
         
diff --git a/src/SdpGenerator.c b/src/SdpGenerator.c
index 013df7e..f0376a8 100644
--- a/src/SdpGenerator.c
+++ b/src/SdpGenerator.c
@@ -183,11 +183,11 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
 
     err |= addAttributeString(&optionHead, "x-nv-video[0].timeoutLengthMs", "7000");
     err |= addAttributeString(&optionHead, "x-nv-video[0].framesWithInvalidRefThreshold", "0");
 
     sprintf(payloadStr, "%d", StreamConfig.bitrate);
-    if (ServerMajorVersion >= 5) {
+    if (AppVersionQuad[0] >= 5) {
         err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.minimumBitrateKbps", payloadStr);
         err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.maximumBitrateKbps", payloadStr);
     }
     else {
         if (StreamConfig.streamingRemotely) {
@@ -217,21 +217,21 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
     else {
         err |= addAttributeString(&optionHead, "x-nv-vqos[0].qosTrafficType", "5");
         err |= addAttributeString(&optionHead, "x-nv-aqos.qosTrafficType", "4");
     }
 
-    if (ServerMajorVersion == 3) {
+    if (AppVersionQuad[0] == 3) {
         err |= addGen3Options(&optionHead, urlSafeAddr);
     }
-    else if (ServerMajorVersion == 4) {
+    else if (AppVersionQuad[0] == 4) {
         err |= addGen4Options(&optionHead, urlSafeAddr);
     }
     else {
         err |= addGen5Options(&optionHead);
     }
 
-    if (ServerMajorVersion >= 4) {
+    if (AppVersionQuad[0] >= 4) {
         if (NegotiatedVideoFormat == VIDEO_FORMAT_H265) {
             err |= addAttributeString(&optionHead, "x-nv-clientSupportHevc", "1");
             err |= addAttributeString(&optionHead, "x-nv-vqos[0].bitStreamFormat", "1");
             
             // Disable slicing on HEVC
@@ -296,11 +296,11 @@ static int fillSdpHeader(char* buffer, int rtspClientVersion, char*urlSafeAddr)
 // Populate the SDP tail with required information
 static int fillSdpTail(char* buffer) {
     return sprintf(buffer,
         "t=0 0\r\n"
         "m=video %d  \r\n",
-        ServerMajorVersion < 4 ? 47996 : 47998);
+        AppVersionQuad[0] < 4 ? 47996 : 47998);
 }
 
 // Get the SDP attributes for the stream config
 char* getSdpPayloadForStreamConfig(int rtspClientVersion, int* length) {
     PSDP_OPTION attributeList;
diff --git a/src/VideoDepacketizer.c b/src/VideoDepacketizer.c
index 89122e3..77626cd 100644
--- a/src/VideoDepacketizer.c
+++ b/src/VideoDepacketizer.c
@@ -505,11 +505,11 @@ void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length) {
         connectionLostPackets(lastPacketInStream, streamPacketIndex);
     }
     lastPacketInStream = streamPacketIndex;
 
     // If this is the first packet, skip the frame header (if one exists)
-    if (firstPacket && ServerMajorVersion >= 5) {
+    if (firstPacket && AppVersionQuad[0] >= 5) {
         currentPos.offset += 8;
         currentPos.length -= 8;
     }
 
     if (firstPacket && isIdrFrameStart(&currentPos))
diff --git a/src/VideoStream.c b/src/VideoStream.c
index 4db3bab..7e6e5ec 100644
--- a/src/VideoStream.c
+++ b/src/VideoStream.c
@@ -216,11 +216,11 @@ int startVideoStream(void* rendererContext, int drFlags) {
         if (err != 0) {
             return err;
         }
     }
 
-    if (ServerMajorVersion == 3) {
+    if (AppVersionQuad[0] == 3) {
         // Connect this socket to open port 47998 for our ping thread
         firstFrameSocket = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
                                             FIRST_FRAME_PORT, FIRST_FRAME_TIMEOUT_SEC);
         if (firstFrameSocket == INVALID_SOCKET) {
             return LastSocketError();
@@ -232,11 +232,11 @@ int startVideoStream(void* rendererContext, int drFlags) {
     err = PltCreateThread(UdpPingThreadProc, NULL, &udpPingThread);
     if (err != 0) {
         return err;
     }
 
-    if (ServerMajorVersion == 3) {
+    if (AppVersionQuad[0] == 3) {
         // Read the first frame to start the flow of video
         err = readFirstFrame();
         if (err != 0) {
             return err;
         }

Vulnerable code: int ServerMajorVersion;
int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
    PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks,
    void* renderContext, int drFlags, int _serverMajorVersion) {
    ServerMajorVersion = _serverMajorVersion;
    RemoteAddrString = strdup(host);
    err = resolveHostName(host);
    if (ServerMajorVersion == 3) {
    else if (ServerMajorVersion == 4) {
    else if (ServerMajorVersion == 5) {
    LC_ASSERT(ServerMajorVersion >= 5);
    LC_ASSERT(ServerMajorVersion < 5);
    if (ServerMajorVersion >= 5) {
    if (ServerMajorVersion >= 5) {
    if (ServerMajorVersion >= 5) {
    LC_ASSERT(ServerMajorVersion >= 5);
    if (ServerMajorVersion >= 5) {
    if (ServerMajorVersion >= 7) {
        if (ServerMajorVersion < 5) {
            if (ServerMajorVersion >= 7 && encryptedSize >= 16 + sizeof(currentAesIv)) {
    if (ServerMajorVersion < 5) {
    if (ServerMajorVersion >= 5) {
    if (ServerMajorVersion >= 5) {
    if (ServerMajorVersion == 3) {
        if (ServerMajorVersion >= 5) {
    if (ServerMajorVersion >= 5) {
extern int ServerMajorVersion;
// _serverMajorVersion is the major version number of the 'appversion' tag in the /serverinfo request
//
int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
    PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags, int _serverMajorVersion);
}
    if (ServerMajorVersion >= 5) {
        if (ServerMajorVersion >= 6) {
    switch (ServerMajorVersion) {
    if (ServerMajorVersion >= 5) {
                         ServerMajorVersion >= 5 ? "streamid=audio/0/0" : "streamid=audio",
                         ServerMajorVersion >= 5 ? "streamid=video/0/0" : "streamid=video",
    if (ServerMajorVersion >= 5) {
    if (ServerMajorVersion >= 5) {
    if (ServerMajorVersion >= 5) {
    if (ServerMajorVersion == 3) {
    else if (ServerMajorVersion == 4) {
    if (ServerMajorVersion >= 4) {
        ServerMajorVersion < 4 ? 47996 : 47998);
    if (firstPacket && ServerMajorVersion >= 5) {
    if (ServerMajorVersion == 3) {
    if (ServerMajorVersion == 3) {
Fixed code: int AppVersionQuad[4];
int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
    PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags) {
    RemoteAddrString = strdup(serverInfo->address);
    
    // Extract the appversion from the supplied string
    if (extractVersionQuadFromString(serverInfo->serverInfoAppVersion,
                                     AppVersionQuad) < 0) {
        Limelog("Invalid appversion string: %s\n", serverInfo->serverInfoAppVersion);
        return -1;
    }
    err = resolveHostName(serverInfo->address);
    if (AppVersionQuad[0] == 3) {
    else if (AppVersionQuad[0] == 4) {
    else if (AppVersionQuad[0] == 5) {
    LC_ASSERT(AppVersionQuad[0] >= 5);
    LC_ASSERT(AppVersionQuad[0] < 5);
    if (AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] >= 5) {
    LC_ASSERT(AppVersionQuad[0] >= 5);
    if (AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] >= 7) {
        if (AppVersionQuad[0] < 5) {
            if (AppVersionQuad[0] >= 7 && encryptedSize >= 16 + sizeof(currentAesIv)) {
    if (AppVersionQuad[0] < 5) {
    if (AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] == 3) {
        if (AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] >= 5) {
extern int AppVersionQuad[4];
int extractVersionQuadFromString(const char* string, int* quad);

typedef struct _SERVER_INFORMATION {
    // Server host name or IP address in text form
    const char* address;
    
    // Text inside 'appversion' tag in /serverinfo
    const char* serverInfoAppVersion;
    
    // Text inside 'GfeVersion' tag in /serverinfo (if present)
    const char* serverInfoGfeVersion;
} SERVER_INFORMATION, *PSERVER_INFORMATION;

// Use this function to zero the server information when allocated on the stack or heap
void LiInitializeServerInformation(PSERVER_INFORMATION serverInfo);

int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
    PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags);
int extractVersionQuadFromString(const char* string, int* quad) {
    char versionString[128];
    char* nextDot;
    char* nextNumber;
    int i;
    
    strcpy(versionString, string);
    nextNumber = versionString;
    
    for (i = 0; i < 4; i++) {
        if (i == 3) {
            nextDot = strchr(nextNumber, '\0');
        }
        else {
            nextDot = strchr(nextNumber, '.');
        }
        if (nextDot == NULL) {
            return -1;
        }
        
        // Cut the string off at the next dot
        *nextDot = '\0';
        
        quad[i] = atoi(nextNumber);
        
        // Move on to the next segment
        nextNumber = nextDot + 1;
    }
    
    return 0;
}

}

void LiInitializeServerInformation(PSERVER_INFORMATION serverInfo) {
    memset(serverInfo, 0, sizeof(*serverInfo));
}
    if (AppVersionQuad[0] >= 5) {
        if (AppVersionQuad[0] >= 6) {
    switch (AppVersionQuad[0]) {
    if (AppVersionQuad[0] >= 5) {
                         AppVersionQuad[0] >= 5 ? "streamid=audio/0/0" : "streamid=audio",
                         AppVersionQuad[0] >= 5 ? "streamid=video/0/0" : "streamid=video",
    if (AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] == 3) {
    else if (AppVersionQuad[0] == 4) {
    if (AppVersionQuad[0] >= 4) {
        AppVersionQuad[0] < 4 ? 47996 : 47998);
    if (firstPacket && AppVersionQuad[0] >= 5) {
    if (AppVersionQuad[0] == 3) {
    if (AppVersionQuad[0] == 3) {