June 17, 2020

GHSL-2020-064: integer overflow in LibVNCClient HandleCursorShape resulting in remote heap overflow - CVE-2019-20788

Bas Alberts

Summary

There exists an integer overflow in HandleCursorShape RFB event handler in libvncclient, which is the client implementation for LibVNC included with libvncserver.

This issue allows a malicious VNC server to trigger a remote heap overflow in a connecting VNC client, which may lead to Remote Code Excution (RCE). This issue was addressed in libvncclient 0.9.13.

NOTE: this issue was addressed in the LibVNC pre-release branch on Nov 17, 2019, prior to GHSL's discovery of the same issue in a release version (0.9.11) as shipped in a mainline Linux distribution. In coordination with the maintainer the GHSL decided to still fully triage this issue with CVE assignment so that the various Linux distributions shipping this library would be aware of the included security fix and update accordingly.

Product

libvncserver/libvncclient

Tested Versions

  • libvnccserver 0.9.11

Details: Potential integer overflow in HandleCursorShape RFB event handling

All source snippets are based on the 0.9.11 release of libvncserver.

libvncclient/cursor.c
...
rfbBool HandleCursorShape(rfbClient* client,int xhot, int yhot, int width, int height, uint32_t enc)
{
  int bytesPerPixel;
  size_t bytesPerRow, bytesMaskData;
  rfbXCursorColors rgb;
  uint32_t colors[2];
  char *buf;
  uint8_t *ptr;
  int x, y, b;

  bytesPerPixel = client->format.bitsPerPixel / 8;
  bytesPerRow = (width + 7) / 8;
  bytesMaskData = bytesPerRow * height;

  if (width * height == 0)
    return TRUE;

  /* Allocate memory for pixel data and temporary mask data. */
  if(client->rcSource)
    free(client->rcSource);

[1]
  client->rcSource = malloc(width * height * bytesPerPixel);
  if (client->rcSource == NULL)
    return FALSE;

  buf = malloc(bytesMaskData);
  if (buf == NULL) {
    free(client->rcSource);
    client->rcSource = NULL;
    return FALSE;
  }

  /* Read and decode cursor pixel data, depending on the encoding type. */

  if (enc == rfbEncodingXCursor) {
    /* Read and convert background and foreground colors. */
    if (!ReadFromRFBServer(client, (char *)&rgb, sz_rfbXCursorColors)) {
      free(client->rcSource);
      client->rcSource = NULL;
      free(buf);
      return FALSE;
    }
    colors[0] = RGB24_TO_PIXEL(32, rgb.backRed, rgb.backGreen, rgb.backBlue);
    colors[1] = RGB24_TO_PIXEL(32, rgb.foreRed, rgb.foreGreen, rgb.foreBlue);

    /* Read 1bpp pixel data into a temporary buffer. */
    if (!ReadFromRFBServer(client, buf, bytesMaskData)) {
      free(client->rcSource);
      client->rcSource = NULL;
      free(buf);
      return FALSE;
    }

    /* Convert 1bpp data to byte-wide color indices. */
    ptr = client->rcSource;
    for (y = 0; y < height; y++) {
      for (x = 0; x < width / 8; x++) {
	for (b = 7; b >= 0; b--) {
	  *ptr = buf[y * bytesPerRow + x] >> b & 1;
	  ptr += bytesPerPixel;
	}
      }
      for (b = 7; b > 7 - width % 8; b--) {
	*ptr = buf[y * bytesPerRow + x] >> b & 1;
	ptr += bytesPerPixel;
      }
    }

    /* Convert indices into the actual pixel values. */
    switch (bytesPerPixel) {
    case 1:
      for (x = 0; x < width * height; x++)
	client->rcSource[x] = (uint8_t)colors[client->rcSource[x]];
      break;
    case 2:
      for (x = 0; x < width * height; x++)
	((uint16_t *)client->rcSource)[x] = (uint16_t)colors[client->rcSource[x * 2]];
      break;
    case 4:
      for (x = 0; x < width * height; x++)
	((uint32_t *)client->rcSource)[x] = colors[client->rcSource[x * 4]];
      break;
    }

  } else {			/* enc == rfbEncodingRichCursor */

    if (!ReadFromRFBServer(client, (char *)client->rcSource, width * height * bytesPerPixel)) {
      free(client->rcSource);
      client->rcSource = NULL;
      free(buf);
      return FALSE;
    }

  }

  /* Read and decode mask data. */

  if (!ReadFromRFBServer(client, buf, bytesMaskData)) {
    free(client->rcSource);
    client->rcSource = NULL;
    free(buf);
    return FALSE;
  }

  client->rcMask = malloc(width * height);
  if (client->rcMask == NULL) {
    free(client->rcSource);
    client->rcSource = NULL;
    free(buf);
    return FALSE;
  }

  ptr = client->rcMask;
  for (y = 0; y < height; y++) {
    for (x = 0; x < width / 8; x++) {
      for (b = 7; b >= 0; b--) {
	*ptr++ = buf[y * bytesPerRow + x] >> b & 1;
      }
    }
    for (b = 7; b > 7 - width % 8; b--) {
      *ptr++ = buf[y * bytesPerRow + x] >> b & 1;
    }
  }

  if (client->GotCursorShape != NULL) {
     client->GotCursorShape(client, xhot, yhot, width, height, bytesPerPixel);
  }

  free(buf);

  return TRUE;
}

When handling rfbFramebufferUpdate RFB messages, the libvnclient code will check whether rect.encoding is set to rfbEncodingXcursor or rfbEncodingRichcursor. If this is the case, it will then pass remote (server side) controlled 16bit integer values as parameters into HandleCursorShape. This includes the xhot, yhot, width, height and enc parameters.

At [1] we observe a 32bit integer arithmetic expression that involves the width, height and bytesPerPixel integers. A malicious VNC server can control up to 16bits of the width and height integers, and it may set the bytesPerPixel integer to 1,2 or 4 based on its control over the "Bits Per Pixel" value of the session (8bits, 24bits, or 32bits, divided by 8).

This level of control over the allocation size integer arithmetic expression allows a malicious VNC server to induce an integer overflow and subsequently under-allocate the intended client->rcSource memory region.

The subsequent population of this memory region is based on the width and height parameters, which will in turn result in potentially exploitable memory tresspasses.

Impact

It is possible to craft this vulnerability into a remote heap overflow which, under the right circumstances, may result in remote code execution. However, due to the specifics of the integer overwrap which results in a fairly limited range allocation control vs overflow length, we do not consider this likely.

CVE

  • CVE-2019-20788

Coordinated Disclosure Timeline

  • 04/10/2020: maintainer contacted
  • 04/10/2020: maintainer notes issue has already been fixed in master
  • 04/10/2020: GHSL suggests further triage due to distributions shipping vulnerable versions
  • 04/23/2020: CVE assigned
  • 04/23/2020: GHSL agrees to delay public advisory until 0.9.13 stable is released
  • 06/01/2020: GHSL requests release timeline update from maintainer
  • 06/13/2020: 0.9.13 stable released

Resources

Credit

This issue was discovered and reported by the GitHub Security Lab.

Contact

You can contact the GHSL team at securitylab@github.com, please include GHSL-2020-064 in any communication regarding this issue.