CVE-2019-15691: Operation on a Resource after Expiration or Release (CWE-672)
value: TigerVNC version prior to 1.10.1 is vulnerable to stack use-after-return, which occurs due to incorrect usage of stack memory in ZRLEDecoder. If decoding routine would throw an exception, ZRLEDecoder may try to access stack variable, which has been already freed during the process of stack unwinding. Exploitation of this vulnerability could potentially result into remote code execution. This attack appear to be exploitable via network connectivity.
CWE description: The product uses, accesses, or otherwise operates on a resource after that resource has been expired, released, or revoked.
CVSS v3 base score: 7.2
Published: 2019-12-26T15:15Z
Repository: https://github.com/CendioOssman/tigervnc
Language: Other
Files changed: common/rdr/ZlibInStream.cxx, common/rdr/ZlibInStream.h, common/rfb/CMsgReader.cxx, common/rfb/SMsgReader.cxx, common/rfb/TightDecoder.cxx, common/rfb/zrleDecode.h
Commit date: 2019-11-15 10:53:26 +0100
Commit message: Make ZlibInStream more robust against failures

Move the checks around to avoid missing cases where we might access
memory that is no longer valid. Also avoid touching the underlying
stream implicitly (e.g. via the destructor) as it might also no
longer be valid.

A malicious server could theoretically use this for remote code
execution in the client.

Issue found by Pavel Cheremushkin from Kaspersky Lab

Diff: diff --git a/common/rdr/ZlibInStream.cxx b/common/rdr/ZlibInStream.cxx
index 4053bd19..a361010c 100644
--- a/common/rdr/ZlibInStream.cxx
+++ b/common/rdr/ZlibInStream.cxx
@@ -50,20 +50,20 @@ void ZlibInStream::setUnderlying(InStream* is, int bytesIn_)
 int ZlibInStream::pos()
 {
   return offset + ptr - start;
 }
 
-void ZlibInStream::removeUnderlying()
+void ZlibInStream::flushUnderlying()
 {
   ptr = end = start;
-  if (!underlying) return;
 
   while (bytesIn > 0) {
     decompress(true);
     end = start; // throw away any data
   }
-  underlying = 0;
+
+  setUnderlying(NULL, 0);
 }
 
 void ZlibInStream::reset()
 {
   deinit();
@@ -88,22 +88,20 @@ void ZlibInStream::init()
 }
 
 void ZlibInStream::deinit()
 {
   assert(zs != NULL);
-  removeUnderlying();
+  setUnderlying(NULL, 0);
   inflateEnd(zs);
   delete zs;
   zs = NULL;
 }
 
 int ZlibInStream::overrun(int itemSize, int nItems, bool wait)
 {
   if (itemSize > bufSize)
     throw Exception("ZlibInStream overrun: max itemSize exceeded");
-  if (!underlying)
-    throw Exception("ZlibInStream overrun: no underlying stream");
 
   if (end - ptr != 0)
     memmove(start, ptr, end - ptr);
 
   offset += ptr - start;
@@ -125,10 +123,13 @@ int ZlibInStream::overrun(int itemSize, int nItems, bool wait)
 // generate any output data - it may just consume some input data.  Returns
 // false if wait is false and we would block on the underlying stream.
 
 bool ZlibInStream::decompress(bool wait)
 {
+  if (!underlying)
+    throw Exception("ZlibInStream overrun: no underlying stream");
+
   zs->next_out = (U8*)end;
   zs->avail_out = start + bufSize - end;
 
   int n = underlying->check(1, 1, wait);
   if (n == 0) return false;
diff --git a/common/rdr/ZlibInStream.h b/common/rdr/ZlibInStream.h
index 6bd4da4c..86ba1ff1 100644
--- a/common/rdr/ZlibInStream.h
+++ b/common/rdr/ZlibInStream.h
@@ -36,11 +36,11 @@ namespace rdr {
 
     ZlibInStream(int bufSize=0);
     virtual ~ZlibInStream();
 
     void setUnderlying(InStream* is, int bytesIn);
-    void removeUnderlying();
+    void flushUnderlying();
     int pos();
     void reset();
 
   private:
 
diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx
index a9e12d70..52d40ce7 100644
--- a/common/rfb/CMsgReader.cxx
+++ b/common/rfb/CMsgReader.cxx
@@ -240,11 +240,12 @@ void CMsgReader::readExtendedClipboard(rdr::S32 len)
       buffers[num] = new rdr::U8[lengths[num]];
       zis.readBytes(buffers[num], lengths[num]);
       num++;
     }
 
-    zis.removeUnderlying();
+    zis.flushUnderlying();
+    zis.setUnderlying(NULL, 0);
 
     handler->handleClipboardProvide(flags, lengths, buffers);
 
     num = 0;
     for (i = 0;i < 16;i++) {
diff --git a/common/rfb/SMsgReader.cxx b/common/rfb/SMsgReader.cxx
index ab42e59a..dc7ddea6 100644
--- a/common/rfb/SMsgReader.cxx
+++ b/common/rfb/SMsgReader.cxx
@@ -291,11 +291,12 @@ void SMsgReader::readExtendedClipboard(rdr::S32 len)
       buffers[num] = new rdr::U8[lengths[num]];
       zis.readBytes(buffers[num], lengths[num]);
       num++;
     }
 
-    zis.removeUnderlying();
+    zis.flushUnderlying();
+    zis.setUnderlying(NULL, 0);
 
     handler->handleClipboardProvide(flags, lengths, buffers);
 
     num = 0;
     for (i = 0;i < 16;i++) {
diff --git a/common/rfb/TightDecoder.cxx b/common/rfb/TightDecoder.cxx
index 5b7c553d..ebc98b06 100644
--- a/common/rfb/TightDecoder.cxx
+++ b/common/rfb/TightDecoder.cxx
@@ -339,11 +339,12 @@ void TightDecoder::decodeRect(const Rect& r, const void* buffer,
     // Allocate buffer and decompress the data
     netbuf = new rdr::U8[dataSize];
 
     zis[streamId].readBytes(netbuf, dataSize);
 
-    zis[streamId].removeUnderlying();
+    zis[streamId].flushUnderlying();
+    zis[streamId].setUnderlying(NULL, 0);
     delete ms;
 
     bufptr = netbuf;
     buflen = dataSize;
   }
diff --git a/common/rfb/zrleDecode.h b/common/rfb/zrleDecode.h
index 32b5c92b..f4325385 100644
--- a/common/rfb/zrleDecode.h
+++ b/common/rfb/zrleDecode.h
@@ -172,11 +172,12 @@ void ZRLE_DECODE (const Rect& r, rdr::InStream* is,
 
       pb->imageRect(pf, t, buf);
     }
   }
 
-  zis->removeUnderlying();
+  zis->flushUnderlying();
+  zis->setUnderlying(NULL, 0);
 }
 
 #undef ZRLE_DECODE
 #undef READ_PIXEL
 #undef PIXEL_T

Vulnerable code: void ZlibInStream::removeUnderlying()
  if (!underlying) return;
  underlying = 0;
  removeUnderlying();
  if (!underlying)
    throw Exception("ZlibInStream overrun: no underlying stream");
    void removeUnderlying();
    zis.removeUnderlying();
    zis.removeUnderlying();
    zis[streamId].removeUnderlying();
  zis->removeUnderlying();
Fixed code: void ZlibInStream::flushUnderlying()

  setUnderlying(NULL, 0);
  setUnderlying(NULL, 0);
  if (!underlying)
    throw Exception("ZlibInStream overrun: no underlying stream");

    void flushUnderlying();
    zis.flushUnderlying();
    zis.setUnderlying(NULL, 0);
    zis.flushUnderlying();
    zis.setUnderlying(NULL, 0);
    zis[streamId].flushUnderlying();
    zis[streamId].setUnderlying(NULL, 0);
  zis->flushUnderlying();
  zis->setUnderlying(NULL, 0);