From bcefa591cd7b4f8c635a9cadd3438bb5bf5ad814 Mon Sep 17 00:00:00 2001 From: Andreas Weigel Date: Thu, 16 Feb 2017 10:10:33 +0100 Subject: factor out hybi decode part to make it testable remove direct dependency on rfbClientPtr structure in hybi decode function(s) --- libvncserver/ws_decode.c | 448 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 libvncserver/ws_decode.c (limited to 'libvncserver/ws_decode.c') diff --git a/libvncserver/ws_decode.c b/libvncserver/ws_decode.c new file mode 100644 index 0000000..e74a33c --- /dev/null +++ b/libvncserver/ws_decode.c @@ -0,0 +1,448 @@ +#include "ws_decode.h" + +#include +#include +#include + +#define WS_HYBI_MASK_LEN 4 + + +static int +hybiRemaining(ws_ctx_t *wsctx) +{ + return wsctx->nToRead - wsctx->nReadRaw; +} + +void +hybiDecodeCleanup(ws_ctx_t *wsctx) +{ + wsctx->header.payloadLen = 0; + wsctx->header.mask.u = 0; + wsctx->nReadRaw = 0; + wsctx->nToRead= 0; + wsctx->carrylen = 0; + wsctx->readPos = (unsigned char *)wsctx->codeBufDecode; + wsctx->readlen = 0; + wsctx->hybiDecodeState = WS_HYBI_STATE_HEADER_PENDING; + wsctx->writePos = NULL; + rfbLog("cleaned up wsctx\n"); +} + +/** + * Return payload data that has been decoded/unmasked from + * a websocket frame. + * + * @param[out] dst destination buffer + * @param[in] len bytes to copy to destination buffer + * @param[in,out] wsctx internal state of decoding procedure + * @param[out] number of bytes actually written to dst buffer + * @return next hybi decoding state + */ +static int +hybiReturnData(char *dst, int len, ws_ctx_t *wsctx, int *nWritten) +{ + int nextState = WS_HYBI_STATE_ERR; + + /* if we have something already decoded copy and return */ + if (wsctx->readlen > 0) { + /* simply return what we have */ + if (wsctx->readlen > len) { + rfbLog("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", len, wsctx->readPos, wsctx->readlen); + memcpy(dst, wsctx->readPos, len); + *nWritten = len; + wsctx->readlen -= len; + wsctx->readPos += len; + nextState = WS_HYBI_STATE_DATA_AVAILABLE; + } else { + rfbLog("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", wsctx->readlen, wsctx->readPos, wsctx->readlen); + memcpy(dst, wsctx->readPos, wsctx->readlen); + *nWritten = wsctx->readlen; + wsctx->readlen = 0; + wsctx->readPos = NULL; + if (hybiRemaining(wsctx) == 0) { + nextState = WS_HYBI_STATE_FRAME_COMPLETE; + } else { + nextState = WS_HYBI_STATE_DATA_NEEDED; + } + } + rfbLog("after copy: readPos=%p, readLen=%d\n", wsctx->readPos, wsctx->readlen); + } else if (wsctx->hybiDecodeState == WS_HYBI_STATE_CLOSE_REASON_PENDING) { + nextState = WS_HYBI_STATE_CLOSE_REASON_PENDING; + } + return nextState; +} + +/** + * Read an RFC 6455 websocket frame (IETF hybi working group). + * + * Internal state is updated according to bytes received and the + * decoding of header information. + * + * @param[in] cl client ptr with ptr to raw socket and ws_ctx_t ptr + * @param[out] sockRet emulated recv return value + * @return next hybi decoding state; WS_HYBI_STATE_HEADER_PENDING indicates + * that the header was not received completely. + */ +static int +hybiReadHeader(ws_ctx_t *wsctx, int *sockRet) +{ + int ret; + char *headerDst = wsctx->codeBufDecode + wsctx->nReadRaw; + int n = WSHLENMAX - wsctx->nReadRaw; + + rfbLog("header_read to %p with len=%d\n", headerDst, n); + //ret = ws_read(cl, headerDst, n); + ret = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, headerDst, n); + rfbLog("read %d bytes from socket\n", ret); + if (ret <= 0) { + if (-1 == ret) { + /* save errno because rfbErr() will tamper it */ + int olderrno = errno; + rfbErr("%s: peek; %m\n", __func__); + errno = olderrno; + *sockRet = -1; + } else { + *sockRet = 0; + } + return WS_HYBI_STATE_ERR; + } + + wsctx->nReadRaw += ret; + if (wsctx->nReadRaw < 2) { + /* cannot decode header with less than two bytes */ + errno = EAGAIN; + *sockRet = -1; + return WS_HYBI_STATE_HEADER_PENDING; + } + + /* first two header bytes received; interpret header data and get rest */ + wsctx->header.data = (ws_header_t *)wsctx->codeBufDecode; + + wsctx->header.opcode = wsctx->header.data->b0 & 0x0f; + + /* fin = (header->b0 & 0x80) >> 7; */ /* not used atm */ + wsctx->header.payloadLen = wsctx->header.data->b1 & 0x7f; + rfbLog("first header bytes received; opcode=%d lenbyte=%d\n", wsctx->header.opcode, wsctx->header.payloadLen); + + /* + * 4.3. Client-to-Server Masking + * + * The client MUST mask all frames sent to the server. A server MUST + * close the connection upon receiving a frame with the MASK bit set to 0. + **/ + if (!(wsctx->header.data->b1 & 0x80)) { + rfbErr("%s: got frame without mask ret=%d\n", __func__, ret); + syslog(LOG_ERR, "%s: got frame without mask; ret=%d\n", __func__, ret); + errno = EIO; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } + + if (wsctx->header.payloadLen < 126 && wsctx->nReadRaw >= 6) { + wsctx->header.headerLen = 2 + WS_HYBI_MASK_LEN; + wsctx->header.mask = wsctx->header.data->u.m; + } else if (wsctx->header.payloadLen == 126 && 8 <= wsctx->nReadRaw) { + wsctx->header.headerLen = 4 + WS_HYBI_MASK_LEN; + wsctx->header.payloadLen = WS_NTOH16(wsctx->header.data->u.s16.l16); + wsctx->header.mask = wsctx->header.data->u.s16.m16; + } else if (wsctx->header.payloadLen == 127 && 14 <= wsctx->nReadRaw) { + wsctx->header.headerLen = 10 + WS_HYBI_MASK_LEN; + wsctx->header.payloadLen = WS_NTOH64(wsctx->header.data->u.s64.l64); + wsctx->header.mask = wsctx->header.data->u.s64.m64; + } else { + /* Incomplete frame header, try again */ + rfbErr("%s: incomplete frame header; ret=%d\n", __func__, ret); + errno = EAGAIN; + *sockRet = -1; + return WS_HYBI_STATE_HEADER_PENDING; + } + + /* absolute length of frame */ + wsctx->nToRead = wsctx->header.headerLen + wsctx->header.payloadLen; + + /* update write position for next bytes */ + wsctx->writePos = wsctx->codeBufDecode + wsctx->nReadRaw; + + /* set payload pointer just after header */ + wsctx->readPos = (unsigned char *)(wsctx->codeBufDecode + wsctx->header.headerLen); + + rfbLog("header complete: state=%d flen=%d writeTo=%p\n", wsctx->hybiDecodeState, wsctx->nToRead, wsctx->writePos); + + return WS_HYBI_STATE_DATA_NEEDED; +} + +static int +hybiWsFrameComplete(ws_ctx_t *wsctx) +{ + return wsctx != NULL && hybiRemaining(wsctx) == 0; +} + +static char * +hybiPayloadStart(ws_ctx_t *wsctx) +{ + return wsctx->codeBufDecode + wsctx->header.headerLen; +} + + +/** + * Read the remaining payload bytes from associated raw socket. + * + * - try to read remaining bytes from socket + * - unmask all multiples of 4 + * - if frame incomplete but some bytes are left, these are copied to + * the carry buffer + * - if opcode is TEXT: Base64-decode all unmasked received bytes + * - set state for reading decoded data + * - reset write position to begin of buffer (+ header) + * --> before we retrieve more data we let the caller clear all bytes + * from the reception buffer + * - execute return data routine + * + * Sets errno corresponding to what it gets from the underlying + * socket or EIO if some internal sanity check fails. + * + * @param[in] cl client ptr with raw socket reference + * @param[out] dst destination buffer + * @param[in] len size of destination buffer + * @param[out] sockRet emulated recv return value + * @return next hybi decode state + */ +static int +hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) +{ + int n; + int i; + int toReturn; + int toDecode; + int bufsize; + int nextRead; + unsigned char *data; + uint32_t *data32; + + /* if data was carried over, copy to start of buffer */ + memcpy(wsctx->writePos, wsctx->carryBuf, wsctx->carrylen); + wsctx->writePos += wsctx->carrylen; + + /* -1 accounts for potential '\0' terminator for base64 decoding */ + bufsize = wsctx->codeBufDecode + ARRAYSIZE(wsctx->codeBufDecode) - wsctx->writePos - 1; + if (hybiRemaining(wsctx) > bufsize) { + nextRead = bufsize; + } else { + nextRead = hybiRemaining(wsctx); + } + + rfbLog("calling read with buf=%p and len=%d (decodebuf=%p headerLen=%d)\n", wsctx->writePos, nextRead, wsctx->codeBufDecode, wsctx->header.headerLen); + + if (wsctx->nReadRaw < wsctx->nToRead) { + /* decode more data */ + //if (-1 == (n = ws_read(cl, wsctx->writePos, nextRead))) { + if (-1 == (n = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, wsctx->writePos, nextRead))) { + int olderrno = errno; + rfbErr("%s: read; %m", __func__); + errno = olderrno; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } else if (n == 0) { + *sockRet = 0; + return WS_HYBI_STATE_ERR; + } + wsctx->nReadRaw += n; + rfbLog("read %d bytes from socket; nRead=%d\n", n, wsctx->nReadRaw); + } else { + n = 0; + } + + wsctx->writePos += n; + + if (wsctx->nReadRaw >= wsctx->nToRead) { + if (wsctx->nReadRaw > wsctx->nToRead) { + rfbErr("%s: internal error, read past websocket frame", __func__); + errno=EIO; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } + } + + toDecode = wsctx->writePos - hybiPayloadStart(wsctx); + rfbLog("toDecode=%d from n=%d carrylen=%d headerLen=%d\n", toDecode, n, wsctx->carrylen, wsctx->header.headerLen); + if (toDecode < 0) { + rfbErr("%s: internal error; negative number of bytes to decode: %d", __func__, toDecode); + errno=EIO; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } + + /* for a possible base64 decoding, we decode multiples of 4 bytes until + * the whole frame is received and carry over any remaining bytes in the carry buf*/ + data = (unsigned char *)hybiPayloadStart(wsctx); + data32= (uint32_t *)data; + + for (i = 0; i < (toDecode >> 2); i++) { + data32[i] ^= wsctx->header.mask.u; + } + rfbLog("mask decoding; i=%d toDecode=%d\n", i, toDecode); + + if (wsctx->hybiDecodeState == WS_HYBI_STATE_FRAME_COMPLETE) { + /* process the remaining bytes (if any) */ + for (i*=4; i < toDecode; i++) { + data[i] ^= wsctx->header.mask.c[i % 4]; + } + + /* all data is here, no carrying */ + wsctx->carrylen = 0; + } else { + /* carry over remaining, non-multiple-of-four bytes */ + wsctx->carrylen = toDecode - (i * 4); + if (wsctx->carrylen < 0 || wsctx->carrylen > ARRAYSIZE(wsctx->carryBuf)) { + syslog(LOG_ERR, "%s: internal error, invalid carry over size: carrylen=%d, toDecode=%d, i=%d", __func__, wsctx->carrylen, toDecode, i); + *sockRet = -1; + errno = EIO; + return WS_HYBI_STATE_ERR; + } + rfbLog("carrying over %d bytes from %p to %p\n", wsctx->carrylen, wsctx->writePos + (i * 4), wsctx->carryBuf); + memcpy(wsctx->carryBuf, data + (i * 4), wsctx->carrylen); + } + + toReturn = toDecode - wsctx->carrylen; + + switch (wsctx->header.opcode) { + case WS_OPCODE_CLOSE: + + /* this data is not returned as payload data */ + if (hybiWsFrameComplete(wsctx)) { + rfbLog("got closure, reason %d\n", WS_NTOH16(((uint16_t *)data)[0])); + rfbLog("got close cmd, reason %d\n", WS_NTOH16(((uint16_t *)data)[0])); + errno = ECONNRESET; + *sockRet = -1; + return WS_HYBI_STATE_FRAME_COMPLETE; + } else { + rfbErr("%s: close reason with long frame not supported", __func__); + errno = EIO; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } + break; + case WS_OPCODE_TEXT_FRAME: + data[toReturn] = '\0'; + rfbLog("Initiate Base64 decoding in %p with max size %d and '\\0' at %p\n", data, bufsize, data + toReturn); + if (-1 == (wsctx->readlen = b64_pton((char *)data, data, bufsize))) { + syslog(LOG_ERR, "Base64 decode error in %s; data=%p bufsize=%d", __func__, data, bufsize); + rfbErr("%s: Base64 decode error; %m\n", __func__); + } + wsctx->writePos = hybiPayloadStart(wsctx); + break; + case WS_OPCODE_BINARY_FRAME: + wsctx->readlen = toReturn; + wsctx->writePos = hybiPayloadStart(wsctx); + rfbLog("set readlen=%d writePos=%p\n", wsctx->readlen, wsctx->writePos); + break; + default: + rfbErr("%s: unhandled opcode %d, b0: %02x, b1: %02x\n", __func__, (int)wsctx->header.opcode, wsctx->header.data->b0, wsctx->header.data->b1); + } + wsctx->readPos = data; + + return hybiReturnData(dst, len, wsctx, sockRet); +} + +/** + * Read function for websocket-socket emulation. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-------+-+-------------+-------------------------------+ + * |F|R|R|R| opcode|M| Payload len | Extended payload length | + * |I|S|S|S| (4) |A| (7) | (16/64) | + * |N|V|V|V| |S| | (if payload len==126/127) | + * | |1|2|3| |K| | | + * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + * | Extended payload length continued, if payload len == 127 | + * + - - - - - - - - - - - - - - - +-------------------------------+ + * | |Masking-key, if MASK set to 1 | + * +-------------------------------+-------------------------------+ + * | Masking-key (continued) | Payload Data | + * +-------------------------------- - - - - - - - - - - - - - - - + + * : Payload Data continued ... : + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * | Payload Data continued ... | + * +---------------------------------------------------------------+ + * + * Using the decode buffer, this function: + * - reads the complete header from the underlying socket + * - reads any remaining data bytes + * - unmasks the payload data using the provided mask + * - decodes Base64 encoded text data + * - copies len bytes of decoded payload data into dst + * + * Emulates a read call on a socket. + */ +int +webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len) +{ + int result = -1; + /* int fin; */ /* not used atm */ + + /* rfbLog(" <== %s[%d]: %d cl: %p, wsctx: %p-%p (%d)\n", __func__, gettid(), len, cl, wsctx, (char *)wsctx + sizeof(ws_ctx_t), sizeof(ws_ctx_t)); */ + rfbLog("%s_enter: len=%d; " + "CTX: readlen=%d readPos=%p " + "writeTo=%p " + "state=%d toRead=%d remaining=%d " + " nReadRaw=%d carrylen=%d carryBuf=%p\n", + __func__, len, + wsctx->readlen, wsctx->readPos, + wsctx->writePos, + wsctx->hybiDecodeState, wsctx->nToRead, hybiRemaining(wsctx), + wsctx->nReadRaw, wsctx->carrylen, wsctx->carryBuf); + + switch (wsctx->hybiDecodeState){ + case WS_HYBI_STATE_HEADER_PENDING: + wsctx->hybiDecodeState = hybiReadHeader(wsctx, &result); + if (wsctx->hybiDecodeState == WS_HYBI_STATE_ERR) { + goto spor; + } + if (wsctx->hybiDecodeState != WS_HYBI_STATE_HEADER_PENDING) { + + /* when header is complete, try to read some more data */ + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result); + } + break; + case WS_HYBI_STATE_DATA_AVAILABLE: + wsctx->hybiDecodeState = hybiReturnData(dst, len, wsctx, &result); + break; + case WS_HYBI_STATE_DATA_NEEDED: + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result); + break; + case WS_HYBI_STATE_CLOSE_REASON_PENDING: + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result); + break; + default: + /* invalid state */ + rfbErr("%s: called with invalid state %d\n", wsctx->hybiDecodeState); + result = -1; + errno = EIO; + wsctx->hybiDecodeState = WS_HYBI_STATE_ERR; + } + + /* single point of return, if someone has questions :-) */ +spor: + /* rfbLog("%s: ret: %d/%d\n", __func__, result, len); */ + if (wsctx->hybiDecodeState == WS_HYBI_STATE_FRAME_COMPLETE) { + rfbLog("frame received successfully, cleaning up: read=%d hlen=%d plen=%d\n", wsctx->header.nRead, wsctx->header.headerLen, wsctx->header.payloadLen); + /* frame finished, cleanup state */ + hybiDecodeCleanup(wsctx); + } else if (wsctx->hybiDecodeState == WS_HYBI_STATE_ERR) { + hybiDecodeCleanup(wsctx); + } + rfbLog("%s_exit: len=%d; " + "CTX: readlen=%d readPos=%p " + "writePos=%p " + "state=%d toRead=%d remaining=%d " + "nRead=%d carrylen=%d carryBuf=%p " + "result=%d\n", + __func__, len, + wsctx->readlen, wsctx->readPos, + wsctx->writePos, + wsctx->hybiDecodeState, wsctx->nToRead, hybiRemaining(wsctx), + wsctx->nReadRaw, wsctx->carrylen, wsctx->carryBuf, + result); + return result; +} -- cgit v1.2.3 From f19d6ee225ff35eb54ca06927a921c98ff721adc Mon Sep 17 00:00:00 2001 From: Andreas Weigel Date: Mon, 20 Feb 2017 11:24:18 +0100 Subject: add ws_decode tests modify automake to include ws_decode test add python frame generator for decode tests modify configure to only include ws_decode test if preconditions are fulfilled --- .gitignore | 3 + libvncserver/websockets.c | 22 +----- libvncserver/ws_decode.c | 56 ++++++++----- libvncserver/ws_decode.h | 4 +- test/wsmaketestframe.py | 121 ++++++++++++++++++++++++++++ test/wstest.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 360 insertions(+), 41 deletions(-) create mode 100755 test/wsmaketestframe.py create mode 100644 test/wstest.c (limited to 'libvncserver/ws_decode.c') diff --git a/.gitignore b/.gitignore index fccd7af..a24f81a 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,9 @@ test/cargstest test/copyrecttest test/cursortest test/encodingstest +test/wstest +test/wsmaketestframe.py +test/wstestdata.in /test/tjbench /test/tjunittest vncterm/LinuxVNC diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c index 364225b..ab9cabb 100644 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -100,8 +100,7 @@ void webSocketsGenMd5(char * target, char *key1, char *key2, char *key3); static int webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst); -static int ws_read(void *cl, char *buf, int len); -static int ws_peek(void *cl, char *buf, int len); +static int ws_read(void *cl, char *buf, size_t len); static int @@ -345,7 +344,6 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) wsctx->encode = webSocketsEncodeHybi; wsctx->decode = webSocketsDecodeHybi; wsctx->ctxInfo.readFunc = ws_read; - wsctx->ctxInfo.peekFunc = ws_peek; wsctx->base64 = base64; hybiDecodeCleanup(wsctx); cl->wsctx = (wsCtx *)wsctx; @@ -403,7 +401,7 @@ webSocketsGenMd5(char * target, char *key1, char *key2, char *key3) } static int -ws_read(void *ctxPtr, char *buf, int len) +ws_read(void *ctxPtr, char *buf, size_t len) { int n; rfbClientPtr cl = ctxPtr; @@ -415,22 +413,6 @@ ws_read(void *ctxPtr, char *buf, int len) return n; } -static int -ws_peek(void *ctxPtr, char *buf, int len) -{ - int n; - rfbClientPtr cl = ctxPtr; - if (cl->sslctx) { - n = rfbssl_peek(cl, buf, len); - } else { - while (-1 == (n = recv(cl->sock, buf, len, MSG_PEEK))) { - if (errno != EAGAIN) - break; - } - } - return n; -} - static int webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) { diff --git a/libvncserver/ws_decode.c b/libvncserver/ws_decode.c index e74a33c..3bd17f4 100644 --- a/libvncserver/ws_decode.c +++ b/libvncserver/ws_decode.c @@ -1,11 +1,12 @@ #include "ws_decode.h" -#include #include #include #define WS_HYBI_MASK_LEN 4 - +#define WS_HYBI_HEADER_LEN_SHORT 2 + WS_HYBI_MASK_LEN +#define WS_HYBI_HEADER_LEN_EXTENDED 4 + WS_HYBI_MASK_LEN +#define WS_HYBI_HEADER_LEN_LONG 10 + WS_HYBI_MASK_LEN static int hybiRemaining(ws_ctx_t *wsctx) @@ -66,8 +67,12 @@ hybiReturnData(char *dst, int len, ws_ctx_t *wsctx, int *nWritten) } } rfbLog("after copy: readPos=%p, readLen=%d\n", wsctx->readPos, wsctx->readlen); - } else if (wsctx->hybiDecodeState == WS_HYBI_STATE_CLOSE_REASON_PENDING) { - nextState = WS_HYBI_STATE_CLOSE_REASON_PENDING; + } else { + /* it may happen that we read some bytes but could not decode them, + * in that case, set errno to EAGAIN and return -1 */ + nextState = wsctx->hybiDecodeState; + errno = EAGAIN; + *nWritten = -1; } return nextState; } @@ -98,7 +103,7 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet) if (-1 == ret) { /* save errno because rfbErr() will tamper it */ int olderrno = errno; - rfbErr("%s: peek; %m\n", __func__); + rfbErr("%s: read; %s\n", __func__, strerror(errno)); errno = olderrno; *sockRet = -1; } else { @@ -131,22 +136,22 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet) * close the connection upon receiving a frame with the MASK bit set to 0. **/ if (!(wsctx->header.data->b1 & 0x80)) { - rfbErr("%s: got frame without mask ret=%d\n", __func__, ret); - syslog(LOG_ERR, "%s: got frame without mask; ret=%d\n", __func__, ret); - errno = EIO; + rfbErr("%s: got frame without mask; ret=%d\n", __func__, ret); + errno = EPROTO; *sockRet = -1; return WS_HYBI_STATE_ERR; } + if (wsctx->header.payloadLen < 126 && wsctx->nReadRaw >= 6) { - wsctx->header.headerLen = 2 + WS_HYBI_MASK_LEN; + wsctx->header.headerLen = WS_HYBI_HEADER_LEN_SHORT; wsctx->header.mask = wsctx->header.data->u.m; } else if (wsctx->header.payloadLen == 126 && 8 <= wsctx->nReadRaw) { - wsctx->header.headerLen = 4 + WS_HYBI_MASK_LEN; + wsctx->header.headerLen = WS_HYBI_HEADER_LEN_EXTENDED; wsctx->header.payloadLen = WS_NTOH16(wsctx->header.data->u.s16.l16); wsctx->header.mask = wsctx->header.data->u.s16.m16; } else if (wsctx->header.payloadLen == 127 && 14 <= wsctx->nReadRaw) { - wsctx->header.headerLen = 10 + WS_HYBI_MASK_LEN; + wsctx->header.headerLen = WS_HYBI_HEADER_LEN_LONG; wsctx->header.payloadLen = WS_NTOH64(wsctx->header.data->u.s64.l64); wsctx->header.mask = wsctx->header.data->u.s64.m64; } else { @@ -157,6 +162,19 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet) return WS_HYBI_STATE_HEADER_PENDING; } + /* while RFC 6455 mandates that lengths MUST be encoded with the minimum + * number of bytes, it does not specify for the server how to react on + * 'wrongly' encoded frames --- this implementation rejects them*/ + if ((wsctx->header.headerLen > WS_HYBI_HEADER_LEN_SHORT + && wsctx->header.payloadLen < 126) + || (wsctx->header.headerLen > WS_HYBI_HEADER_LEN_EXTENDED + && wsctx->header.payloadLen < 65536)) { + rfbErr("%s: invalid length field; headerLen=%d payloadLen=%llu\n", __func__, wsctx->header.headerLen, wsctx->header.payloadLen); + errno = EPROTO; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } + /* absolute length of frame */ wsctx->nToRead = wsctx->header.headerLen + wsctx->header.payloadLen; @@ -238,7 +256,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) //if (-1 == (n = ws_read(cl, wsctx->writePos, nextRead))) { if (-1 == (n = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, wsctx->writePos, nextRead))) { int olderrno = errno; - rfbErr("%s: read; %m", __func__); + rfbErr("%s: read; %s", __func__, strerror(errno)); errno = olderrno; *sockRet = -1; return WS_HYBI_STATE_ERR; @@ -260,6 +278,8 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) errno=EIO; *sockRet = -1; return WS_HYBI_STATE_ERR; + } else { + wsctx->hybiDecodeState = WS_HYBI_STATE_FRAME_COMPLETE; } } @@ -294,7 +314,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) /* carry over remaining, non-multiple-of-four bytes */ wsctx->carrylen = toDecode - (i * 4); if (wsctx->carrylen < 0 || wsctx->carrylen > ARRAYSIZE(wsctx->carryBuf)) { - syslog(LOG_ERR, "%s: internal error, invalid carry over size: carrylen=%d, toDecode=%d, i=%d", __func__, wsctx->carrylen, toDecode, i); + rfbErr("%s: internal error, invalid carry over size: carrylen=%d, toDecode=%d, i=%d", __func__, wsctx->carrylen, toDecode, i); *sockRet = -1; errno = EIO; return WS_HYBI_STATE_ERR; @@ -310,7 +330,6 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) /* this data is not returned as payload data */ if (hybiWsFrameComplete(wsctx)) { - rfbLog("got closure, reason %d\n", WS_NTOH16(((uint16_t *)data)[0])); rfbLog("got close cmd, reason %d\n", WS_NTOH16(((uint16_t *)data)[0])); errno = ECONNRESET; *sockRet = -1; @@ -326,8 +345,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) data[toReturn] = '\0'; rfbLog("Initiate Base64 decoding in %p with max size %d and '\\0' at %p\n", data, bufsize, data + toReturn); if (-1 == (wsctx->readlen = b64_pton((char *)data, data, bufsize))) { - syslog(LOG_ERR, "Base64 decode error in %s; data=%p bufsize=%d", __func__, data, bufsize); - rfbErr("%s: Base64 decode error; %m\n", __func__); + rfbErr("%s: Base64 decode error; %s\n", __func__, strerror(errno)); } wsctx->writePos = hybiPayloadStart(wsctx); break; @@ -437,12 +455,14 @@ spor: "writePos=%p " "state=%d toRead=%d remaining=%d " "nRead=%d carrylen=%d carryBuf=%p " - "result=%d\n", + "result=%d " + "errno=%d\n", __func__, len, wsctx->readlen, wsctx->readPos, wsctx->writePos, wsctx->hybiDecodeState, wsctx->nToRead, hybiRemaining(wsctx), wsctx->nReadRaw, wsctx->carrylen, wsctx->carryBuf, - result); + result, + errno); return result; } diff --git a/libvncserver/ws_decode.h b/libvncserver/ws_decode.h index e75c4d1..fac3c68 100644 --- a/libvncserver/ws_decode.h +++ b/libvncserver/ws_decode.h @@ -50,13 +50,11 @@ typedef struct ws_ctx_s ws_ctx_t; typedef int (*wsEncodeFunc)(rfbClientPtr cl, const char *src, int len, char **dst); typedef int (*wsDecodeFunc)(ws_ctx_t *wsctx, char *dst, int len); -typedef int (*wsReadFunc)(void *ctx, char *dst, int len); -typedef int (*wsPeekFunc)(void *ctx, char *dst, int len); +typedef int (*wsReadFunc)(void *ctx, char *dst, size_t len); typedef struct ctxInfo_s{ void *ctxPtr; wsReadFunc readFunc; - wsPeekFunc peekFunc; } ctxInfo_t; enum { diff --git a/test/wsmaketestframe.py b/test/wsmaketestframe.py new file mode 100755 index 0000000..d0053a2 --- /dev/null +++ b/test/wsmaketestframe.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# Copyright (C)2017 Andreas Weigel. All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import websockets +import base64 +import errno + +def add_field(s, name, value, first=False): + deli = ",\n\t\t" + if first: + deli = "\t\t" + s += "{2}.{0}={1}".format(name, value, deli) + return s + + +class Testframe(): + def __init__(self, frame, descr, retbytes=[], modify_bytes={}, experrno=0, mask=True): + self.frame = frame + self.descr = descr + self.retbytes = retbytes + self.modify_bytes = modify_bytes + self.experrno = experrno + self.b64 = True if frame.opcode == 1 else False + self.mask = mask + + def to_carray_initializer(self, buf): + values = [] + for i in range(len(buf)): + values.append("0X{0:02X}".format(buf[i])) + + if self.modify_bytes != {}: + for k in self.modify_bytes: + values[k] = "0X{0:02X}".format(self.modify_bytes[k]) + + return "{{{0}}}".format(", ".join(values)) + + + def set_frame_buf(self, buf): + self.frame_carray = self.to_carray_initializer(buf) + self.framelen = len(buf) + + def __str__(self): + #print("processing frame: {0}".format(self.descr)) + the_frame = self.frame + if self.b64: + olddata = self.frame.data + newdata = base64.b64encode(self.frame.data) + #print("converting\n{0}\nto{1}\n".format(olddata, newdata)) + the_frame = websockets.framing.Frame(self.frame.fin, self.frame.opcode, base64.b64encode(olddata)) + + websockets.framing.write_frame(the_frame, self.set_frame_buf, self.mask) + s = "\t{\n" + s = add_field(s, "frame", "{0}".format(self.frame_carray), True) + s = add_field(s, "expectedDecodeBuf", self.to_carray_initializer(self.frame.data)) + s = add_field(s, "frame_len", self.framelen) + s = add_field(s, "raw_payload_len", len(self.frame.data)) + s = add_field(s, "expected_errno", self.experrno) + s = add_field(s, "descr", "\"{0}\"".format(self.descr)) + s = add_field(s, "ret_bytes", "{{{0}}}".format(", ".join(self.retbytes))) + s = add_field(s, "ret_bytes_len", len(self.retbytes)) + s = add_field(s, "i", "0") + s = add_field(s, "simulate_sock_malfunction_at", "0") + s = add_field(s, "errno_val", "0") + s = add_field(s, "close_sock_at", "0") + s += "\n\t}" + return s + +### create test frames +flist = [] +### standard text frames with different lengths +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Testit", encoding="utf-8")), "Short valid text frame", {})) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Frame2 does contain much more text and even goes beyond the 126 byte len field. Frame2 does contain much more text and even goes beyond the 126 byte len field.", encoding="utf-8")), + "Mid-long valid text frame", {})) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray([(x % 26) + 65 for x in range(100000)])), "100k text frame (ABC..YZABC..)", {})) + +### standard binary frames with different lengths +flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray("Testit", encoding="utf-8")), "Short valid binary frame", {})) +flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray("Frame2 does contain much more text and even goes beyond the 126 byte len field. Frame2 does contain much more text and even goes beyond the 126 byte len field.", encoding="utf-8")), + "Mid-long valid binary frame", {})) +flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray([(x % 26) + 65 for x in range(100000)])), "100k binary frame (ABC..YZABC..)", {})) + +### some conn reset frames, one with no close message, one with close message (the latter should cause an error) +flist.append(Testframe(websockets.framing.Frame(1, 8, bytearray(list([0x03, 0xEB]))), "Close frame (Reason 1003)", {}, experrno=errno.ECONNRESET)) +flist.append(Testframe(websockets.framing.Frame(1, 8, bytearray(list([0x03, 0xEB])) + bytearray("I'm a close reason", encoding="utf-8")), "Close frame (Reason 1003) and msg", {}, experrno=errno.EIO)) + +### invalid header values +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Testit", encoding="utf-8")), "Invalid frame: Wrong masking", {}, experrno=errno.EPROTO, mask=False)) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("..Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 16 bit len field", {}, experrno=errno.EPROTO, modify_bytes={ 1: 0xFE, 2: 0x00, 3: 0x0F})) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("........Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 64 bit len field", {}, experrno=errno.EPROTO, modify_bytes={ 1: 0xFF, 2: 0x00, 3: 0x00, 4: 0x00, 5: 0x00, 6: 0x80, 7: 0x40})) + +s = "struct ws_frame_test tests[] = {\n" +for i in range(len(flist)): + s += flist[i].__str__() + if (i + 1 < len(flist)): + s += "," + s += "\n" +s += "};\n" + +with open("wstestdata.in", "w") as cdatafile: + cdatafile.write(s) diff --git a/test/wstest.c b/test/wstest.c new file mode 100644 index 0000000..30324cb --- /dev/null +++ b/test/wstest.c @@ -0,0 +1,195 @@ +/* + * Copyright (C)2017 Andreas Weigel. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 + +#define TEST_BUF_SIZE B64LEN(131072) + WSHLENMAX +#define RND_SEED 100 +#define WS_TMP_LOG "ws_tmp.log" + +enum { + OK, + FAIL_DATA, + FAIL_ERRNO, + FAIL_CLOSED, +}; + +const char *result_descr[] = { + "", + "Data buffers do not match", + "Wrong errno", + "Wrongly reported closed socket", + "Internal test error" +}; + +struct ws_frame_test { + char frame[TEST_BUF_SIZE]; + char *pos; + char expectedDecodeBuf[TEST_BUF_SIZE]; + uint64_t frame_len; + uint64_t raw_payload_len; + int expected_errno; + const char *descr; + int ret_bytes[16]; + int ret_bytes_len; + int i; + int simulate_sock_malfunction_at; + int errno_val; + int close_sock_at; +}; + +char el_log[1000000]; +char *el_pos; + +static void logtest(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + size_t left = el_log + sizeof(el_log) - el_pos; + size_t off = vsnprintf(el_pos, left, fmt, args); + el_pos += off; + va_end(args); +} + +static int emu_read(void *ctx, char *dst, size_t len); + +static int emu_read(void *ctx, char *dst, size_t len) +{ + struct ws_frame_test *ft = (struct ws_frame_test *)ctx; + ssize_t nret; + int r; + ssize_t modu; + + rfbLog("emu_read called with dst=%p and len=%lu\n", dst, len); + if (ft->simulate_sock_malfunction_at > 0 && ft->simulate_sock_malfunction_at == ft->i) { + rfbLog("simulating IO error with errno=%d\n", ft->errno_val); + errno = ft->errno_val; + return -1; + } + + /* return something */ + r = rand(); + modu = (ft->frame + ft->frame_len) - ft->pos; + rfbLog("r=%d modu=%ld frame=%p pos=%p\n", r, modu, ft->frame, ft->pos); + nret = (r % modu) + 1; + nret = nret > len ? len : nret; + + rfbLog("copy and return %ld bytes\n", nret); + memcpy(dst, ft->pos, nret); + ft->pos += nret; + rfbLog("leaving %s; pos=%p framebuf=%p nret=%ld\n", __func__, ft->pos, ft->frame, nret); + return nret; +} + +static uint64_t run_test(struct ws_frame_test *ft, ws_ctx_t *ctx) +{ + uint64_t nleft = ft->raw_payload_len; + char dstbuf[ft->raw_payload_len]; + char *dst = dstbuf; + ssize_t n; + + ft->pos = ft->frame; + + ctx->ctxInfo.ctxPtr = (void *)ft; + + while (nleft > 0) { + rfbLog("calling ws_decode with dst=%p, len=%lu\n", dst, nleft); + n = ctx->decode(ctx, dst, nleft); + rfbLog("read n=%ld\n", n); + if (n == 0) { + if (ft->close_sock_at > 0) { + return OK; + } else { + return FAIL_CLOSED; + } + } else if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* ok, just call again */ + } else { + if (ft->expected_errno == errno) { + rfbLog("errno=%d as expected\n", errno); + return OK; + } else { + rfbLog("errno=%d != expected(%d)\n", errno, ft->expected_errno); + return FAIL_ERRNO; + } + } + } else { + nleft -= n; + dst += n; + rfbLog("read n=%ld from decode; dst=%p, nleft=%lu\n", n, dst, nleft); + } + } + + if (memcmp(ft->expectedDecodeBuf, dstbuf, ft->raw_payload_len) != 0) { + ft->expectedDecodeBuf[ft->raw_payload_len] = '\0'; + dstbuf[ft->raw_payload_len] = '\0'; + rfbLog("decoded result not equal:\nexpected:\n%s\ngot\n%s\n\n", ft->expectedDecodeBuf, dstbuf); + return FAIL_DATA; + } + + return OK; +} + +#include "wstestdata.in" + +int main() +{ + ws_ctx_t ctx; + int retall= 0; + srand(RND_SEED); + + for (int i = 0; i < ARRAYSIZE(tests); i++) { + int ret; + + el_pos = el_log; + rfbLog = logtest; + rfbErr = logtest; + + hybiDecodeCleanup(&ctx); + ctx.decode = webSocketsDecodeHybi; + ctx.version = WEBSOCKETS_VERSION_HYBI; + + ctx.ctxInfo.readFunc = emu_read; + + ret = run_test(&tests[i], &ctx); + printf("%s: \"%s\"\n", ret == 0 ? "PASS" : "FAIL", tests[i].descr); + if (ret != 0) { + *el_pos = '\0'; + printf("%s", el_log); + retall = -1; + } + } + return retall; +} + +#endif -- cgit v1.2.3 From 826e0f9e39a49ae3598f8709218180f835af269b Mon Sep 17 00:00:00 2001 From: Andreas Weigel Date: Mon, 20 Feb 2017 15:13:03 +0100 Subject: add generation wstest to cmake add wstestdata.c, because the python data generation script has too many dependencies remove some redundance from jpeg test creation add support for decoding close messages --- .gitignore | 2 - CMakeLists.txt | 41 +++++++++++++----- libvncserver/ws_decode.c | 44 +++++++++++-------- libvncserver/ws_decode.h | 7 --- test/wsmaketestframe.py | 44 ++++++++++--------- test/wstest.c | 23 +++++++--- test/wstestdata.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 207 insertions(+), 64 deletions(-) create mode 100644 test/wstestdata.c (limited to 'libvncserver/ws_decode.c') diff --git a/.gitignore b/.gitignore index a24f81a..03bdf0f 100644 --- a/.gitignore +++ b/.gitignore @@ -67,8 +67,6 @@ test/copyrecttest test/cursortest test/encodingstest test/wstest -test/wsmaketestframe.py -test/wstestdata.in /test/tjbench /test/tjunittest vncterm/LinuxVNC diff --git a/CMakeLists.txt b/CMakeLists.txt index cf6017d..8c6da06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,6 @@ option(WITH_IPv6 "Enable IPv6 Support" ON) option(WITH_WEBSOCKETS "Build with websockets support" ON) - if(WITH_ZLIB) find_package(ZLIB) endif(WITH_ZLIB) @@ -387,6 +386,7 @@ if(LIBVNCSERVER_WITH_WEBSOCKETS) set(LIBVNCSERVER_SOURCES ${LIBVNCSERVER_SOURCES} ${LIBVNCSERVER_DIR}/websockets.c + ${LIBVNCSERVER_DIR}/ws_decode.c ${WSSRCS} ) endif(LIBVNCSERVER_WITH_WEBSOCKETS) @@ -500,11 +500,9 @@ foreach(e ${LIBVNCCLIENT_EXAMPLES}) target_link_libraries(client_examples_${e} vncclient ${CMAKE_THREAD_LIBS_INIT} ${SDL_LIBRARY} ${FFMPEG_LIBRARIES}) endforeach(e ${LIBVNCCLIENT_EXAMPLES}) - # # them tests # - if(UNIX) set(ADDITIONAL_TEST_LIBS m) endif(UNIX) @@ -512,18 +510,41 @@ endif(UNIX) set(SIMPLETESTS cargstest copyrecttest + wstest ) -if(CMAKE_USE_PTHREADS_INIT) - set(SIMPLETESTS - ${SIMPLETESTS} - encodingstest +add_test(NAME cargs COMMAND test_cargstest) +add_test(NAME websockets_decode COMMAND test_wstest) + +if(CMAKE_USE_PTHREADS_INI) + list(APPEND SIMPLETESTS encodingstest) +endif(CMAKE_USE_PTHREADS_INI) + +if(FOUND_LIBJPEG_TURBO) + list(APPEND SIMPLETESTS tjunittest tjbench) + set(tjunittest_add_src + ${TESTS_DIR}/tjutil.c + ${TESTS_DIR}/tjutil.h + ${COMMON_DIR}/turbojpeg.c + ${COMMON_DIR}/turbojpeg.h ) -endif(CMAKE_USE_PTHREADS_INIT) + + set(tjbench_add_src + ${TESTS_DIR}/tjbench.c + ${TESTS_DIR}/tjutil.c + ${TESTS_DIR}/tjutil.h + ${TESTS_DIR}/bmp.c + ${TESTS_DIR}/bmp.h + ${COMMON_DIR}/turbojpeg.c + ${COMMON_DIR}/turbojpeg.h + ) + + add_test(NAME turbojpeg COMMAND test_tjunittest) +endif(FOUND_LIBJPEG_TURBO) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) foreach(t ${SIMPLETESTS}) - add_executable(test_${t} ${TESTS_DIR}/${t}.c) + add_executable(test_${t} ${TESTS_DIR}/${t}.c ${${t}_add_src}) set_target_properties(test_${t} PROPERTIES OUTPUT_NAME ${t}) set_target_properties(test_${t} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) target_link_libraries(test_${t} vncserver vncclient ${ADDITIONAL_TEST_LIBS}) @@ -561,8 +582,6 @@ if(FOUND_LIBJPEG_TURBO) add_test(NAME turbojpeg COMMAND test_tjunittest) endif(FOUND_LIBJPEG_TURBO) - - # # this gets the libraries needed by TARGET in "-libx -liby ..." form # diff --git a/libvncserver/ws_decode.c b/libvncserver/ws_decode.c index 3bd17f4..472a44a 100644 --- a/libvncserver/ws_decode.c +++ b/libvncserver/ws_decode.c @@ -85,11 +85,12 @@ hybiReturnData(char *dst, int len, ws_ctx_t *wsctx, int *nWritten) * * @param[in] cl client ptr with ptr to raw socket and ws_ctx_t ptr * @param[out] sockRet emulated recv return value + * @param[out] nPayload number of payload bytes already read * @return next hybi decoding state; WS_HYBI_STATE_HEADER_PENDING indicates * that the header was not received completely. */ static int -hybiReadHeader(ws_ctx_t *wsctx, int *sockRet) +hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) { int ret; char *headerDst = wsctx->codeBufDecode + wsctx->nReadRaw; @@ -184,7 +185,8 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet) /* set payload pointer just after header */ wsctx->readPos = (unsigned char *)(wsctx->codeBufDecode + wsctx->header.headerLen); - rfbLog("header complete: state=%d flen=%d writeTo=%p\n", wsctx->hybiDecodeState, wsctx->nToRead, wsctx->writePos); + *nPayload = wsctx->nReadRaw - wsctx->header.headerLen; + rfbLog("header complete: state=%d flen=%d writeTo=%p nPayload=%d\n", wsctx->hybiDecodeState, wsctx->nToRead, wsctx->writePos, *nPayload); return WS_HYBI_STATE_DATA_NEEDED; } @@ -217,21 +219,24 @@ hybiPayloadStart(ws_ctx_t *wsctx) * - execute return data routine * * Sets errno corresponding to what it gets from the underlying - * socket or EIO if some internal sanity check fails. + * socket or EPROTO if some invalid data is in the received frame + * or ECONNRESET if a close reason + message is received. EIO is used if + * an internal sanity check fails. * * @param[in] cl client ptr with raw socket reference * @param[out] dst destination buffer * @param[in] len size of destination buffer * @param[out] sockRet emulated recv return value + * @param[in] nInBuf number of undecoded bytes before writePos from header read * @return next hybi decode state */ static int -hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) +hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) { int n; int i; - int toReturn; - int toDecode; + int toReturn; /* number of data bytes to return */ + int toDecode; /* number of bytes to decode starting at wsctx->writePos */ int bufsize; int nextRead; unsigned char *data; @@ -253,7 +258,6 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) if (wsctx->nReadRaw < wsctx->nToRead) { /* decode more data */ - //if (-1 == (n = ws_read(cl, wsctx->writePos, nextRead))) { if (-1 == (n = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, wsctx->writePos, nextRead))) { int olderrno = errno; rfbErr("%s: read; %s", __func__, strerror(errno)); @@ -283,7 +287,9 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) } } - toDecode = wsctx->writePos - hybiPayloadStart(wsctx); + /* number of not yet unmasked payload bytes: what we read here + what was + * carried over + what was read with the header */ + toDecode = n + wsctx->carrylen + nInBuf; rfbLog("toDecode=%d from n=%d carrylen=%d headerLen=%d\n", toDecode, n, wsctx->carrylen, wsctx->header.headerLen); if (toDecode < 0) { rfbErr("%s: internal error; negative number of bytes to decode: %d", __func__, toDecode); @@ -294,7 +300,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) /* for a possible base64 decoding, we decode multiples of 4 bytes until * the whole frame is received and carry over any remaining bytes in the carry buf*/ - data = (unsigned char *)hybiPayloadStart(wsctx); + data = (unsigned char *)(wsctx->writePos - toDecode); data32= (uint32_t *)data; for (i = 0; i < (toDecode >> 2); i++) { @@ -321,24 +327,25 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet) } rfbLog("carrying over %d bytes from %p to %p\n", wsctx->carrylen, wsctx->writePos + (i * 4), wsctx->carryBuf); memcpy(wsctx->carryBuf, data + (i * 4), wsctx->carrylen); + wsctx->writePos -= wsctx->carrylen; } toReturn = toDecode - wsctx->carrylen; switch (wsctx->header.opcode) { case WS_OPCODE_CLOSE: - /* this data is not returned as payload data */ if (hybiWsFrameComplete(wsctx)) { - rfbLog("got close cmd, reason %d\n", WS_NTOH16(((uint16_t *)data)[0])); + *(wsctx->writePos) = '\0'; + rfbLog("got close cmd %d, reason %d: %s\n", (int)(wsctx->writePos - hybiPayloadStart(wsctx)), WS_NTOH16(((uint16_t *)hybiPayloadStart(wsctx))[0]), &hybiPayloadStart(wsctx)[2]); errno = ECONNRESET; *sockRet = -1; return WS_HYBI_STATE_FRAME_COMPLETE; } else { - rfbErr("%s: close reason with long frame not supported", __func__); - errno = EIO; + rfbLog("got close cmd; waiting for %d more bytes to arrive\n", hybiRemaining(wsctx)); *sockRet = -1; - return WS_HYBI_STATE_ERR; + errno = EAGAIN; + return WS_HYBI_STATE_CLOSE_REASON_PENDING; } break; case WS_OPCODE_TEXT_FRAME: @@ -412,25 +419,26 @@ webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len) wsctx->nReadRaw, wsctx->carrylen, wsctx->carryBuf); switch (wsctx->hybiDecodeState){ + int nInBuf; case WS_HYBI_STATE_HEADER_PENDING: - wsctx->hybiDecodeState = hybiReadHeader(wsctx, &result); + wsctx->hybiDecodeState = hybiReadHeader(wsctx, &result, &nInBuf); if (wsctx->hybiDecodeState == WS_HYBI_STATE_ERR) { goto spor; } if (wsctx->hybiDecodeState != WS_HYBI_STATE_HEADER_PENDING) { /* when header is complete, try to read some more data */ - wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result); + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result, nInBuf); } break; case WS_HYBI_STATE_DATA_AVAILABLE: wsctx->hybiDecodeState = hybiReturnData(dst, len, wsctx, &result); break; case WS_HYBI_STATE_DATA_NEEDED: - wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result); + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result, 0); break; case WS_HYBI_STATE_CLOSE_REASON_PENDING: - wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result); + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result, 0); break; default: /* invalid state */ diff --git a/libvncserver/ws_decode.h b/libvncserver/ws_decode.h index fac3c68..0dcbc83 100644 --- a/libvncserver/ws_decode.h +++ b/libvncserver/ws_decode.h @@ -7,13 +7,6 @@ #include /* __b64_ntop */ #endif - - -enum { - WEBSOCKETS_VERSION_HIXIE, - WEBSOCKETS_VERSION_HYBI -}; - #if defined(__APPLE__) #include diff --git a/test/wsmaketestframe.py b/test/wsmaketestframe.py index d0053a2..3412754 100755 --- a/test/wsmaketestframe.py +++ b/test/wsmaketestframe.py @@ -26,6 +26,14 @@ import websockets import base64 import errno +''' + Create websocket frames for the wstest websocket decoding unit test. + + Generates c ws_frame_test structure definitions + included by wstest.c. +''' + + def add_field(s, name, value, first=False): deli = ",\n\t\t" if first: @@ -35,10 +43,9 @@ def add_field(s, name, value, first=False): class Testframe(): - def __init__(self, frame, descr, retbytes=[], modify_bytes={}, experrno=0, mask=True): + def __init__(self, frame, descr, modify_bytes={}, experrno=0, mask=True): self.frame = frame self.descr = descr - self.retbytes = retbytes self.modify_bytes = modify_bytes self.experrno = experrno self.b64 = True if frame.opcode == 1 else False @@ -53,7 +60,7 @@ class Testframe(): for k in self.modify_bytes: values[k] = "0X{0:02X}".format(self.modify_bytes[k]) - return "{{{0}}}".format(", ".join(values)) + return "{{{0}}}".format(",".join(values)) def set_frame_buf(self, buf): @@ -61,14 +68,13 @@ class Testframe(): self.framelen = len(buf) def __str__(self): - #print("processing frame: {0}".format(self.descr)) + print("processing frame: {0}".format(self.descr)) the_frame = self.frame if self.b64: olddata = self.frame.data newdata = base64.b64encode(self.frame.data) #print("converting\n{0}\nto{1}\n".format(olddata, newdata)) the_frame = websockets.framing.Frame(self.frame.fin, self.frame.opcode, base64.b64encode(olddata)) - websockets.framing.write_frame(the_frame, self.set_frame_buf, self.mask) s = "\t{\n" s = add_field(s, "frame", "{0}".format(self.frame_carray), True) @@ -77,8 +83,6 @@ class Testframe(): s = add_field(s, "raw_payload_len", len(self.frame.data)) s = add_field(s, "expected_errno", self.experrno) s = add_field(s, "descr", "\"{0}\"".format(self.descr)) - s = add_field(s, "ret_bytes", "{{{0}}}".format(", ".join(self.retbytes))) - s = add_field(s, "ret_bytes_len", len(self.retbytes)) s = add_field(s, "i", "0") s = add_field(s, "simulate_sock_malfunction_at", "0") s = add_field(s, "errno_val", "0") @@ -89,25 +93,25 @@ class Testframe(): ### create test frames flist = [] ### standard text frames with different lengths -flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Testit", encoding="utf-8")), "Short valid text frame", {})) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Testit", encoding="utf-8")), "Short valid text frame")) flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Frame2 does contain much more text and even goes beyond the 126 byte len field. Frame2 does contain much more text and even goes beyond the 126 byte len field.", encoding="utf-8")), - "Mid-long valid text frame", {})) -flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray([(x % 26) + 65 for x in range(100000)])), "100k text frame (ABC..YZABC..)", {})) + "Mid-long valid text frame")) +#flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray([(x % 26) + 65 for x in range(100000)])), "100k text frame (ABC..YZABC..)")) ### standard binary frames with different lengths -flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray("Testit", encoding="utf-8")), "Short valid binary frame", {})) +flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray("Testit", encoding="utf-8")), "Short valid binary frame")) flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray("Frame2 does contain much more text and even goes beyond the 126 byte len field. Frame2 does contain much more text and even goes beyond the 126 byte len field.", encoding="utf-8")), - "Mid-long valid binary frame", {})) -flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray([(x % 26) + 65 for x in range(100000)])), "100k binary frame (ABC..YZABC..)", {})) + "Mid-long valid binary frame")) +#flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray([(x % 26) + 65 for x in range(100000)])), "100k binary frame (ABC..YZABC..)")) -### some conn reset frames, one with no close message, one with close message (the latter should cause an error) -flist.append(Testframe(websockets.framing.Frame(1, 8, bytearray(list([0x03, 0xEB]))), "Close frame (Reason 1003)", {}, experrno=errno.ECONNRESET)) -flist.append(Testframe(websockets.framing.Frame(1, 8, bytearray(list([0x03, 0xEB])) + bytearray("I'm a close reason", encoding="utf-8")), "Close frame (Reason 1003) and msg", {}, experrno=errno.EIO)) +### some conn reset frames, one with no close message, one with close message +flist.append(Testframe(websockets.framing.Frame(1, 8, bytearray(list([0x03, 0xEB]))), "Close frame (Reason 1003)", experrno=errno.ECONNRESET)) +flist.append(Testframe(websockets.framing.Frame(1, 8, bytearray(list([0x03, 0xEB])) + bytearray("I'm a close reason and much more than that!", encoding="utf-8")), "Close frame (Reason 1003) and msg", experrno=errno.ECONNRESET)) ### invalid header values -flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Testit", encoding="utf-8")), "Invalid frame: Wrong masking", {}, experrno=errno.EPROTO, mask=False)) -flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("..Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 16 bit len field", {}, experrno=errno.EPROTO, modify_bytes={ 1: 0xFE, 2: 0x00, 3: 0x0F})) -flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("........Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 64 bit len field", {}, experrno=errno.EPROTO, modify_bytes={ 1: 0xFF, 2: 0x00, 3: 0x00, 4: 0x00, 5: 0x00, 6: 0x80, 7: 0x40})) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Testit", encoding="utf-8")), "Invalid frame: Wrong masking", experrno=errno.EPROTO, mask=False)) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("..Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 16 bit len field", experrno=errno.EPROTO, modify_bytes={ 1: 0xFE, 2: 0x00, 3: 0x0F})) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("........Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 64 bit len field", experrno=errno.EPROTO, modify_bytes={ 1: 0xFF, 2: 0x00, 3: 0x00, 4: 0x00, 5: 0x00, 6: 0x80, 7: 0x40})) s = "struct ws_frame_test tests[] = {\n" for i in range(len(flist)): @@ -117,5 +121,5 @@ for i in range(len(flist)): s += "\n" s += "};\n" -with open("wstestdata.in", "w") as cdatafile: +with open("wstestdata.c", "w") as cdatafile: cdatafile.write(s) diff --git a/test/wstest.c b/test/wstest.c index 30324cb..4a5ba91 100644 --- a/test/wstest.c +++ b/test/wstest.c @@ -23,6 +23,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#ifndef _WIN32 + #include #include #include @@ -30,11 +32,11 @@ #include #include -#ifndef _WIN32 - +/* incoming data frames should not be larger than that */ #define TEST_BUF_SIZE B64LEN(131072) + WSHLENMAX + +/* seed is fixed deliberately to get reproducible test cases */ #define RND_SEED 100 -#define WS_TMP_LOG "ws_tmp.log" enum { OK, @@ -55,6 +57,7 @@ struct ws_frame_test { char frame[TEST_BUF_SIZE]; char *pos; char expectedDecodeBuf[TEST_BUF_SIZE]; + uint64_t n_compare; uint64_t frame_len; uint64_t raw_payload_len; int expected_errno; @@ -67,6 +70,8 @@ struct ws_frame_test { int close_sock_at; }; +#include "wstestdata.c" + char el_log[1000000]; char *el_pos; @@ -160,15 +165,15 @@ static uint64_t run_test(struct ws_frame_test *ft, ws_ctx_t *ctx) return OK; } -#include "wstestdata.in" int main() { ws_ctx_t ctx; - int retall= 0; + int retall= 0; + int i; srand(RND_SEED); - for (int i = 0; i < ARRAYSIZE(tests); i++) { + for (i = 0; i < ARRAYSIZE(tests); i++) { int ret; el_pos = el_log; @@ -192,4 +197,10 @@ int main() return retall; } +#else + +int main() { + return 0; +} + #endif diff --git a/test/wstestdata.c b/test/wstestdata.c new file mode 100644 index 0000000..628bdb1 --- /dev/null +++ b/test/wstestdata.c @@ -0,0 +1,110 @@ +struct ws_frame_test tests[] = { + { + .frame={0X81,0X88,0XB7,0XDB,0X16,0X16,0XE1,0X9C,0X40,0X6C,0XD3,0X9C,0X7A,0X26}, + .expectedDecodeBuf={0X54,0X65,0X73,0X74,0X69,0X74}, + .frame_len=14, + .raw_payload_len=6, + .expected_errno=0, + .descr="Short valid text frame", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X81,0XFE,0X00,0XD4,0X67,0XFE,0X8A,0X31,0X35,0X90,0XC0,0X59,0X05,0XA9,0XDF,0X48,0X2E,0XB9,0XD8,0X47,0X3D,0XA6,0XC7,0X56,0X3E,0XCC,0XB3,0X44,0X03,0XB9,0XCC,0X41,0X05,0X97,0XC8,0X45,0X03,0XA9,0XC4,0X5E,0X2E,0XB9,0XBB,0X47,0X04,0X93,0XDF,0X56,0X03,0XB9,0XDC,0X05,0X03,0XBD,0XC8,0X59,0X05,0X93,0XDB,0X56,0X3D,0XA6,0XD0,0X5D,0X05,0X97,0XC8,0X5F,0X05,0XCC,0XDC,0X4B,0X2E,0XB9,0XC0,0X5D,0X02,0XA9,0XB3,0X44,0X3D,0XBD,0XC8,0X01,0X06,0XB9,0XDF,0X56,0X2A,0XAA,0XC3,0X03,0X2E,0XB9,0XC0,0X04,0X03,0XB9,0XDF,0X56,0X05,0XB9,0XDC,0X44,0X2E,0XB9,0XD0,0X41,0X3D,0XA9,0XF2,0X5A,0X2B,0X97,0XC8,0X76,0X04,0X93,0XCC,0X45,0X3D,0XAA,0XC3,0X56,0X3D,0XB9,0XB3,0X5D,0X04,0X87,0XC8,0X5B,0X05,0XCC,0XBF,0X01,0X3E,0XA9,0XE6,0X44,0X2E,0XB9,0XBB,0X00,0X3E,0XCC,0XED,0X56,0X05,0XA9,0XB3,0X48,0X3D,0XAD,0XC8,0X01,0X3D,0XA6,0XE2,0X01,0X2E,0XB9,0XCC,0X44,0X3D,0XBD,0XC8,0X5D,0X03,0X93,0XDC,0X44,0X2E,0XB9,0XEE,0X47,0X3D,0XA6,0XC7,0X56,0X3E,0X93,0XDC,0X04,0X05,0XCC,0XBF,0X5A,0X2E,0XB6,0XD8,0X5E,0X3D,0XAD,0XCB,0X49,0X2A,0X94,0XD3,0X56,0X3E,0X90,0XE6,0X01,0X3D,0XAD,0XC8,0X42,0X3D,0XA9,0XBE,0X56,0X3D,0X93,0XE6,0X5D,0X05,0XB9,0XDB,0X44}, + .expectedDecodeBuf={0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E,0X20,0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E}, + .frame_len=220, + .raw_payload_len=159, + .expected_errno=0, + .descr="Mid-long valid text frame", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X82,0X86,0X90,0X5E,0X2B,0X8E,0XC4,0X3B,0X58,0XFA,0XF9,0X2A}, + .expectedDecodeBuf={0X54,0X65,0X73,0X74,0X69,0X74}, + .frame_len=12, + .raw_payload_len=6, + .expected_errno=0, + .descr="Short valid binary frame", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X82,0XFE,0X00,0X9F,0X7D,0X97,0X6B,0XA2,0X3B,0XE5,0X0A,0XCF,0X18,0XA5,0X4B,0XC6,0X12,0XF2,0X18,0X82,0X1E,0XF8,0X05,0XD6,0X1C,0XFE,0X05,0X82,0X10,0XE2,0X08,0XCA,0X5D,0XFA,0X04,0XD0,0X18,0XB7,0X1F,0XC7,0X05,0XE3,0X4B,0XC3,0X13,0XF3,0X4B,0XC7,0X0B,0XF2,0X05,0X82,0X1A,0XF8,0X0E,0XD1,0X5D,0XF5,0X0E,0XDB,0X12,0XF9,0X0F,0X82,0X09,0XFF,0X0E,0X82,0X4C,0XA5,0X5D,0X82,0X1F,0XEE,0X1F,0XC7,0X5D,0XFB,0X0E,0XCC,0X5D,0XF1,0X02,0XC7,0X11,0XF3,0X45,0X82,0X3B,0XE5,0X0A,0XCF,0X18,0XA5,0X4B,0XC6,0X12,0XF2,0X18,0X82,0X1E,0XF8,0X05,0XD6,0X1C,0XFE,0X05,0X82,0X10,0XE2,0X08,0XCA,0X5D,0XFA,0X04,0XD0,0X18,0XB7,0X1F,0XC7,0X05,0XE3,0X4B,0XC3,0X13,0XF3,0X4B,0XC7,0X0B,0XF2,0X05,0X82,0X1A,0XF8,0X0E,0XD1,0X5D,0XF5,0X0E,0XDB,0X12,0XF9,0X0F,0X82,0X09,0XFF,0X0E,0X82,0X4C,0XA5,0X5D,0X82,0X1F,0XEE,0X1F,0XC7,0X5D,0XFB,0X0E,0XCC,0X5D,0XF1,0X02,0XC7,0X11,0XF3,0X45}, + .expectedDecodeBuf={0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E,0X20,0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E}, + .frame_len=167, + .raw_payload_len=159, + .expected_errno=0, + .descr="Mid-long valid binary frame", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X88,0X82,0X71,0X1D,0X00,0XFE,0X72,0XF6}, + .expectedDecodeBuf={0X03,0XEB}, + .frame_len=8, + .raw_payload_len=2, + .expected_errno=104, + .descr="Close frame (Reason 1003)", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X88,0XAD,0XD0,0X8D,0X26,0XD8,0XD3,0X66,0X6F,0XFF,0XBD,0XAD,0X47,0XF8,0XB3,0XE1,0X49,0XAB,0XB5,0XAD,0X54,0XBD,0XB1,0XFE,0X49,0XB6,0XF0,0XEC,0X48,0XBC,0XF0,0XE0,0X53,0XBB,0XB8,0XAD,0X4B,0XB7,0XA2,0XE8,0X06,0XAC,0XB8,0XEC,0X48,0XF8,0XA4,0XE5,0X47,0XAC,0XF1}, + .expectedDecodeBuf={0X03,0XEB,0X49,0X27,0X6D,0X20,0X61,0X20,0X63,0X6C,0X6F,0X73,0X65,0X20,0X72,0X65,0X61,0X73,0X6F,0X6E,0X20,0X61,0X6E,0X64,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X68,0X61,0X6E,0X20,0X74,0X68,0X61,0X74,0X21}, + .frame_len=51, + .raw_payload_len=45, + .expected_errno=104, + .descr="Close frame (Reason 1003) and msg", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X81,0X08,0X56,0X47,0X56,0X7A,0X64,0X47,0X6C,0X30}, + .expectedDecodeBuf={0X54,0X65,0X73,0X74,0X69,0X74}, + .frame_len=10, + .raw_payload_len=6, + .expected_errno=71, + .descr="Invalid frame: Wrong masking", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X81,0XFE,0X00,0X0F,0X24,0X22,0X8D,0X9C,0X11,0X6F,0XA3,0XC6,0X6E,0X4E,0X88,0XB0,0X48,0X55,0XA2,0XC6,0X72,0X56}, + .expectedDecodeBuf={0X2E,0XFE,0X00,0X0F,0X72,0X65,0X20,0X49,0X70,0X73,0X75,0X6D}, + .frame_len=22, + .raw_payload_len=12, + .expected_errno=71, + .descr="Invalid frame: Length of < 126 with add. 16 bit len field", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X81,0XFF,0X00,0X00,0X00,0X00,0X80,0X40,0X7D,0XBB,0X03,0X56,0X7D,0XBB,0X03,0X56,0X7C,0X83,0X2D,0X0C,0X03,0XA2,0X06,0X7A,0X25,0XB9,0X2C,0X0C,0X1F,0XBA}, + .expectedDecodeBuf={0X2E,0XFF,0X00,0X00,0X00,0X00,0X80,0X40,0X4C,0X6F,0X72,0X65,0X20,0X49,0X70,0X73,0X75,0X6D}, + .frame_len=30, + .raw_payload_len=18, + .expected_errno=71, + .descr="Invalid frame: Length of < 126 with add. 64 bit len field", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + } +}; -- cgit v1.2.3 From 5d9d6a87124a5439d3432c37a67f9b2babe04407 Mon Sep 17 00:00:00 2001 From: Andreas Weigel Date: Mon, 27 Feb 2017 08:45:32 +0100 Subject: add decode support for continuation frames use FIN bit and implement opcode 0x00 make consistent use of uint64_t for big frame sizes --- libvncserver/websockets.c | 3 +- libvncserver/ws_decode.c | 137 +++++++++++++++++++++++++++++++++++++--------- libvncserver/ws_decode.h | 32 +++++------ test/wsmaketestframe.py | 15 +++-- test/wstest.c | 29 +++++----- test/wstestdata.inc | 54 +++++++++++++++--- 6 files changed, 196 insertions(+), 74 deletions(-) (limited to 'libvncserver/ws_decode.c') diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c index 73ad81c..921015d 100644 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -339,12 +339,11 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) free(buf); wsctx = calloc(1, sizeof(ws_ctx_t)); - wsctx->version = WEBSOCKETS_VERSION_HYBI; wsctx->encode = webSocketsEncodeHybi; wsctx->decode = webSocketsDecodeHybi; wsctx->ctxInfo.readFunc = ws_read; wsctx->base64 = base64; - hybiDecodeCleanup(wsctx); + hybiDecodeCleanupComplete(wsctx); cl->wsctx = (wsCtx *)wsctx; return TRUE; } diff --git a/libvncserver/ws_decode.c b/libvncserver/ws_decode.c index 472a44a..485478d 100644 --- a/libvncserver/ws_decode.c +++ b/libvncserver/ws_decode.c @@ -8,17 +8,27 @@ #define WS_HYBI_HEADER_LEN_EXTENDED 4 + WS_HYBI_MASK_LEN #define WS_HYBI_HEADER_LEN_LONG 10 + WS_HYBI_MASK_LEN -static int +static inline int +isControlFrame(ws_ctx_t *wsctx) +{ + return 0 != (wsctx->header.opcode & 0x08); +} + +static uint64_t hybiRemaining(ws_ctx_t *wsctx) { return wsctx->nToRead - wsctx->nReadRaw; } -void -hybiDecodeCleanup(ws_ctx_t *wsctx) +static void +hybiDecodeCleanupBasics(ws_ctx_t *wsctx) { + /* keep opcode, cleanup rest */ + wsctx->header.opcode = WS_OPCODE_INVALID; wsctx->header.payloadLen = 0; wsctx->header.mask.u = 0; + wsctx->header.headerLen = 0; + wsctx->header.data = NULL; wsctx->nReadRaw = 0; wsctx->nToRead= 0; wsctx->carrylen = 0; @@ -26,9 +36,24 @@ hybiDecodeCleanup(ws_ctx_t *wsctx) wsctx->readlen = 0; wsctx->hybiDecodeState = WS_HYBI_STATE_HEADER_PENDING; wsctx->writePos = NULL; - rfbLog("cleaned up wsctx\n"); } +static void +hybiDecodeCleanupForContinuation(ws_ctx_t *wsctx) +{ + hybiDecodeCleanupBasics(wsctx); + rfbLog("clean up frame, but expect continuation with opcode %d\n", wsctx->continuation_opcode); +} + +void +hybiDecodeCleanupComplete(ws_ctx_t *wsctx) +{ + hybiDecodeCleanupBasics(wsctx); + wsctx->continuation_opcode = WS_OPCODE_INVALID; + rfbLog("cleaned up wsctx completely\n"); +} + + /** * Return payload data that has been decoded/unmasked from * a websocket frame. @@ -94,10 +119,9 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) { int ret; char *headerDst = wsctx->codeBufDecode + wsctx->nReadRaw; - int n = WSHLENMAX - wsctx->nReadRaw; + int n = ((uint64_t)WSHLENMAX) - wsctx->nReadRaw; rfbLog("header_read to %p with len=%d\n", headerDst, n); - //ret = ws_read(cl, headerDst, n); ret = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, headerDst, n); rfbLog("read %d bytes from socket\n", ret); if (ret <= 0) { @@ -106,29 +130,65 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) int olderrno = errno; rfbErr("%s: read; %s\n", __func__, strerror(errno)); errno = olderrno; - *sockRet = -1; + goto err_cleanup_state; } else { *sockRet = 0; + goto err_cleanup_state_sock_closed; } - return WS_HYBI_STATE_ERR; } wsctx->nReadRaw += ret; if (wsctx->nReadRaw < 2) { /* cannot decode header with less than two bytes */ - errno = EAGAIN; - *sockRet = -1; - return WS_HYBI_STATE_HEADER_PENDING; + goto ret_header_pending; } /* first two header bytes received; interpret header data and get rest */ wsctx->header.data = (ws_header_t *)wsctx->codeBufDecode; wsctx->header.opcode = wsctx->header.data->b0 & 0x0f; + wsctx->header.fin = (wsctx->header.data->b0 & 0x80) >> 7; + if (isControlFrame(wsctx)) { + rfbLog("is control frame\n"); + /* is a control frame, leave remembered continuation opcode unchanged; + * just check if there is a wrong fragmentation */ + if (wsctx->header.fin == 0) { + + /* we only accept text/binary continuation frames; RFC6455: + * Control frames (see Section 5.5) MAY be injected in the middle of + * a fragmented message. Control frames themselves MUST NOT be + * fragmented. */ + rfbErr("control frame with FIN bit cleared received, aborting\n"); + errno = EPROTO; + goto err_cleanup_state; + } + } else { + rfbLog("not a control frame\n"); + /* not a control frame, check for continuation opcode */ + if (wsctx->header.opcode == WS_OPCODE_CONTINUATION) { + rfbLog("cont_frame\n"); + /* do we have state (i.e., opcode) for continuation frame? */ + if (wsctx->continuation_opcode == WS_OPCODE_INVALID) { + rfbErr("no continuation state\n"); + errno = EPROTO; + goto err_cleanup_state; + } - /* fin = (header->b0 & 0x80) >> 7; */ /* not used atm */ - wsctx->header.payloadLen = wsctx->header.data->b1 & 0x7f; - rfbLog("first header bytes received; opcode=%d lenbyte=%d\n", wsctx->header.opcode, wsctx->header.payloadLen); + /* otherwise, set opcode = continuation_opcode */ + wsctx->header.opcode = wsctx->continuation_opcode; + rfbLog("set opcode to continuation_opcode: %d\n", wsctx->header.opcode); + } else { + if (wsctx->header.fin == 0) { + wsctx->continuation_opcode = wsctx->header.opcode; + } else { + wsctx->continuation_opcode = WS_OPCODE_INVALID; + } + rfbLog("set continuation_opcode to %d\n", wsctx->continuation_opcode); + } + } + + wsctx->header.payloadLen = (uint64_t)(wsctx->header.data->b1 & 0x7f); + rfbLog("first header bytes received; opcode=%d lenbyte=%d fin=%d\n", wsctx->header.opcode, wsctx->header.payloadLen, wsctx->header.fin); /* * 4.3. Client-to-Server Masking @@ -139,8 +199,7 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) if (!(wsctx->header.data->b1 & 0x80)) { rfbErr("%s: got frame without mask; ret=%d\n", __func__, ret); errno = EPROTO; - *sockRet = -1; - return WS_HYBI_STATE_ERR; + goto err_cleanup_state; } @@ -158,22 +217,27 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) } else { /* Incomplete frame header, try again */ rfbErr("%s: incomplete frame header; ret=%d\n", __func__, ret); - errno = EAGAIN; - *sockRet = -1; - return WS_HYBI_STATE_HEADER_PENDING; + goto ret_header_pending; } + char *h = wsctx->codeBufDecode; + int i; + rfbLog("Header:\n"); + for (i=0; i <10; i++) { + rfbLog("0x%02X\n", (unsigned char)h[i]); + } + rfbLog("\n"); + /* while RFC 6455 mandates that lengths MUST be encoded with the minimum * number of bytes, it does not specify for the server how to react on * 'wrongly' encoded frames --- this implementation rejects them*/ if ((wsctx->header.headerLen > WS_HYBI_HEADER_LEN_SHORT - && wsctx->header.payloadLen < 126) + && wsctx->header.payloadLen < (uint64_t)126) || (wsctx->header.headerLen > WS_HYBI_HEADER_LEN_EXTENDED - && wsctx->header.payloadLen < 65536)) { + && wsctx->header.payloadLen < (uint64_t)65536)) { rfbErr("%s: invalid length field; headerLen=%d payloadLen=%llu\n", __func__, wsctx->header.headerLen, wsctx->header.payloadLen); errno = EPROTO; - *sockRet = -1; - return WS_HYBI_STATE_ERR; + goto err_cleanup_state; } /* absolute length of frame */ @@ -186,9 +250,20 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) wsctx->readPos = (unsigned char *)(wsctx->codeBufDecode + wsctx->header.headerLen); *nPayload = wsctx->nReadRaw - wsctx->header.headerLen; - rfbLog("header complete: state=%d flen=%d writeTo=%p nPayload=%d\n", wsctx->hybiDecodeState, wsctx->nToRead, wsctx->writePos, *nPayload); + rfbLog("header complete: state=%d flen=%llu writeTo=%p nPayload=%d\n", wsctx->hybiDecodeState, wsctx->nToRead, wsctx->writePos, *nPayload); return WS_HYBI_STATE_DATA_NEEDED; + +ret_header_pending: + errno = EAGAIN; + *sockRet = -1; + return WS_HYBI_STATE_HEADER_PENDING; + +err_cleanup_state: + *sockRet = -1; +err_cleanup_state_sock_closed: + hybiDecodeCleanupComplete(wsctx); + return WS_HYBI_STATE_ERR; } static int @@ -248,6 +323,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) /* -1 accounts for potential '\0' terminator for base64 decoding */ bufsize = wsctx->codeBufDecode + ARRAYSIZE(wsctx->codeBufDecode) - wsctx->writePos - 1; + rfbLog("bufsize=%d\n", bufsize); if (hybiRemaining(wsctx) > bufsize) { nextRead = bufsize; } else { @@ -453,11 +529,18 @@ spor: /* rfbLog("%s: ret: %d/%d\n", __func__, result, len); */ if (wsctx->hybiDecodeState == WS_HYBI_STATE_FRAME_COMPLETE) { rfbLog("frame received successfully, cleaning up: read=%d hlen=%d plen=%d\n", wsctx->header.nRead, wsctx->header.headerLen, wsctx->header.payloadLen); - /* frame finished, cleanup state */ - hybiDecodeCleanup(wsctx); + if (wsctx->header.fin && !isControlFrame(wsctx)) { + /* frame finished, cleanup state */ + hybiDecodeCleanupComplete(wsctx); + } else { + /* always retain continuation opcode for unfinished data frames + * or control frames, which may interleave with data frames */ + hybiDecodeCleanupForContinuation(wsctx); + } } else if (wsctx->hybiDecodeState == WS_HYBI_STATE_ERR) { - hybiDecodeCleanup(wsctx); + hybiDecodeCleanupComplete(wsctx); } + rfbLog("%s_exit: len=%d; " "CTX: readlen=%d readPos=%p " "writePos=%p " diff --git a/libvncserver/ws_decode.h b/libvncserver/ws_decode.h index 0dcbc83..07d37bd 100644 --- a/libvncserver/ws_decode.h +++ b/libvncserver/ws_decode.h @@ -27,16 +27,11 @@ #endif #define B64LEN(__x) (((__x + 2) / 3) * 12 / 3) -#define WSHLENMAX 14 /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */ +#define WSHLENMAX 14LL /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */ #define WS_HYBI_MASK_LEN 4 #define ARRAYSIZE(a) ((sizeof(a) / sizeof((a[0]))) / (size_t)(!(sizeof(a) % sizeof((a[0]))))) -enum { - WEBSOCKETS_VERSION_HIXIE, - WEBSOCKETS_VERSION_HYBI -}; - struct ws_ctx_s; typedef struct ws_ctx_s ws_ctx_t; @@ -111,9 +106,11 @@ typedef struct ws_header_data_s { /** length of frame header including payload len, but without mask */ int headerLen; /** length of the payload data */ - int payloadLen; + uint64_t payloadLen; /** opcode */ unsigned char opcode; + /** fin bit */ + unsigned char fin; } ws_header_data_t; typedef struct ws_ctx_s { @@ -125,11 +122,11 @@ typedef struct ws_ctx_s { int hybiDecodeState; char carryBuf[3]; /* For base64 carry-over */ int carrylen; - int version; int base64; ws_header_data_t header; - int nReadRaw; - int nToRead; + uint64_t nReadRaw; + uint64_t nToRead; + unsigned char continuation_opcode; wsEncodeFunc encode; wsDecodeFunc decode; ctxInfo_t ctxInfo; @@ -137,15 +134,16 @@ typedef struct ws_ctx_s { enum { - WS_OPCODE_CONTINUATION = 0x0, - WS_OPCODE_TEXT_FRAME, - WS_OPCODE_BINARY_FRAME, - WS_OPCODE_CLOSE = 0x8, - WS_OPCODE_PING, - WS_OPCODE_PONG + WS_OPCODE_CONTINUATION = 0x00, + WS_OPCODE_TEXT_FRAME = 0x01, + WS_OPCODE_BINARY_FRAME = 0x02, + WS_OPCODE_CLOSE = 0x08, + WS_OPCODE_PING = 0x09, + WS_OPCODE_PONG = 0x0A, + WS_OPCODE_INVALID = 0xFF }; int webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len); -void hybiDecodeCleanup(ws_ctx_t *wsctx); +void hybiDecodeCleanupComplete(ws_ctx_t *wsctx); #endif diff --git a/test/wsmaketestframe.py b/test/wsmaketestframe.py index 1d4d24d..fc03e39 100755 --- a/test/wsmaketestframe.py +++ b/test/wsmaketestframe.py @@ -42,12 +42,12 @@ def add_field(s, name, value, first=False): class Testframe(): - def __init__(self, frame, descr, modify_bytes={}, experrno=0, mask=True): + def __init__(self, frame, descr, modify_bytes={}, experrno=0, mask=True, opcode_overwrite=False): self.frame = frame self.descr = descr self.modify_bytes = modify_bytes self.experrno = experrno - self.b64 = True if frame.opcode == 1 else False + self.b64 = True if frame.opcode == 1 or opcode_overwrite == 1 else False self.mask = mask def to_carray_initializer(self, buf): @@ -110,7 +110,14 @@ flist.append(Testframe(websockets.framing.Frame(1, 8, bytearray(list([0x03, 0xEB ### invalid header values flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Testit", encoding="utf-8")), "Invalid frame: Wrong masking", experrno="EPROTO", mask=False)) flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("..Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 16 bit len field", experrno="EPROTO", modify_bytes={ 1: 0xFE, 2: 0x00, 3: 0x0F})) -flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("........Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 64 bit len field", experrno="EPROTO", modify_bytes={ 1: 0xFF, 2: 0x00, 3: 0x00, 4: 0x00, 5: 0x00, 6: 0x80, 7: 0x40})) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("........Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 64 bit len field", experrno="EPROTO", modify_bytes={ 1: 0xFF, 2: 0x00, 3: 0x00, 4: 0x00, 5: 0x00, 6: 0x00, 7: 0x00, 8: 0x80, 9: 0x40})) + +frag1 = websockets.framing.Frame(0, 1, bytearray("This is a fragmented websocket...", encoding="utf-8")) +frag2 = websockets.framing.Frame(0, 0, bytearray("... and it goes on...", encoding="utf-8")) +frag3 = websockets.framing.Frame(1, 0, bytearray("and on and stop", encoding="utf-8")) +flist.append(Testframe(frag1, "Continuation test frag1")) +flist.append(Testframe(frag2, "Continuation test frag2", opcode_overwrite=1)) +flist.append(Testframe(frag3, "Continuation test frag3", opcode_overwrite=1)) s = "struct ws_frame_test tests[] = {\n" for i in range(len(flist)): @@ -120,5 +127,5 @@ for i in range(len(flist)): s += "\n" s += "};\n" -with open("wstestdata.c", "w") as cdatafile: +with open("wstestdata.inc", "w") as cdatafile: cdatafile.write(s) diff --git a/test/wstest.c b/test/wstest.c index 69cd174..042b75b 100644 --- a/test/wstest.c +++ b/test/wstest.c @@ -98,10 +98,10 @@ static int emu_read(void *ctx, char *dst, size_t len) rfbLog("emu_read called with dst=%p and len=%lu\n", dst, len); if (ft->simulate_sock_malfunction_at > 0 && ft->simulate_sock_malfunction_at == ft->i) { rfbLog("simulating IO error with errno=%d\n", ft->errno_val); - errno = ft->errno_val; + errno = ft->errno_val; return -1; } - + /* return something */ r = rand(); modu = (ft->frame + ft->frame_len) - ft->pos; @@ -130,7 +130,7 @@ static uint64_t run_test(struct ws_frame_test *ft, ws_ctx_t *ctx) while (nleft > 0) { rfbLog("calling ws_decode with dst=%p, len=%lu\n", dst, nleft); n = ctx->decode(ctx, dst, nleft); - rfbLog("read n=%ld\n", n); + rfbLog("read n=%ld\n", n); if (n == 0) { if (ft->close_sock_at > 0) { return OK; @@ -155,7 +155,7 @@ static uint64_t run_test(struct ws_frame_test *ft, ws_ctx_t *ctx) rfbLog("read n=%ld from decode; dst=%p, nleft=%lu\n", n, dst, nleft); } } - + if (memcmp(ft->expectedDecodeBuf, dstbuf, ft->raw_payload_len) != 0) { ft->expectedDecodeBuf[ft->raw_payload_len] = '\0'; dstbuf[ft->raw_payload_len] = '\0'; @@ -168,24 +168,23 @@ static uint64_t run_test(struct ws_frame_test *ft, ws_ctx_t *ctx) int main() -{ +{ ws_ctx_t ctx; int retall= 0; int i; - srand(RND_SEED); + srand(RND_SEED); + hybiDecodeCleanupComplete(&ctx); + ctx.decode = webSocketsDecodeHybi; + ctx.ctxInfo.readFunc = emu_read; + rfbLog = logtest; + rfbErr = logtest; + for (i = 0; i < ARRAYSIZE(tests); i++) { int ret; + /* reset output log buffer to begin */ el_pos = el_log; - rfbLog = logtest; - rfbErr = logtest; - - hybiDecodeCleanup(&ctx); - ctx.decode = webSocketsDecodeHybi; - ctx.version = WEBSOCKETS_VERSION_HYBI; - - ctx.ctxInfo.readFunc = emu_read; ret = run_test(&tests[i], &ctx); printf("%s: \"%s\"\n", ret == 0 ? "PASS" : "FAIL", tests[i].descr); @@ -198,7 +197,7 @@ int main() return retall; } -#else +#else int main() { return 0; diff --git a/test/wstestdata.inc b/test/wstestdata.inc index 9dc919e..595b891 100644 --- a/test/wstestdata.inc +++ b/test/wstestdata.inc @@ -1,6 +1,6 @@ struct ws_frame_test tests[] = { { - .frame={0X81,0X88,0XB7,0XDB,0X16,0X16,0XE1,0X9C,0X40,0X6C,0XD3,0X9C,0X7A,0X26}, + .frame={0X81,0X88,0X2F,0X2A,0X17,0X41,0X79,0X6D,0X41,0X3B,0X4B,0X6D,0X7B,0X71}, .expectedDecodeBuf={0X54,0X65,0X73,0X74,0X69,0X74}, .frame_len=14, .raw_payload_len=6, @@ -12,7 +12,7 @@ struct ws_frame_test tests[] = { .close_sock_at=0 }, { - .frame={0X81,0XFE,0X00,0XD4,0X67,0XFE,0X8A,0X31,0X35,0X90,0XC0,0X59,0X05,0XA9,0XDF,0X48,0X2E,0XB9,0XD8,0X47,0X3D,0XA6,0XC7,0X56,0X3E,0XCC,0XB3,0X44,0X03,0XB9,0XCC,0X41,0X05,0X97,0XC8,0X45,0X03,0XA9,0XC4,0X5E,0X2E,0XB9,0XBB,0X47,0X04,0X93,0XDF,0X56,0X03,0XB9,0XDC,0X05,0X03,0XBD,0XC8,0X59,0X05,0X93,0XDB,0X56,0X3D,0XA6,0XD0,0X5D,0X05,0X97,0XC8,0X5F,0X05,0XCC,0XDC,0X4B,0X2E,0XB9,0XC0,0X5D,0X02,0XA9,0XB3,0X44,0X3D,0XBD,0XC8,0X01,0X06,0XB9,0XDF,0X56,0X2A,0XAA,0XC3,0X03,0X2E,0XB9,0XC0,0X04,0X03,0XB9,0XDF,0X56,0X05,0XB9,0XDC,0X44,0X2E,0XB9,0XD0,0X41,0X3D,0XA9,0XF2,0X5A,0X2B,0X97,0XC8,0X76,0X04,0X93,0XCC,0X45,0X3D,0XAA,0XC3,0X56,0X3D,0XB9,0XB3,0X5D,0X04,0X87,0XC8,0X5B,0X05,0XCC,0XBF,0X01,0X3E,0XA9,0XE6,0X44,0X2E,0XB9,0XBB,0X00,0X3E,0XCC,0XED,0X56,0X05,0XA9,0XB3,0X48,0X3D,0XAD,0XC8,0X01,0X3D,0XA6,0XE2,0X01,0X2E,0XB9,0XCC,0X44,0X3D,0XBD,0XC8,0X5D,0X03,0X93,0XDC,0X44,0X2E,0XB9,0XEE,0X47,0X3D,0XA6,0XC7,0X56,0X3E,0X93,0XDC,0X04,0X05,0XCC,0XBF,0X5A,0X2E,0XB6,0XD8,0X5E,0X3D,0XAD,0XCB,0X49,0X2A,0X94,0XD3,0X56,0X3E,0X90,0XE6,0X01,0X3D,0XAD,0XC8,0X42,0X3D,0XA9,0XBE,0X56,0X3D,0X93,0XE6,0X5D,0X05,0XB9,0XDB,0X44}, + .frame={0X81,0XFE,0X00,0XD4,0X66,0X27,0XE5,0X24,0X34,0X49,0XAF,0X4C,0X04,0X70,0XB0,0X5D,0X2F,0X60,0XB7,0X52,0X3C,0X7F,0XA8,0X43,0X3F,0X15,0XDC,0X51,0X02,0X60,0XA3,0X54,0X04,0X4E,0XA7,0X50,0X02,0X70,0XAB,0X4B,0X2F,0X60,0XD4,0X52,0X05,0X4A,0XB0,0X43,0X02,0X60,0XB3,0X10,0X02,0X64,0XA7,0X4C,0X04,0X4A,0XB4,0X43,0X3C,0X7F,0XBF,0X48,0X04,0X4E,0XA7,0X4A,0X04,0X15,0XB3,0X5E,0X2F,0X60,0XAF,0X48,0X03,0X70,0XDC,0X51,0X3C,0X64,0XA7,0X14,0X07,0X60,0XB0,0X43,0X2B,0X73,0XAC,0X16,0X2F,0X60,0XAF,0X11,0X02,0X60,0XB0,0X43,0X04,0X60,0XB3,0X51,0X2F,0X60,0XBF,0X54,0X3C,0X70,0X9D,0X4F,0X2A,0X4E,0XA7,0X63,0X05,0X4A,0XA3,0X50,0X3C,0X73,0XAC,0X43,0X3C,0X60,0XDC,0X48,0X05,0X5E,0XA7,0X4E,0X04,0X15,0XD0,0X14,0X3F,0X70,0X89,0X51,0X2F,0X60,0XD4,0X15,0X3F,0X15,0X82,0X43,0X04,0X70,0XDC,0X5D,0X3C,0X74,0XA7,0X14,0X3C,0X7F,0X8D,0X14,0X2F,0X60,0XA3,0X51,0X3C,0X64,0XA7,0X48,0X02,0X4A,0XB3,0X51,0X2F,0X60,0X81,0X52,0X3C,0X7F,0XA8,0X43,0X3F,0X4A,0XB3,0X11,0X04,0X15,0XD0,0X4F,0X2F,0X6F,0XB7,0X4B,0X3C,0X74,0XA4,0X5C,0X2B,0X4D,0XBC,0X43,0X3F,0X49,0X89,0X14,0X3C,0X74,0XA7,0X57,0X3C,0X70,0XD1,0X43,0X3C,0X4A,0X89,0X48,0X04,0X60,0XB4,0X51}, .expectedDecodeBuf={0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E,0X20,0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E}, .frame_len=220, .raw_payload_len=159, @@ -24,7 +24,7 @@ struct ws_frame_test tests[] = { .close_sock_at=0 }, { - .frame={0X82,0X86,0X90,0X5E,0X2B,0X8E,0XC4,0X3B,0X58,0XFA,0XF9,0X2A}, + .frame={0X82,0X86,0XDD,0X9B,0XD8,0X56,0X89,0XFE,0XAB,0X22,0XB4,0XEF}, .expectedDecodeBuf={0X54,0X65,0X73,0X74,0X69,0X74}, .frame_len=12, .raw_payload_len=6, @@ -36,7 +36,7 @@ struct ws_frame_test tests[] = { .close_sock_at=0 }, { - .frame={0X82,0XFE,0X00,0X9F,0X7D,0X97,0X6B,0XA2,0X3B,0XE5,0X0A,0XCF,0X18,0XA5,0X4B,0XC6,0X12,0XF2,0X18,0X82,0X1E,0XF8,0X05,0XD6,0X1C,0XFE,0X05,0X82,0X10,0XE2,0X08,0XCA,0X5D,0XFA,0X04,0XD0,0X18,0XB7,0X1F,0XC7,0X05,0XE3,0X4B,0XC3,0X13,0XF3,0X4B,0XC7,0X0B,0XF2,0X05,0X82,0X1A,0XF8,0X0E,0XD1,0X5D,0XF5,0X0E,0XDB,0X12,0XF9,0X0F,0X82,0X09,0XFF,0X0E,0X82,0X4C,0XA5,0X5D,0X82,0X1F,0XEE,0X1F,0XC7,0X5D,0XFB,0X0E,0XCC,0X5D,0XF1,0X02,0XC7,0X11,0XF3,0X45,0X82,0X3B,0XE5,0X0A,0XCF,0X18,0XA5,0X4B,0XC6,0X12,0XF2,0X18,0X82,0X1E,0XF8,0X05,0XD6,0X1C,0XFE,0X05,0X82,0X10,0XE2,0X08,0XCA,0X5D,0XFA,0X04,0XD0,0X18,0XB7,0X1F,0XC7,0X05,0XE3,0X4B,0XC3,0X13,0XF3,0X4B,0XC7,0X0B,0XF2,0X05,0X82,0X1A,0XF8,0X0E,0XD1,0X5D,0XF5,0X0E,0XDB,0X12,0XF9,0X0F,0X82,0X09,0XFF,0X0E,0X82,0X4C,0XA5,0X5D,0X82,0X1F,0XEE,0X1F,0XC7,0X5D,0XFB,0X0E,0XCC,0X5D,0XF1,0X02,0XC7,0X11,0XF3,0X45}, + .frame={0X82,0XFE,0X00,0X9F,0XB5,0X6E,0X7F,0X4C,0XF3,0X1C,0X1E,0X21,0XD0,0X5C,0X5F,0X28,0XDA,0X0B,0X0C,0X6C,0XD6,0X01,0X11,0X38,0XD4,0X07,0X11,0X6C,0XD8,0X1B,0X1C,0X24,0X95,0X03,0X10,0X3E,0XD0,0X4E,0X0B,0X29,0XCD,0X1A,0X5F,0X2D,0XDB,0X0A,0X5F,0X29,0XC3,0X0B,0X11,0X6C,0XD2,0X01,0X1A,0X3F,0X95,0X0C,0X1A,0X35,0XDA,0X00,0X1B,0X6C,0XC1,0X06,0X1A,0X6C,0X84,0X5C,0X49,0X6C,0XD7,0X17,0X0B,0X29,0X95,0X02,0X1A,0X22,0X95,0X08,0X16,0X29,0XD9,0X0A,0X51,0X6C,0XF3,0X1C,0X1E,0X21,0XD0,0X5C,0X5F,0X28,0XDA,0X0B,0X0C,0X6C,0XD6,0X01,0X11,0X38,0XD4,0X07,0X11,0X6C,0XD8,0X1B,0X1C,0X24,0X95,0X03,0X10,0X3E,0XD0,0X4E,0X0B,0X29,0XCD,0X1A,0X5F,0X2D,0XDB,0X0A,0X5F,0X29,0XC3,0X0B,0X11,0X6C,0XD2,0X01,0X1A,0X3F,0X95,0X0C,0X1A,0X35,0XDA,0X00,0X1B,0X6C,0XC1,0X06,0X1A,0X6C,0X84,0X5C,0X49,0X6C,0XD7,0X17,0X0B,0X29,0X95,0X02,0X1A,0X22,0X95,0X08,0X16,0X29,0XD9,0X0A,0X51}, .expectedDecodeBuf={0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E,0X20,0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E}, .frame_len=167, .raw_payload_len=159, @@ -48,7 +48,7 @@ struct ws_frame_test tests[] = { .close_sock_at=0 }, { - .frame={0X88,0X82,0X71,0X1D,0X00,0XFE,0X72,0XF6}, + .frame={0X88,0X82,0X6B,0X33,0X77,0X94,0X68,0XD8}, .expectedDecodeBuf={0X03,0XEB}, .frame_len=8, .raw_payload_len=2, @@ -60,7 +60,7 @@ struct ws_frame_test tests[] = { .close_sock_at=0 }, { - .frame={0X88,0XAD,0XD0,0X8D,0X26,0XD8,0XD3,0X66,0X6F,0XFF,0XBD,0XAD,0X47,0XF8,0XB3,0XE1,0X49,0XAB,0XB5,0XAD,0X54,0XBD,0XB1,0XFE,0X49,0XB6,0XF0,0XEC,0X48,0XBC,0XF0,0XE0,0X53,0XBB,0XB8,0XAD,0X4B,0XB7,0XA2,0XE8,0X06,0XAC,0XB8,0XEC,0X48,0XF8,0XA4,0XE5,0X47,0XAC,0XF1}, + .frame={0X88,0XAD,0X4B,0XA1,0XCE,0XE8,0X48,0X4A,0X87,0XCF,0X26,0X81,0XAF,0XC8,0X28,0XCD,0XA1,0X9B,0X2E,0X81,0XBC,0X8D,0X2A,0XD2,0XA1,0X86,0X6B,0XC0,0XA0,0X8C,0X6B,0XCC,0XBB,0X8B,0X23,0X81,0XA3,0X87,0X39,0XC4,0XEE,0X9C,0X23,0XC0,0XA0,0XC8,0X3F,0XC9,0XAF,0X9C,0X6A}, .expectedDecodeBuf={0X03,0XEB,0X49,0X27,0X6D,0X20,0X61,0X20,0X63,0X6C,0X6F,0X73,0X65,0X20,0X72,0X65,0X61,0X73,0X6F,0X6E,0X20,0X61,0X6E,0X64,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X68,0X61,0X6E,0X20,0X74,0X68,0X61,0X74,0X21}, .frame_len=51, .raw_payload_len=45, @@ -84,7 +84,7 @@ struct ws_frame_test tests[] = { .close_sock_at=0 }, { - .frame={0X81,0XFE,0X00,0X0F,0X24,0X22,0X8D,0X9C,0X11,0X6F,0XA3,0XC6,0X6E,0X4E,0X88,0XB0,0X48,0X55,0XA2,0XC6,0X72,0X56}, + .frame={0X81,0XFE,0X00,0X0F,0X71,0XE9,0X29,0X79,0X44,0XA4,0X07,0X23,0X3B,0X85,0X2C,0X55,0X1D,0X9E,0X06,0X23,0X27,0X9D}, .expectedDecodeBuf={0X2E,0XFE,0X00,0X0F,0X72,0X65,0X20,0X49,0X70,0X73,0X75,0X6D}, .frame_len=22, .raw_payload_len=12, @@ -96,8 +96,8 @@ struct ws_frame_test tests[] = { .close_sock_at=0 }, { - .frame={0X81,0XFF,0X00,0X00,0X00,0X00,0X80,0X40,0X7D,0XBB,0X03,0X56,0X7D,0XBB,0X03,0X56,0X7C,0X83,0X2D,0X0C,0X03,0XA2,0X06,0X7A,0X25,0XB9,0X2C,0X0C,0X1F,0XBA}, - .expectedDecodeBuf={0X2E,0XFF,0X00,0X00,0X00,0X00,0X80,0X40,0X4C,0X6F,0X72,0X65,0X20,0X49,0X70,0X73,0X75,0X6D}, + .frame={0X81,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X80,0X40,0X2F,0X40,0XF3,0X5B,0X2F,0X40,0XF2,0X63,0X01,0X1A,0X8D,0X42,0X2A,0X6C,0XAB,0X59,0X00,0X1A,0X91,0X5A}, + .expectedDecodeBuf={0X2E,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X80,0X40,0X72,0X65,0X20,0X49,0X70,0X73,0X75,0X6D}, .frame_len=30, .raw_payload_len=18, .expected_errno=EPROTO, @@ -106,5 +106,41 @@ struct ws_frame_test tests[] = { .simulate_sock_malfunction_at=0, .errno_val=0, .close_sock_at=0 + }, + { + .frame={0X01,0XAC,0XC9,0X6E,0XC7,0X6E,0X9F,0X29,0XAF,0X1E,0XAA,0X17,0X85,0X1E,0XAA,0X17,0X85,0X06,0X80,0X29,0X9D,0X17,0X90,0X39,0XA3,0X1A,0X93,0X39,0XF2,0X5E,0X93,0X39,0X96,0X09,0XAD,0X5C,0X91,0X07,0XAA,0X5C,0XFE,0X04,0XA8,0X5C,0X91,0X5E,0X85,0X07,0XF3,0X1B}, + .expectedDecodeBuf={0X54,0X68,0X69,0X73,0X20,0X69,0X73,0X20,0X61,0X20,0X66,0X72,0X61,0X67,0X6D,0X65,0X6E,0X74,0X65,0X64,0X20,0X77,0X65,0X62,0X73,0X6F,0X63,0X6B,0X65,0X74,0X2E,0X2E,0X2E}, + .frame_len=50, + .raw_payload_len=33, + .expected_errno=0, + .descr="Continuation test frag1", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X00,0X9C,0X52,0XBC,0XD5,0X99,0X1E,0XD5,0XE1,0XEC,0X1B,0XFB,0X93,0XEC,0X08,0XFF,0X97,0XE9,0X36,0XFF,0X97,0XF7,0X30,0X8E,0X83,0XE3,0X1B,0XFB,0XEC,0XEC,0X1E,0XD5,0XE1,0XEC}, + .expectedDecodeBuf={0X2E,0X2E,0X2E,0X20,0X61,0X6E,0X64,0X20,0X69,0X74,0X20,0X67,0X6F,0X65,0X73,0X20,0X6F,0X6E,0X2E,0X2E,0X2E}, + .frame_len=34, + .raw_payload_len=21, + .expected_errno=0, + .descr="Continuation test frag2", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X80,0X94,0X3B,0X88,0XA1,0XE9,0X62,0XDF,0X94,0X82,0X72,0XCF,0X98,0X9C,0X72,0XCF,0XE7,0X9C,0X61,0XCB,0XE3,0X93,0X5F,0XCF,0X98,0X9E}, + .expectedDecodeBuf={0X61,0X6E,0X64,0X20,0X6F,0X6E,0X20,0X61,0X6E,0X64,0X20,0X73,0X74,0X6F,0X70}, + .frame_len=26, + .raw_payload_len=15, + .expected_errno=0, + .descr="Continuation test frag3", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 } }; -- cgit v1.2.3 From ef8d2852f546135c94282a4d634fe4ac9e7558a4 Mon Sep 17 00:00:00 2001 From: Andreas Weigel Date: Mon, 27 Feb 2017 09:00:19 +0100 Subject: remove potential 64 bit len overflow calculation --- libvncserver/ws_decode.c | 66 ++++++++++++++++++++++-------------------------- libvncserver/ws_decode.h | 3 +-- 2 files changed, 31 insertions(+), 38 deletions(-) (limited to 'libvncserver/ws_decode.c') diff --git a/libvncserver/ws_decode.c b/libvncserver/ws_decode.c index 485478d..4616fdc 100644 --- a/libvncserver/ws_decode.c +++ b/libvncserver/ws_decode.c @@ -14,10 +14,10 @@ isControlFrame(ws_ctx_t *wsctx) return 0 != (wsctx->header.opcode & 0x08); } -static uint64_t +static uint64_t hybiRemaining(ws_ctx_t *wsctx) { - return wsctx->nToRead - wsctx->nReadRaw; + return wsctx->header.payloadLen - wsctx->nReadPayload; } static void @@ -29,8 +29,8 @@ hybiDecodeCleanupBasics(ws_ctx_t *wsctx) wsctx->header.mask.u = 0; wsctx->header.headerLen = 0; wsctx->header.data = NULL; - wsctx->nReadRaw = 0; - wsctx->nToRead= 0; + wsctx->header.nRead = 0; + wsctx->nReadPayload = 0; wsctx->carrylen = 0; wsctx->readPos = (unsigned char *)wsctx->codeBufDecode; wsctx->readlen = 0; @@ -118,8 +118,9 @@ static int hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) { int ret; - char *headerDst = wsctx->codeBufDecode + wsctx->nReadRaw; - int n = ((uint64_t)WSHLENMAX) - wsctx->nReadRaw; + char *headerDst = wsctx->codeBufDecode + wsctx->header.nRead; + int n = ((uint64_t)WSHLENMAX) - wsctx->header.nRead; + rfbLog("header_read to %p with len=%d\n", headerDst, n); ret = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, headerDst, n); @@ -137,8 +138,8 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) } } - wsctx->nReadRaw += ret; - if (wsctx->nReadRaw < 2) { + wsctx->header.nRead += ret; + if (wsctx->header.nRead < 2) { /* cannot decode header with less than two bytes */ goto ret_header_pending; } @@ -203,14 +204,14 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) } - if (wsctx->header.payloadLen < 126 && wsctx->nReadRaw >= 6) { + if (wsctx->header.payloadLen < 126 && wsctx->header.nRead >= 6) { wsctx->header.headerLen = WS_HYBI_HEADER_LEN_SHORT; wsctx->header.mask = wsctx->header.data->u.m; - } else if (wsctx->header.payloadLen == 126 && 8 <= wsctx->nReadRaw) { + } else if (wsctx->header.payloadLen == 126 && 8 <= wsctx->header.nRead) { wsctx->header.headerLen = WS_HYBI_HEADER_LEN_EXTENDED; wsctx->header.payloadLen = WS_NTOH16(wsctx->header.data->u.s16.l16); wsctx->header.mask = wsctx->header.data->u.s16.m16; - } else if (wsctx->header.payloadLen == 127 && 14 <= wsctx->nReadRaw) { + } else if (wsctx->header.payloadLen == 127 && 14 <= wsctx->header.nRead) { wsctx->header.headerLen = WS_HYBI_HEADER_LEN_LONG; wsctx->header.payloadLen = WS_NTOH64(wsctx->header.data->u.s64.l64); wsctx->header.mask = wsctx->header.data->u.s64.m64; @@ -240,17 +241,16 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) goto err_cleanup_state; } - /* absolute length of frame */ - wsctx->nToRead = wsctx->header.headerLen + wsctx->header.payloadLen; - /* update write position for next bytes */ - wsctx->writePos = wsctx->codeBufDecode + wsctx->nReadRaw; + wsctx->writePos = wsctx->codeBufDecode + wsctx->header.nRead; /* set payload pointer just after header */ wsctx->readPos = (unsigned char *)(wsctx->codeBufDecode + wsctx->header.headerLen); - *nPayload = wsctx->nReadRaw - wsctx->header.headerLen; - rfbLog("header complete: state=%d flen=%llu writeTo=%p nPayload=%d\n", wsctx->hybiDecodeState, wsctx->nToRead, wsctx->writePos, *nPayload); + *nPayload = wsctx->header.nRead - wsctx->header.headerLen; + wsctx->nReadPayload = *nPayload; + + rfbLog("header complete: state=%d headerlen=%d payloadlen=%llu writeTo=%p nPayload=%d\n", wsctx->hybiDecodeState, wsctx->header.headerLen, wsctx->header.payloadLen, wsctx->writePos, *nPayload); return WS_HYBI_STATE_DATA_NEEDED; @@ -332,7 +332,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) rfbLog("calling read with buf=%p and len=%d (decodebuf=%p headerLen=%d)\n", wsctx->writePos, nextRead, wsctx->codeBufDecode, wsctx->header.headerLen); - if (wsctx->nReadRaw < wsctx->nToRead) { + if (nextRead > 0) { /* decode more data */ if (-1 == (n = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, wsctx->writePos, nextRead))) { int olderrno = errno; @@ -343,24 +343,18 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) } else if (n == 0) { *sockRet = 0; return WS_HYBI_STATE_ERR; + } else { + rfbLog("read %d bytes from socket; nRead=%d\n", n, wsctx->nReadPayload); } - wsctx->nReadRaw += n; - rfbLog("read %d bytes from socket; nRead=%d\n", n, wsctx->nReadRaw); } else { n = 0; } + wsctx->nReadPayload += n; wsctx->writePos += n; - if (wsctx->nReadRaw >= wsctx->nToRead) { - if (wsctx->nReadRaw > wsctx->nToRead) { - rfbErr("%s: internal error, read past websocket frame", __func__); - errno=EIO; - *sockRet = -1; - return WS_HYBI_STATE_ERR; - } else { - wsctx->hybiDecodeState = WS_HYBI_STATE_FRAME_COMPLETE; - } + if (hybiRemaining(wsctx) == 0) { + wsctx->hybiDecodeState = WS_HYBI_STATE_FRAME_COMPLETE; } /* number of not yet unmasked payload bytes: what we read here + what was @@ -486,13 +480,13 @@ webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len) rfbLog("%s_enter: len=%d; " "CTX: readlen=%d readPos=%p " "writeTo=%p " - "state=%d toRead=%d remaining=%d " - " nReadRaw=%d carrylen=%d carryBuf=%p\n", + "state=%d payloadtoRead=%d payloadRemaining=%llu " + " nReadPayload=%d carrylen=%d carryBuf=%p\n", __func__, len, wsctx->readlen, wsctx->readPos, wsctx->writePos, - wsctx->hybiDecodeState, wsctx->nToRead, hybiRemaining(wsctx), - wsctx->nReadRaw, wsctx->carrylen, wsctx->carryBuf); + wsctx->hybiDecodeState, wsctx->header.payloadLen, hybiRemaining(wsctx), + wsctx->nReadPayload, wsctx->carrylen, wsctx->carryBuf); switch (wsctx->hybiDecodeState){ int nInBuf; @@ -544,15 +538,15 @@ spor: rfbLog("%s_exit: len=%d; " "CTX: readlen=%d readPos=%p " "writePos=%p " - "state=%d toRead=%d remaining=%d " + "state=%d payloadtoRead=%d payloadRemaining=%d " "nRead=%d carrylen=%d carryBuf=%p " "result=%d " "errno=%d\n", __func__, len, wsctx->readlen, wsctx->readPos, wsctx->writePos, - wsctx->hybiDecodeState, wsctx->nToRead, hybiRemaining(wsctx), - wsctx->nReadRaw, wsctx->carrylen, wsctx->carryBuf, + wsctx->hybiDecodeState, wsctx->header.payloadLen, hybiRemaining(wsctx), + wsctx->nReadPayload, wsctx->carrylen, wsctx->carryBuf, result, errno); return result; diff --git a/libvncserver/ws_decode.h b/libvncserver/ws_decode.h index 07d37bd..2923e3d 100644 --- a/libvncserver/ws_decode.h +++ b/libvncserver/ws_decode.h @@ -124,8 +124,7 @@ typedef struct ws_ctx_s { int carrylen; int base64; ws_header_data_t header; - uint64_t nReadRaw; - uint64_t nToRead; + uint64_t nReadPayload; unsigned char continuation_opcode; wsEncodeFunc encode; wsDecodeFunc decode; -- cgit v1.2.3 From 051fe2a0090516f3688b40c6e6d966d95be0c326 Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Mon, 15 May 2017 00:17:53 +0200 Subject: websockets: hide decode debug output per default --- libvncserver/ws_decode.c | 73 +++++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 32 deletions(-) (limited to 'libvncserver/ws_decode.c') diff --git a/libvncserver/ws_decode.c b/libvncserver/ws_decode.c index 4616fdc..513fd4b 100644 --- a/libvncserver/ws_decode.c +++ b/libvncserver/ws_decode.c @@ -8,6 +8,17 @@ #define WS_HYBI_HEADER_LEN_EXTENDED 4 + WS_HYBI_MASK_LEN #define WS_HYBI_HEADER_LEN_LONG 10 + WS_HYBI_MASK_LEN +#undef WS_DECODE_DEBUG +/* set to 1 to produce very fine debugging output */ +#define WS_DECODE_DEBUG 0 + +#if WS_DECODE_DEBUG == 1 +#define ws_dbg(fmt, ...) rfbLog((fmt), ##__VA_ARGS) +#else +#define ws_dbg(fmt, ...) +#endif + + static inline int isControlFrame(ws_ctx_t *wsctx) { @@ -42,7 +53,7 @@ static void hybiDecodeCleanupForContinuation(ws_ctx_t *wsctx) { hybiDecodeCleanupBasics(wsctx); - rfbLog("clean up frame, but expect continuation with opcode %d\n", wsctx->continuation_opcode); + ws_dbg("clean up frame, but expect continuation with opcode %d\n", wsctx->continuation_opcode); } void @@ -50,7 +61,7 @@ hybiDecodeCleanupComplete(ws_ctx_t *wsctx) { hybiDecodeCleanupBasics(wsctx); wsctx->continuation_opcode = WS_OPCODE_INVALID; - rfbLog("cleaned up wsctx completely\n"); + ws_dbg("cleaned up wsctx completely\n"); } @@ -73,14 +84,14 @@ hybiReturnData(char *dst, int len, ws_ctx_t *wsctx, int *nWritten) if (wsctx->readlen > 0) { /* simply return what we have */ if (wsctx->readlen > len) { - rfbLog("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", len, wsctx->readPos, wsctx->readlen); + ws_dbg("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", len, wsctx->readPos, wsctx->readlen); memcpy(dst, wsctx->readPos, len); *nWritten = len; wsctx->readlen -= len; wsctx->readPos += len; nextState = WS_HYBI_STATE_DATA_AVAILABLE; } else { - rfbLog("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", wsctx->readlen, wsctx->readPos, wsctx->readlen); + ws_dbg("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", wsctx->readlen, wsctx->readPos, wsctx->readlen); memcpy(dst, wsctx->readPos, wsctx->readlen); *nWritten = wsctx->readlen; wsctx->readlen = 0; @@ -91,7 +102,7 @@ hybiReturnData(char *dst, int len, ws_ctx_t *wsctx, int *nWritten) nextState = WS_HYBI_STATE_DATA_NEEDED; } } - rfbLog("after copy: readPos=%p, readLen=%d\n", wsctx->readPos, wsctx->readlen); + ws_dbg("after copy: readPos=%p, readLen=%d\n", wsctx->readPos, wsctx->readlen); } else { /* it may happen that we read some bytes but could not decode them, * in that case, set errno to EAGAIN and return -1 */ @@ -122,9 +133,9 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) int n = ((uint64_t)WSHLENMAX) - wsctx->header.nRead; - rfbLog("header_read to %p with len=%d\n", headerDst, n); + ws_dbg("header_read to %p with len=%d\n", headerDst, n); ret = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, headerDst, n); - rfbLog("read %d bytes from socket\n", ret); + ws_dbg("read %d bytes from socket\n", ret); if (ret <= 0) { if (-1 == ret) { /* save errno because rfbErr() will tamper it */ @@ -150,7 +161,7 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) wsctx->header.opcode = wsctx->header.data->b0 & 0x0f; wsctx->header.fin = (wsctx->header.data->b0 & 0x80) >> 7; if (isControlFrame(wsctx)) { - rfbLog("is control frame\n"); + ws_dbg("is control frame\n"); /* is a control frame, leave remembered continuation opcode unchanged; * just check if there is a wrong fragmentation */ if (wsctx->header.fin == 0) { @@ -164,10 +175,10 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) goto err_cleanup_state; } } else { - rfbLog("not a control frame\n"); + ws_dbg("not a control frame\n"); /* not a control frame, check for continuation opcode */ if (wsctx->header.opcode == WS_OPCODE_CONTINUATION) { - rfbLog("cont_frame\n"); + ws_dbg("cont_frame\n"); /* do we have state (i.e., opcode) for continuation frame? */ if (wsctx->continuation_opcode == WS_OPCODE_INVALID) { rfbErr("no continuation state\n"); @@ -177,19 +188,19 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) /* otherwise, set opcode = continuation_opcode */ wsctx->header.opcode = wsctx->continuation_opcode; - rfbLog("set opcode to continuation_opcode: %d\n", wsctx->header.opcode); + ws_dbg("set opcode to continuation_opcode: %d\n", wsctx->header.opcode); } else { if (wsctx->header.fin == 0) { wsctx->continuation_opcode = wsctx->header.opcode; } else { wsctx->continuation_opcode = WS_OPCODE_INVALID; } - rfbLog("set continuation_opcode to %d\n", wsctx->continuation_opcode); + ws_dbg("set continuation_opcode to %d\n", wsctx->continuation_opcode); } } wsctx->header.payloadLen = (uint64_t)(wsctx->header.data->b1 & 0x7f); - rfbLog("first header bytes received; opcode=%d lenbyte=%d fin=%d\n", wsctx->header.opcode, wsctx->header.payloadLen, wsctx->header.fin); + ws_dbg("first header bytes received; opcode=%d lenbyte=%d fin=%d\n", wsctx->header.opcode, wsctx->header.payloadLen, wsctx->header.fin); /* * 4.3. Client-to-Server Masking @@ -223,11 +234,11 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) char *h = wsctx->codeBufDecode; int i; - rfbLog("Header:\n"); + ws_dbg("Header:\n"); for (i=0; i <10; i++) { - rfbLog("0x%02X\n", (unsigned char)h[i]); + ws_dbg("0x%02X\n", (unsigned char)h[i]); } - rfbLog("\n"); + ws_dbg("\n"); /* while RFC 6455 mandates that lengths MUST be encoded with the minimum * number of bytes, it does not specify for the server how to react on @@ -250,7 +261,7 @@ hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) *nPayload = wsctx->header.nRead - wsctx->header.headerLen; wsctx->nReadPayload = *nPayload; - rfbLog("header complete: state=%d headerlen=%d payloadlen=%llu writeTo=%p nPayload=%d\n", wsctx->hybiDecodeState, wsctx->header.headerLen, wsctx->header.payloadLen, wsctx->writePos, *nPayload); + ws_dbg("header complete: state=%d headerlen=%d payloadlen=%llu writeTo=%p nPayload=%d\n", wsctx->hybiDecodeState, wsctx->header.headerLen, wsctx->header.payloadLen, wsctx->writePos, *nPayload); return WS_HYBI_STATE_DATA_NEEDED; @@ -323,14 +334,14 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) /* -1 accounts for potential '\0' terminator for base64 decoding */ bufsize = wsctx->codeBufDecode + ARRAYSIZE(wsctx->codeBufDecode) - wsctx->writePos - 1; - rfbLog("bufsize=%d\n", bufsize); + ws_dbg("bufsize=%d\n", bufsize); if (hybiRemaining(wsctx) > bufsize) { nextRead = bufsize; } else { nextRead = hybiRemaining(wsctx); } - rfbLog("calling read with buf=%p and len=%d (decodebuf=%p headerLen=%d)\n", wsctx->writePos, nextRead, wsctx->codeBufDecode, wsctx->header.headerLen); + ws_dbg("calling read with buf=%p and len=%d (decodebuf=%p headerLen=%d)\n", wsctx->writePos, nextRead, wsctx->codeBufDecode, wsctx->header.headerLen); if (nextRead > 0) { /* decode more data */ @@ -344,7 +355,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) *sockRet = 0; return WS_HYBI_STATE_ERR; } else { - rfbLog("read %d bytes from socket; nRead=%d\n", n, wsctx->nReadPayload); + ws_dbg("read %d bytes from socket; nRead=%d\n", n, wsctx->nReadPayload); } } else { n = 0; @@ -360,7 +371,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) /* number of not yet unmasked payload bytes: what we read here + what was * carried over + what was read with the header */ toDecode = n + wsctx->carrylen + nInBuf; - rfbLog("toDecode=%d from n=%d carrylen=%d headerLen=%d\n", toDecode, n, wsctx->carrylen, wsctx->header.headerLen); + ws_dbg("toDecode=%d from n=%d carrylen=%d headerLen=%d\n", toDecode, n, wsctx->carrylen, wsctx->header.headerLen); if (toDecode < 0) { rfbErr("%s: internal error; negative number of bytes to decode: %d", __func__, toDecode); errno=EIO; @@ -376,7 +387,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) for (i = 0; i < (toDecode >> 2); i++) { data32[i] ^= wsctx->header.mask.u; } - rfbLog("mask decoding; i=%d toDecode=%d\n", i, toDecode); + ws_dbg("mask decoding; i=%d toDecode=%d\n", i, toDecode); if (wsctx->hybiDecodeState == WS_HYBI_STATE_FRAME_COMPLETE) { /* process the remaining bytes (if any) */ @@ -395,7 +406,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) errno = EIO; return WS_HYBI_STATE_ERR; } - rfbLog("carrying over %d bytes from %p to %p\n", wsctx->carrylen, wsctx->writePos + (i * 4), wsctx->carryBuf); + ws_dbg("carrying over %d bytes from %p to %p\n", wsctx->carrylen, wsctx->writePos + (i * 4), wsctx->carryBuf); memcpy(wsctx->carryBuf, data + (i * 4), wsctx->carrylen); wsctx->writePos -= wsctx->carrylen; } @@ -407,12 +418,12 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) /* this data is not returned as payload data */ if (hybiWsFrameComplete(wsctx)) { *(wsctx->writePos) = '\0'; - rfbLog("got close cmd %d, reason %d: %s\n", (int)(wsctx->writePos - hybiPayloadStart(wsctx)), WS_NTOH16(((uint16_t *)hybiPayloadStart(wsctx))[0]), &hybiPayloadStart(wsctx)[2]); + ws_dbg("got close cmd %d, reason %d: %s\n", (int)(wsctx->writePos - hybiPayloadStart(wsctx)), WS_NTOH16(((uint16_t *)hybiPayloadStart(wsctx))[0]), &hybiPayloadStart(wsctx)[2]); errno = ECONNRESET; *sockRet = -1; return WS_HYBI_STATE_FRAME_COMPLETE; } else { - rfbLog("got close cmd; waiting for %d more bytes to arrive\n", hybiRemaining(wsctx)); + ws_dbg("got close cmd; waiting for %d more bytes to arrive\n", hybiRemaining(wsctx)); *sockRet = -1; errno = EAGAIN; return WS_HYBI_STATE_CLOSE_REASON_PENDING; @@ -420,7 +431,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) break; case WS_OPCODE_TEXT_FRAME: data[toReturn] = '\0'; - rfbLog("Initiate Base64 decoding in %p with max size %d and '\\0' at %p\n", data, bufsize, data + toReturn); + ws_dbg("Initiate Base64 decoding in %p with max size %d and '\\0' at %p\n", data, bufsize, data + toReturn); if (-1 == (wsctx->readlen = b64_pton((char *)data, data, bufsize))) { rfbErr("%s: Base64 decode error; %s\n", __func__, strerror(errno)); } @@ -429,7 +440,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) case WS_OPCODE_BINARY_FRAME: wsctx->readlen = toReturn; wsctx->writePos = hybiPayloadStart(wsctx); - rfbLog("set readlen=%d writePos=%p\n", wsctx->readlen, wsctx->writePos); + ws_dbg("set readlen=%d writePos=%p\n", wsctx->readlen, wsctx->writePos); break; default: rfbErr("%s: unhandled opcode %d, b0: %02x, b1: %02x\n", __func__, (int)wsctx->header.opcode, wsctx->header.data->b0, wsctx->header.data->b1); @@ -476,8 +487,7 @@ webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len) int result = -1; /* int fin; */ /* not used atm */ - /* rfbLog(" <== %s[%d]: %d cl: %p, wsctx: %p-%p (%d)\n", __func__, gettid(), len, cl, wsctx, (char *)wsctx + sizeof(ws_ctx_t), sizeof(ws_ctx_t)); */ - rfbLog("%s_enter: len=%d; " + ws_dbg("%s_enter: len=%d; " "CTX: readlen=%d readPos=%p " "writeTo=%p " "state=%d payloadtoRead=%d payloadRemaining=%llu " @@ -520,9 +530,8 @@ webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len) /* single point of return, if someone has questions :-) */ spor: - /* rfbLog("%s: ret: %d/%d\n", __func__, result, len); */ if (wsctx->hybiDecodeState == WS_HYBI_STATE_FRAME_COMPLETE) { - rfbLog("frame received successfully, cleaning up: read=%d hlen=%d plen=%d\n", wsctx->header.nRead, wsctx->header.headerLen, wsctx->header.payloadLen); + ws_dbg("frame received successfully, cleaning up: read=%d hlen=%d plen=%d\n", wsctx->header.nRead, wsctx->header.headerLen, wsctx->header.payloadLen); if (wsctx->header.fin && !isControlFrame(wsctx)) { /* frame finished, cleanup state */ hybiDecodeCleanupComplete(wsctx); @@ -535,7 +544,7 @@ spor: hybiDecodeCleanupComplete(wsctx); } - rfbLog("%s_exit: len=%d; " + ws_dbg("%s_exit: len=%d; " "CTX: readlen=%d readPos=%p " "writePos=%p " "state=%d payloadtoRead=%d payloadRemaining=%d " -- cgit v1.2.3 From e8a1ca20352f14bf3b527bb1f148610fc1fb5247 Mon Sep 17 00:00:00 2001 From: Jocelyn Le Sage Date: Tue, 21 Feb 2017 06:36:15 -0500 Subject: Fixed compilation of websockets on system where there is no implementation of base64 functions. --- CMakeLists.txt | 30 +---- common/base64.c | 315 ++++++++++++++++++++++++++++++++++++++++++++++ common/base64.h | 10 ++ libvncserver/websockets.c | 8 +- libvncserver/ws_decode.c | 3 +- libvncserver/ws_decode.h | 3 - 6 files changed, 335 insertions(+), 34 deletions(-) create mode 100644 common/base64.c create mode 100644 common/base64.h (limited to 'libvncserver/ws_decode.c') diff --git a/CMakeLists.txt b/CMakeLists.txt index 75d6470..16f235e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,28 +174,6 @@ check_function_exists(strdup LIBVNCSERVER_HAVE_STRDUP) check_function_exists(strerror LIBVNCSERVER_HAVE_STRERROR) check_function_exists(strstr LIBVNCSERVER_HAVE_STRSTR) -# On systems such as GNU/Linux with glibc, __b64_ntop is defined in a -# separate library, libresolv. On some others, such as FreeBSD, it is -# part of libc itself. We first check if __b64_ntop is found without -# additional libraries, and then try looking for it with libresolv if -# the first test fails. -check_function_exists(__b64_ntop HAVE_B64_IN_LIBC) -if(NOT HAVE_B64_IN_LIBC) - set(CMAKE_REQUIRED_LIBRARIES resolv) - check_function_exists(__b64_ntop HAVE_B64_IN_LIBRESOLV) - set(CMAKE_REQUIRED_LIBRARIES) - - if(HAVE_B64_IN_LIBRESOLV) - set(RESOLV_LIB "resolv") - endif(HAVE_B64_IN_LIBRESOLV) - - # the function check somehow fails for apple but the function is there - if(APPLE) - set(RESOLV_LIB "resolv") - endif(APPLE) - -endif(NOT HAVE_B64_IN_LIBC) - if(Threads_FOUND) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) endif(Threads_FOUND) @@ -225,16 +203,16 @@ if(WITH_WEBSOCKETS AND LIBVNCSERVER_HAVE_SYS_UIO_H) if(GNUTLS_FOUND) set(LIBVNCSERVER_WITH_CLIENT_TLS 1) message(STATUS "Building websockets with GnuTLS") - set(WEBSOCKET_LIBRARIES ${RESOLV_LIB} ${GNUTLS_LIBRARIES}) + set(WEBSOCKET_LIBRARIES ${GNUTLS_LIBRARIES}) set(WSSRCS ${LIBVNCSERVER_DIR}/rfbssl_gnutls ${LIBVNCSERVER_DIR}/rfbcrypto_gnutls) include_directories(${GNUTLS_INCLUDE_DIR}) elseif(OPENSSL_FOUND) message(STATUS "Building websockets with OpenSSL") - set(WEBSOCKET_LIBRARIES ${RESOLV_LIB} ${OPENSSL_LIBRARIES}) + set(WEBSOCKET_LIBRARIES ${OPENSSL_LIBRARIES}) set(WSSRCS ${LIBVNCSERVER_DIR}/rfbssl_openssl ${LIBVNCSERVER_DIR}/rfbcrypto_openssl) else() message(STATUS "Building websockets without SSL") - set(WEBSOCKET_LIBRARIES ${RESOLV_LIB}) + set(WEBSOCKET_LIBRARIES) set(WSSRCS ${LIBVNCSERVER_DIR}/rfbssl_none.c ${LIBVNCSERVER_DIR}/rfbcrypto_included.c ${COMMON_DIR}/md5.c ${COMMON_DIR}/sha1.c) endif() endif(WITH_WEBSOCKETS AND LIBVNCSERVER_HAVE_SYS_UIO_H) @@ -388,11 +366,11 @@ if(LIBVNCSERVER_WITH_WEBSOCKETS) ${LIBVNCSERVER_SOURCES} ${LIBVNCSERVER_DIR}/websockets.c ${LIBVNCSERVER_DIR}/ws_decode.c + ${COMMON_DIR}/base64.c ${WSSRCS} ) endif(LIBVNCSERVER_WITH_WEBSOCKETS) - add_library(vncclient ${LIBVNCCLIENT_SOURCES}) add_library(vncserver ${LIBVNCSERVER_SOURCES}) if(WIN32) diff --git a/common/base64.c b/common/base64.c new file mode 100644 index 0000000..4e3685a --- /dev/null +++ b/common/base64.c @@ -0,0 +1,315 @@ +/* $OpenBSD: base64.c,v 1.8 2015/01/16 16:48:51 deraadt Exp $ */ + +/* + * Copyright (c) 1996 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Portions Copyright (c) 1995 by International Business Machines, Inc. + * + * International Business Machines, Inc. (hereinafter called IBM) grants + * permission under its copyrights to use, copy, modify, and distribute this + * Software with or without fee, provided that the above copyright notice and + * all paragraphs of this notice appear in all copies, and that the name of IBM + * not be used in connection with the marketing of any product incorporating + * the Software or modifications thereof, without specific, written prior + * permission. + * + * To the extent it has a right to do so, IBM grants an immunity from suit + * under its patents, if any, for the use, sale or manufacture of products to + * the extent that such products are used for performing Domain Name System + * dynamic updates in TCP/IP networks by means of the Software. No immunity is + * granted for any product per se or for any other function of any product. + * + * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN + * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) + The following encoding technique is taken from RFC 1521 by Borenstein + and Freed. It is reproduced here in a slightly edited form for + convenience. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a quantity. When fewer than 24 input + bits are available in an input group, zero bits are added (on the + right) to form an integral number of 6-bit groups. Padding at the + end of the data is performed using the '=' character. + + Since all base64 input is an integral number of octets, only the + ------------------------------------------------- + following cases can arise: + + (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded + output will be an integral multiple of 4 characters + with no "=" padding, + (2) the final quantum of encoding input is exactly 8 bits; + here, the final unit of encoded output will be two + characters followed by two "=" padding characters, or + (3) the final quantum of encoding input is exactly 16 bits; + here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + +int +__b64_ntop(src, srclength, target, targsize) + u_char const *src; + size_t srclength; + char *target; + size_t targsize; +{ + size_t datalength = 0; + u_char input[3]; + u_char output[4]; + int i; + + while (2 < srclength) { + input[0] = *src++; + input[1] = *src++; + input[2] = *src++; + srclength -= 3; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + output[3] = input[2] & 0x3f; + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + target[datalength++] = Base64[output[2]]; + target[datalength++] = Base64[output[3]]; + } + + /* Now we worry about padding. */ + if (0 != srclength) { + /* Get what's left. */ + input[0] = input[1] = input[2] = '\0'; + for (i = 0; i < srclength; i++) + input[i] = *src++; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + if (srclength == 1) + target[datalength++] = Pad64; + else + target[datalength++] = Base64[output[2]]; + target[datalength++] = Pad64; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; /* Returned value doesn't count \0. */ + return (datalength); +} + +/* skips all whitespace anywhere. + converts characters, four at a time, starting at (or after) + src from base - 64 numbers into three 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + */ + +int +__b64_pton(src, target, targsize) + char const *src; + u_char *target; + size_t targsize; +{ + int tarindex, state, ch; + u_char nextbyte; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = (unsigned char)*src++) != '\0') { + if (isspace(ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = strchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + nextbyte = ((pos - Base64) & 0x0f) << 4; + if (tarindex + 1 < targsize) + target[tarindex+1] = nextbyte; + else if (nextbyte) + return (-1); + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + nextbyte = ((pos - Base64) & 0x03) << 6; + if (tarindex + 1 < targsize) + target[tarindex+1] = nextbyte; + else if (nextbyte) + return (-1); + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = (unsigned char)*src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for (; ch != '\0'; ch = (unsigned char)*src++) + if (!isspace(ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = (unsigned char)*src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for (; ch != '\0'; ch = (unsigned char)*src++) + if (!isspace(ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && tarindex < targsize && + target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); +} diff --git a/common/base64.h b/common/base64.h new file mode 100644 index 0000000..9b86fc1 --- /dev/null +++ b/common/base64.h @@ -0,0 +1,10 @@ +#ifndef _BASE64_H +#define _BASE64_H + +extern int __b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize); +extern int __b64_pton(char const *src, u_char *target, size_t targsize); + +#define rfbBase64NtoP __b64_ntop +#define rfbBase64PtoN __b64_pton + +#endif /* _BASE64_H */ diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c index b9947c4..4ebff72 100644 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -52,7 +52,7 @@ #include "rfbssl.h" #include "rfbcrypto.h" #include "ws_decode.h" - +#include "base64.h" #if 0 #include @@ -117,8 +117,8 @@ static void webSocketsGenSha1Key(char *target, int size, char *key) iov[1].iov_base = GUID; iov[1].iov_len = sizeof(GUID) - 1; digestsha1(iov, 2, hash); - if (-1 == b64_ntop(hash, sizeof(hash), target, size)) - rfbErr("b64_ntop failed\n"); + if (-1 == rfbBase64NtoP(hash, sizeof(hash), target, size)) + rfbErr("rfbBase64NtoP failed\n"); } /* @@ -412,7 +412,7 @@ webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) } if (wsctx->base64) { - if (-1 == (ret = b64_ntop((unsigned char *)src, len, wsctx->codeBufEncode + sz, sizeof(wsctx->codeBufEncode) - sz))) { + if (-1 == (ret = rfbBase64NtoP((unsigned char *)src, len, wsctx->codeBufEncode + sz, sizeof(wsctx->codeBufEncode) - sz))) { rfbErr("%s: Base 64 encode failed\n", __func__); } else { if (ret != blen) diff --git a/libvncserver/ws_decode.c b/libvncserver/ws_decode.c index 513fd4b..441ebc7 100644 --- a/libvncserver/ws_decode.c +++ b/libvncserver/ws_decode.c @@ -1,4 +1,5 @@ #include "ws_decode.h" +#include "base64.h" #include #include @@ -432,7 +433,7 @@ hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) case WS_OPCODE_TEXT_FRAME: data[toReturn] = '\0'; ws_dbg("Initiate Base64 decoding in %p with max size %d and '\\0' at %p\n", data, bufsize, data + toReturn); - if (-1 == (wsctx->readlen = b64_pton((char *)data, data, bufsize))) { + if (-1 == (wsctx->readlen = rfbBase64PtoN((char *)data, data, bufsize))) { rfbErr("%s: Base64 decode error; %s\n", __func__, strerror(errno)); } wsctx->writePos = hybiPayloadStart(wsctx); diff --git a/libvncserver/ws_decode.h b/libvncserver/ws_decode.h index 2923e3d..709477a 100644 --- a/libvncserver/ws_decode.h +++ b/libvncserver/ws_decode.h @@ -3,9 +3,6 @@ #include #include -#ifndef _MSC_VER -#include /* __b64_ntop */ -#endif #if defined(__APPLE__) -- cgit v1.2.3