CVE-2019-15693: Out-of-bounds Write (CWE-787)
value: TigerVNC version prior to 1.10.1 is vulnerable to heap buffer overflow, which occurs in TightDecoder::FilterGradient. Exploitation of this vulnerability could potentially result into remote code execution. This attack appear to be exploitable via network connectivity.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.2
Published: 2019-12-26T15:15Z
Repository: https://github.com/CendioOssman/tigervnc
Language: C
Files changed: common/rfb/tightDecode.h
Commit date: 2019-11-15 11:20:26 +0100
Commit message: Handle empty Tight gradient rects

We always assumed there would be one pixel per row so a rect with
a zero width would result in us writing to unknown memory.

This could theoretically be used by a malicious server to inject
code in to the viewer process.

Issue found by Pavel Cheremushkin from Kaspersky Lab.

Diff: diff --git a/common/rfb/tightDecode.h b/common/rfb/tightDecode.h
index b6e86ed5e..8f77aebd0 100644
--- a/common/rfb/tightDecode.h
+++ b/common/rfb/tightDecode.h
@@ -54,19 +54,21 @@ TightDecoder::FilterGradient24(const rdr::U8 *inbuf,
   // Set up shortcut variables
   int rectHeight = r.height();
   int rectWidth = r.width();
 
   for (y = 0; y < rectHeight; y++) {
-    /* First pixel in a row */
-    for (c = 0; c < 3; c++) {
-      pix[c] = inbuf[y*rectWidth*3+c] + prevRow[c];
-      thisRow[c] = pix[c];
-    }
-    pf.bufferFromRGB((rdr::U8*)&outbuf[y*stride], pix, 1);
+    for (x = 0; x < rectWidth; x++) {
+      /* First pixel in a row */
+      if (x == 0) {
+        for (c = 0; c < 3; c++) {
+          pix[c] = inbuf[y*rectWidth*3+c] + prevRow[c];
+          thisRow[c] = pix[c];
+        }
+        pf.bufferFromRGB((rdr::U8*)&outbuf[y*stride], pix, 1);
+        continue;
+      }
 
-    /* Remaining pixels of a row */
-    for (x = 1; x < rectWidth; x++) {
       for (c = 0; c < 3; c++) {
         est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c];
         if (est[c] > 0xff) {
           est[c] = 0xff;
         } else if (est[c] < 0) {
@@ -101,21 +103,24 @@ void TightDecoder::FilterGradient(const rdr::U8* inbuf,
   // Set up shortcut variables
   int rectHeight = r.height();
   int rectWidth = r.width();
 
   for (y = 0; y < rectHeight; y++) {
-    /* First pixel in a row */
-    pf.rgbFromBuffer(pix, &inbuf[y*rectWidth], 1);
-    for (c = 0; c < 3; c++)
-      pix[c] += prevRow[c];
+    for (x = 0; x < rectWidth; x++) {
+      /* First pixel in a row */
+      if (x == 0) {
+        pf.rgbFromBuffer(pix, &inbuf[y*rectWidth], 1);
+        for (c = 0; c < 3; c++)
+          pix[c] += prevRow[c];
 
-    memcpy(thisRow, pix, sizeof(pix));
+        memcpy(thisRow, pix, sizeof(pix));
 
-    pf.bufferFromRGB((rdr::U8*)&outbuf[y*stride], pix, 1);
+        pf.bufferFromRGB((rdr::U8*)&outbuf[y*stride], pix, 1);
+
+        continue;
+      }
 
-    /* Remaining pixels of a row */
-    for (x = 1; x < rectWidth; x++) {
       for (c = 0; c < 3; c++) {
         est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c];
         if (est[c] > 255) {
           est[c] = 255;
         } else if (est[c] < 0) {

Vulnerable code:     /* First pixel in a row */
    for (c = 0; c < 3; c++) {
      pix[c] = inbuf[y*rectWidth*3+c] + prevRow[c];
      thisRow[c] = pix[c];
    }
    pf.bufferFromRGB((rdr::U8*)&outbuf[y*stride], pix, 1);
    /* Remaining pixels of a row */
    for (x = 1; x < rectWidth; x++) {
    /* First pixel in a row */
    pf.rgbFromBuffer(pix, &inbuf[y*rectWidth], 1);
    for (c = 0; c < 3; c++)
      pix[c] += prevRow[c];
    memcpy(thisRow, pix, sizeof(pix));
    pf.bufferFromRGB((rdr::U8*)&outbuf[y*stride], pix, 1);
    /* Remaining pixels of a row */
    for (x = 1; x < rectWidth; x++) {
Fixed code:     for (x = 0; x < rectWidth; x++) {
      /* First pixel in a row */
      if (x == 0) {
        for (c = 0; c < 3; c++) {
          pix[c] = inbuf[y*rectWidth*3+c] + prevRow[c];
          thisRow[c] = pix[c];
        }
        pf.bufferFromRGB((rdr::U8*)&outbuf[y*stride], pix, 1);
        continue;
      }
    for (x = 0; x < rectWidth; x++) {
      /* First pixel in a row */
      if (x == 0) {
        pf.rgbFromBuffer(pix, &inbuf[y*rectWidth], 1);
        for (c = 0; c < 3; c++)
          pix[c] += prevRow[c];
        memcpy(thisRow, pix, sizeof(pix));
        pf.bufferFromRGB((rdr::U8*)&outbuf[y*stride], pix, 1);

        continue;
      }