Talos Vulnerability Report

TALOS-2026-2411

GeoVision GV-VMS V20 GV-Cloud memory corruption vulnerability

June 23, 2026
CVE Number

CVE-2026-12488

Summary

A memory corruption vulnerability exists in the GV-Cloud functionality of GV-VMS V20 (version(s): 20.0.2). A specially crafted network request can lead to a denial of service. An attacker can impersonate the legitimate server to trigger this vulnerability.

Confirmed Vulnerable Versions

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

GV-VMS V20 (version(s): 20.0.2)

Product URLs

GV-VMS V20 - https://www.geovision.com.tw/product/GV-VMS%20V20

CVSSv3 Score

6.2 - CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:N/A:H

CWE

CWE-121 - Stack-based Buffer Overflow

Details

The GV-Cloud feature of the GV-VMS allows to remotely monitor and operate the VMS system. One of its features, the RelayProxy, allows the cloud to send commands to a local instance of the relay that will then dispatch messages to a set of ip/ports provided by the cloud. This feature is implemented via the GvRelayProxy.dll file. When started, this service will connect to relay.vsm.mygvcloud.com and will process a server-initiated handshake to exchange key material and identification. It will then keep reading data from the socket to process commands from the cloud connection. We can see this process in the following decompiled code:

__int64 __fastcall GvRelayProxyServerThreadProc(config_data *a1)
{
   /* snipped variable declaration) */ 

  v37 = -2i64;
  log_stuff("GvRelayProxyServerThreadProc: Start");
  time64 = get_time64(0i64);
  srand(time64 % 0xFFFFFFFFi64);
  for ( i = 0; i < 16; ++i )
  {
    for ( j = 0; j < i; ++j )
      rand();
    a1->key_material[i] = rand();
  }
  do
  {
    if ( ConnectToRelayServer(a1) )  // [0] authenticate to server and provide key_material
    {
      v11 = 0;
      v10 = 0;
      while ( !a1->is_stopping )
      {
            /* snipped: use select() to make sure there's data to read */ 
            
          buf.header = 0;
          memset(&buf.field_1, 0, 0xFE4ui64);
          v15 = recv(a1->relay_sock, (char *)&buf, 8, 0);             // [1]  read message header
          if ( v15 <= 0 )
          {
            log_stuff("GvRelayProxyServerThreadProc: relay server disconnect.");
            break;
          }
          if ( buf.header_sync != 12 )
          {
            log_stuff("GvRelayProxyServerThreadProc: [GvRelay] Invaild header sync");
            break;
          }
          if ( buf.size != 8i64 )  // size of the message inlcuding the 8-byte header
          {
            remaining_size_to_read = buf.size - 8i64;
            if ( !recv_data(a1->relay_sock, recv_buffer, remaining_size_to_read) )           // [2]  read remaining data for the current message
            {
              log_stuff("GvRelayProxyServerThreadProc: [GvRelay] Recv Cmd Body Fail");
              break;
            }
          }
          msg_cmd_id = buf.msg_cmd_id;
          if ( buf.msg_cmd_id == 0x8000 )       // GVRELAY_STA_REQUEST_BINDING
          {  
                              /* process commands */
          }
                        /* more commands */ 
  }
  while ( !a1->is_stopping );  //[3] Clean up code executed when the thread is terminating 
  log_stuff("GvRelayProxyServerThreadProc: Wait until all proxy connection disconnected...");
  while ( 1 )
  {
    EnterCriticalSection_(&a1->crit_section1);
    if ( !a1->connection_count )
      break;
    log_stuff("GvRelayProxyServerThreadProc: Connection Count: %ld", a1->connection_count);
    LeaveCriticalSection_(&a1->crit_section1);
    Sleep(0xC8u);
  }
  LeaveCriticalSection_(&a1->crit_section1);
  log_stuff("GvRelayProxyServerThreadProc: All proxy connection disconnected...");
  return 0i64;
}

We can see in the code above that at [0] the connection with the server occurs; at [1] the header of the next message is read and then at [2] the remaining of the message is loaded into the recv_buffer stack variable (which is an array of char of size 4072). The recv_data function is a helper function that repeats multiple calls to recv until all the expected data is read or will stop if an error occurs:

  char __fastcall recv_data(SOCKET a1, char *dst, int sz)
{
  int v4; // [rsp+20h] [rbp-18h]
  int v5; // [rsp+24h] [rbp-14h]

  v4 = 0;
  if ( !sz )
    return 1;
  while ( v4 < sz )
  {
    v5 = recv(a1, &dst[v4], sz - v4, 0);
    if ( v5 <= 0 )
      return 0;
    v4 += v5;
  }
  return 1;
}

Meanwhile the header looks as follows:

struct base_msg_header
{
  char header_sync;
  char field_1;
  WORD size;
  WORD msg_cmd_id;
  __int16 ack_maybe;
};

We can see that the code above blindly trusts the buf.size field regarding the expected size of the message to be read, and does not verify that the data’s expected size fits into the destination buffer. As such, a message too big would cause a stack overflow.

From an attacker perspective, this could be exploited by tricking the VMS into connecting to the wrong relay server; this could happen by either an attacker being able to Man-in-the-middle the connection or tamper with the DNS resolution of the machine hosting the VMS software (e.g. maliciously modifying the DNS server of the router providing internet access to the VMS software). This is possible because the ConnectToRelayServer fails to verify the identity of the server it is talking with.

Upon an exploitation attempt, a few things can happen. If too much data is read, recv will fail silently (warning of an access violation) but the process and thread will keep running assuming this was a simple recv failure. If the amount of data matches the stack frame’s size, no error will occur at first, but upon exiting (a1->is_stopping changed to true and handled at [3]) the function will try to return but will trigger a stack cookie failure instead. If an attacker can leverage another vulnerability to disclose the stack cookie value, then a remote code execution scenario would likely happen. Finally, if the memory layout is favorable to exploitation (i.e. more mapped memory after the current stack frame) then, data past the function’s stack frame could be modified and would not trigger an immediate crash thus creating another risk of remote code execution.

Timeline

2026-05-10 - Vendor Disclosure
2025-12-02 - Vendor Patch Release

Credit

Philippe Laulheret of Cisco Talos