xemu/io/channel-websock.c
Daniel P. Berrange a7b20a8efa io: monitor encoutput buffer size from websocket GSource
The websocket GSource is monitoring the size of the rawoutput
buffer to determine if the channel can accepts more writes.
The rawoutput buffer, however, is merely a temporary staging
buffer before data is copied into the encoutput buffer. Thus
its size will always be zero when the GSource runs.

This flaw causes the encoutput buffer to grow without bound
if the other end of the underlying data channel doesn't
read data being sent. This can be seen with VNC if a client
is on a slow WAN link and the guest OS is sending many screen
updates. A malicious VNC client can act like it is on a slow
link by playing a video in the guest and then reading data
very slowly, causing QEMU host memory to expand arbitrarily.

This issue is assigned CVE-2017-15268, publically reported in

  https://bugs.launchpad.net/qemu/+bug/1718964

Reviewed-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2017-10-16 16:57:08 +01:00

1303 lines
41 KiB
C

/*
* QEMU I/O channels driver websockets
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/bswap.h"
#include "io/channel-websock.h"
#include "crypto/hash.h"
#include "trace.h"
#include <time.h>
/* Max amount to allow in rawinput/encoutput buffers */
#define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192
#define QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN 24
#define QIO_CHANNEL_WEBSOCK_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
#define QIO_CHANNEL_WEBSOCK_GUID_LEN strlen(QIO_CHANNEL_WEBSOCK_GUID)
#define QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL "sec-websocket-protocol"
#define QIO_CHANNEL_WEBSOCK_HEADER_VERSION "sec-websocket-version"
#define QIO_CHANNEL_WEBSOCK_HEADER_KEY "sec-websocket-key"
#define QIO_CHANNEL_WEBSOCK_HEADER_UPGRADE "upgrade"
#define QIO_CHANNEL_WEBSOCK_HEADER_HOST "host"
#define QIO_CHANNEL_WEBSOCK_HEADER_CONNECTION "connection"
#define QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY "binary"
#define QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE "Upgrade"
#define QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET "websocket"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Server: QEMU VNC\r\n" \
"Date: %s\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK \
"HTTP/1.1 101 Switching Protocols\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Upgrade: websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: %s\r\n" \
"Sec-WebSocket-Protocol: binary\r\n" \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_NOT_FOUND \
"HTTP/1.1 404 Not Found\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Connection: close\r\n" \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST \
"HTTP/1.1 400 Bad Request\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Connection: close\r\n" \
"Sec-WebSocket-Version: " \
QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_SERVER_ERR \
"HTTP/1.1 500 Internal Server Error\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Connection: close\r\n" \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_TOO_LARGE \
"HTTP/1.1 403 Request Entity Too Large\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Connection: close\r\n" \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM "\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_END "\r\n\r\n"
#define QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION "13"
#define QIO_CHANNEL_WEBSOCK_HTTP_METHOD "GET"
#define QIO_CHANNEL_WEBSOCK_HTTP_PATH "/"
#define QIO_CHANNEL_WEBSOCK_HTTP_VERSION "HTTP/1.1"
/* The websockets packet header is variable length
* depending on the size of the payload... */
/* ...length when using 7-bit payload length */
#define QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT 6
/* ...length when using 16-bit payload length */
#define QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT 8
/* ...length when using 64-bit payload length */
#define QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT 14
/* Length of the optional data mask field in header */
#define QIO_CHANNEL_WEBSOCK_HEADER_LEN_MASK 4
/* Maximum length that can fit in 7-bit payload size */
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT 126
/* Maximum length that can fit in 16-bit payload size */
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_16_BIT 65536
/* Magic 7-bit length to indicate use of 16-bit payload length */
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT 126
/* Magic 7-bit length to indicate use of 64-bit payload length */
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT 127
/* Bitmasks for accessing header fields */
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN 0x80
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE 0x0f
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK 0x80
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN 0x7f
#define QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK 0x8
typedef struct QIOChannelWebsockHeader QIOChannelWebsockHeader;
struct QEMU_PACKED QIOChannelWebsockHeader {
unsigned char b0;
unsigned char b1;
union {
struct QEMU_PACKED {
uint16_t l16;
QIOChannelWebsockMask m16;
} s16;
struct QEMU_PACKED {
uint64_t l64;
QIOChannelWebsockMask m64;
} s64;
QIOChannelWebsockMask m;
} u;
};
typedef struct QIOChannelWebsockHTTPHeader QIOChannelWebsockHTTPHeader;
struct QIOChannelWebsockHTTPHeader {
char *name;
char *value;
};
enum {
QIO_CHANNEL_WEBSOCK_OPCODE_CONTINUATION = 0x0,
QIO_CHANNEL_WEBSOCK_OPCODE_TEXT_FRAME = 0x1,
QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME = 0x2,
QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE = 0x8,
QIO_CHANNEL_WEBSOCK_OPCODE_PING = 0x9,
QIO_CHANNEL_WEBSOCK_OPCODE_PONG = 0xA
};
static void qio_channel_websock_handshake_send_res(QIOChannelWebsock *ioc,
const char *resmsg,
...)
{
va_list vargs;
char *response;
size_t responselen;
va_start(vargs, resmsg);
response = g_strdup_vprintf(resmsg, vargs);
responselen = strlen(response);
buffer_reserve(&ioc->encoutput, responselen);
buffer_append(&ioc->encoutput, response, responselen);
va_end(vargs);
}
static gchar *qio_channel_websock_date_str(void)
{
struct tm tm;
time_t now = time(NULL);
char datebuf[128];
gmtime_r(&now, &tm);
strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S GMT", &tm);
return g_strdup(datebuf);
}
static void qio_channel_websock_handshake_send_res_err(QIOChannelWebsock *ioc,
const char *resdata)
{
char *date = qio_channel_websock_date_str();
qio_channel_websock_handshake_send_res(ioc, resdata, date);
g_free(date);
}
enum {
QIO_CHANNEL_WEBSOCK_STATUS_NORMAL = 1000,
QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR = 1002,
QIO_CHANNEL_WEBSOCK_STATUS_INVALID_DATA = 1003,
QIO_CHANNEL_WEBSOCK_STATUS_POLICY = 1008,
QIO_CHANNEL_WEBSOCK_STATUS_TOO_LARGE = 1009,
QIO_CHANNEL_WEBSOCK_STATUS_SERVER_ERR = 1011,
};
static size_t
qio_channel_websock_extract_headers(QIOChannelWebsock *ioc,
char *buffer,
QIOChannelWebsockHTTPHeader *hdrs,
size_t nhdrsalloc,
Error **errp)
{
char *nl, *sep, *tmp;
size_t nhdrs = 0;
/*
* First parse the HTTP protocol greeting of format:
*
* $METHOD $PATH $VERSION
*
* e.g.
*
* GET / HTTP/1.1
*/
nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
if (!nl) {
error_setg(errp, "Missing HTTP header delimiter");
goto bad_request;
}
*nl = '\0';
tmp = strchr(buffer, ' ');
if (!tmp) {
error_setg(errp, "Missing HTTP path delimiter");
return 0;
}
*tmp = '\0';
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_METHOD)) {
error_setg(errp, "Unsupported HTTP method %s", buffer);
goto bad_request;
}
buffer = tmp + 1;
tmp = strchr(buffer, ' ');
if (!tmp) {
error_setg(errp, "Missing HTTP version delimiter");
goto bad_request;
}
*tmp = '\0';
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_PATH)) {
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_NOT_FOUND);
error_setg(errp, "Unexpected HTTP path %s", buffer);
return 0;
}
buffer = tmp + 1;
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_VERSION)) {
error_setg(errp, "Unsupported HTTP version %s", buffer);
goto bad_request;
}
buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
/*
* Now parse all the header fields of format
*
* $NAME: $VALUE
*
* e.g.
*
* Cache-control: no-cache
*/
do {
QIOChannelWebsockHTTPHeader *hdr;
nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
if (nl) {
*nl = '\0';
}
sep = strchr(buffer, ':');
if (!sep) {
error_setg(errp, "Malformed HTTP header");
goto bad_request;
}
*sep = '\0';
sep++;
while (*sep == ' ') {
sep++;
}
if (nhdrs >= nhdrsalloc) {
error_setg(errp, "Too many HTTP headers");
goto bad_request;
}
hdr = &hdrs[nhdrs++];
hdr->name = buffer;
hdr->value = sep;
/* Canonicalize header name for easier identification later */
for (tmp = hdr->name; *tmp; tmp++) {
*tmp = g_ascii_tolower(*tmp);
}
if (nl) {
buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
}
} while (nl != NULL);
return nhdrs;
bad_request:
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
return 0;
}
static const char *
qio_channel_websock_find_header(QIOChannelWebsockHTTPHeader *hdrs,
size_t nhdrs,
const char *name)
{
size_t i;
for (i = 0; i < nhdrs; i++) {
if (g_str_equal(hdrs[i].name, name)) {
return hdrs[i].value;
}
}
return NULL;
}
static void qio_channel_websock_handshake_send_res_ok(QIOChannelWebsock *ioc,
const char *key,
Error **errp)
{
char combined_key[QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
QIO_CHANNEL_WEBSOCK_GUID_LEN + 1];
char *accept = NULL;
char *date = qio_channel_websock_date_str();
g_strlcpy(combined_key, key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + 1);
g_strlcat(combined_key, QIO_CHANNEL_WEBSOCK_GUID,
QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
QIO_CHANNEL_WEBSOCK_GUID_LEN + 1);
/* hash and encode it */
if (qcrypto_hash_base64(QCRYPTO_HASH_ALG_SHA1,
combined_key,
QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
QIO_CHANNEL_WEBSOCK_GUID_LEN,
&accept,
errp) < 0) {
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_SERVER_ERR);
return;
}
qio_channel_websock_handshake_send_res(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK, date, accept);
g_free(date);
g_free(accept);
}
static void qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
char *buffer,
Error **errp)
{
QIOChannelWebsockHTTPHeader hdrs[32];
size_t nhdrs = G_N_ELEMENTS(hdrs);
const char *protocols = NULL, *version = NULL, *key = NULL,
*host = NULL, *connection = NULL, *upgrade = NULL;
nhdrs = qio_channel_websock_extract_headers(ioc, buffer, hdrs, nhdrs, errp);
if (!nhdrs) {
return;
}
protocols = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL);
if (!protocols) {
error_setg(errp, "Missing websocket protocol header data");
goto bad_request;
}
version = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_VERSION);
if (!version) {
error_setg(errp, "Missing websocket version header data");
goto bad_request;
}
key = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_KEY);
if (!key) {
error_setg(errp, "Missing websocket key header data");
goto bad_request;
}
host = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_HOST);
if (!host) {
error_setg(errp, "Missing websocket host header data");
goto bad_request;
}
connection = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_CONNECTION);
if (!connection) {
error_setg(errp, "Missing websocket connection header data");
goto bad_request;
}
upgrade = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_UPGRADE);
if (!upgrade) {
error_setg(errp, "Missing websocket upgrade header data");
goto bad_request;
}
if (!g_strrstr(protocols, QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY)) {
error_setg(errp, "No '%s' protocol is supported by client '%s'",
QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY, protocols);
goto bad_request;
}
if (!g_str_equal(version, QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION)) {
error_setg(errp, "Version '%s' is not supported by client '%s'",
QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION, version);
goto bad_request;
}
if (strlen(key) != QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN) {
error_setg(errp, "Key length '%zu' was not as expected '%d'",
strlen(key), QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN);
goto bad_request;
}
if (strcasecmp(connection, QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE) != 0) {
error_setg(errp, "No connection upgrade requested '%s'", connection);
goto bad_request;
}
if (strcasecmp(upgrade, QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET) != 0) {
error_setg(errp, "Incorrect upgrade method '%s'", upgrade);
goto bad_request;
}
qio_channel_websock_handshake_send_res_ok(ioc, key, errp);
return;
bad_request:
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
}
static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
Error **errp)
{
char *handshake_end;
ssize_t ret;
/* Typical HTTP headers from novnc are 512 bytes, so limiting
* total header size to 4096 is easily enough. */
size_t want = 4096 - ioc->encinput.offset;
buffer_reserve(&ioc->encinput, want);
ret = qio_channel_read(ioc->master,
(char *)buffer_end(&ioc->encinput), want, errp);
if (ret < 0) {
return -1;
}
ioc->encinput.offset += ret;
handshake_end = g_strstr_len((char *)ioc->encinput.buffer,
ioc->encinput.offset,
QIO_CHANNEL_WEBSOCK_HANDSHAKE_END);
if (!handshake_end) {
if (ioc->encinput.offset >= 4096) {
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_TOO_LARGE);
error_setg(errp,
"End of headers not found in first 4096 bytes");
return 1;
} else {
return 0;
}
}
*handshake_end = '\0';
qio_channel_websock_handshake_process(ioc,
(char *)ioc->encinput.buffer,
errp);
buffer_advance(&ioc->encinput,
handshake_end - (char *)ioc->encinput.buffer +
strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_END));
return 1;
}
static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
GIOCondition condition,
gpointer user_data)
{
QIOTask *task = user_data;
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(
qio_task_get_source(task));
Error *err = NULL;
ssize_t ret;
ret = qio_channel_write(wioc->master,
(char *)wioc->encoutput.buffer,
wioc->encoutput.offset,
&err);
if (ret < 0) {
trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
qio_task_set_error(task, err);
qio_task_complete(task);
return FALSE;
}
buffer_advance(&wioc->encoutput, ret);
if (wioc->encoutput.offset == 0) {
if (wioc->io_err) {
trace_qio_channel_websock_handshake_fail(
ioc, error_get_pretty(wioc->io_err));
qio_task_set_error(task, wioc->io_err);
wioc->io_err = NULL;
qio_task_complete(task);
} else {
trace_qio_channel_websock_handshake_complete(ioc);
qio_task_complete(task);
}
return FALSE;
}
trace_qio_channel_websock_handshake_pending(ioc, G_IO_OUT);
return TRUE;
}
static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
GIOCondition condition,
gpointer user_data)
{
QIOTask *task = user_data;
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(
qio_task_get_source(task));
Error *err = NULL;
int ret;
ret = qio_channel_websock_handshake_read(wioc, &err);
if (ret < 0) {
/*
* We only take this path on a fatal I/O error reading from
* client connection, as most of the time we have an
* HTTP 4xx err response to send instead
*/
trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
qio_task_set_error(task, err);
qio_task_complete(task);
return FALSE;
}
if (ret == 0) {
trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN);
/* need more data still */
return TRUE;
}
if (err) {
error_propagate(&wioc->io_err, err);
}
trace_qio_channel_websock_handshake_reply(ioc);
qio_channel_add_watch(
wioc->master,
G_IO_OUT,
qio_channel_websock_handshake_send,
task,
NULL);
return FALSE;
}
static void qio_channel_websock_encode_buffer(QIOChannelWebsock *ioc,
Buffer *output,
uint8_t opcode, Buffer *buffer)
{
size_t header_size;
union {
char buf[QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT];
QIOChannelWebsockHeader ws;
} header;
header.ws.b0 = QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN |
(opcode & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE);
if (buffer->offset < QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT) {
header.ws.b1 = (uint8_t)buffer->offset;
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
} else if (buffer->offset <
QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_16_BIT) {
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT;
header.ws.u.s16.l16 = cpu_to_be16((uint16_t)buffer->offset);
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT;
} else {
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT;
header.ws.u.s64.l64 = cpu_to_be64(buffer->offset);
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT;
}
header_size -= QIO_CHANNEL_WEBSOCK_HEADER_LEN_MASK;
trace_qio_channel_websock_encode(ioc, opcode, header_size, buffer->offset);
buffer_reserve(output, header_size + buffer->offset);
buffer_append(output, header.buf, header_size);
buffer_append(output, buffer->buffer, buffer->offset);
}
static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
{
if (!ioc->rawoutput.offset) {
return;
}
qio_channel_websock_encode_buffer(
ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME,
&ioc->rawoutput);
buffer_reset(&ioc->rawoutput);
}
static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *, Error **);
static void qio_channel_websock_write_close(QIOChannelWebsock *ioc,
uint16_t code, const char *reason)
{
buffer_reserve(&ioc->rawoutput, 2 + (reason ? strlen(reason) : 0));
*(uint16_t *)(ioc->rawoutput.buffer + ioc->rawoutput.offset) =
cpu_to_be16(code);
ioc->rawoutput.offset += 2;
if (reason) {
buffer_append(&ioc->rawoutput, reason, strlen(reason));
}
qio_channel_websock_encode_buffer(
ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE,
&ioc->rawoutput);
buffer_reset(&ioc->rawoutput);
qio_channel_websock_write_wire(ioc, NULL);
qio_channel_shutdown(ioc->master, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
}
static int qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
Error **errp)
{
unsigned char opcode, fin, has_mask;
size_t header_size;
size_t payload_len;
QIOChannelWebsockHeader *header =
(QIOChannelWebsockHeader *)ioc->encinput.buffer;
if (ioc->payload_remain) {
error_setg(errp,
"Decoding header but %zu bytes of payload remain",
ioc->payload_remain);
qio_channel_websock_write_close(
ioc, QIO_CHANNEL_WEBSOCK_STATUS_SERVER_ERR,
"internal server error");
return -1;
}
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT) {
/* header not complete */
return QIO_CHANNEL_ERR_BLOCK;
}
fin = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN;
opcode = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE;
has_mask = header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK;
payload_len = header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN;
/* Save or restore opcode. */
if (opcode) {
ioc->opcode = opcode;
} else {
opcode = ioc->opcode;
}
trace_qio_channel_websock_header_partial_decode(ioc, payload_len,
fin, opcode, (int)has_mask);
if (opcode == QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE) {
/* disconnect */
return 0;
}
/* Websocket frame sanity check:
* * Fragmentation is only supported for binary frames.
* * All frames sent by a client MUST be masked.
* * Only binary and ping/pong encoding is supported.
*/
if (!fin) {
if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
error_setg(errp, "only binary websocket frames may be fragmented");
qio_channel_websock_write_close(
ioc, QIO_CHANNEL_WEBSOCK_STATUS_POLICY ,
"only binary frames may be fragmented");
return -1;
}
} else {
if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME &&
opcode != QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE &&
opcode != QIO_CHANNEL_WEBSOCK_OPCODE_PING &&
opcode != QIO_CHANNEL_WEBSOCK_OPCODE_PONG) {
error_setg(errp, "unsupported opcode: %#04x; only binary, close, "
"ping, and pong websocket frames are supported", opcode);
qio_channel_websock_write_close(
ioc, QIO_CHANNEL_WEBSOCK_STATUS_INVALID_DATA ,
"only binary, close, ping, and pong frames are supported");
return -1;
}
}
if (!has_mask) {
error_setg(errp, "client websocket frames must be masked");
qio_channel_websock_write_close(
ioc, QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR,
"client frames must be masked");
return -1;
}
if (payload_len < QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT) {
ioc->payload_remain = payload_len;
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
ioc->mask = header->u.m;
} else if (opcode & QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK) {
error_setg(errp, "websocket control frame is too large");
qio_channel_websock_write_close(
ioc, QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR,
"control frame is too large");
return -1;
} else if (payload_len == QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT &&
ioc->encinput.offset >= QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT) {
ioc->payload_remain = be16_to_cpu(header->u.s16.l16);
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT;
ioc->mask = header->u.s16.m16;
} else if (payload_len == QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT &&
ioc->encinput.offset >= QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT) {
ioc->payload_remain = be64_to_cpu(header->u.s64.l64);
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT;
ioc->mask = header->u.s64.m64;
} else {
/* header not complete */
return QIO_CHANNEL_ERR_BLOCK;
}
trace_qio_channel_websock_header_full_decode(
ioc, header_size, ioc->payload_remain, ioc->mask.u);
buffer_advance(&ioc->encinput, header_size);
return 0;
}
static int qio_channel_websock_decode_payload(QIOChannelWebsock *ioc,
Error **errp)
{
size_t i;
size_t payload_len = 0;
uint32_t *payload32;
if (ioc->payload_remain) {
/* If we aren't at the end of the payload, then drop
* off the last bytes, so we're always multiple of 4
* for purpose of unmasking, except at end of payload
*/
if (ioc->encinput.offset < ioc->payload_remain) {
/* Wait for the entire payload before processing control frames
* because the payload will most likely be echoed back. */
if (ioc->opcode & QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK) {
return QIO_CHANNEL_ERR_BLOCK;
}
payload_len = ioc->encinput.offset - (ioc->encinput.offset % 4);
} else {
payload_len = ioc->payload_remain;
}
if (payload_len == 0) {
return QIO_CHANNEL_ERR_BLOCK;
}
ioc->payload_remain -= payload_len;
/* unmask frame */
/* process 1 frame (32 bit op) */
payload32 = (uint32_t *)ioc->encinput.buffer;
for (i = 0; i < payload_len / 4; i++) {
payload32[i] ^= ioc->mask.u;
}
/* process the remaining bytes (if any) */
for (i *= 4; i < payload_len; i++) {
ioc->encinput.buffer[i] ^= ioc->mask.c[i % 4];
}
}
trace_qio_channel_websock_payload_decode(
ioc, ioc->opcode, ioc->payload_remain);
if (ioc->opcode == QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
if (payload_len) {
/* binary frames are passed on */
buffer_reserve(&ioc->rawinput, payload_len);
buffer_append(&ioc->rawinput, ioc->encinput.buffer, payload_len);
}
} else if (ioc->opcode == QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE) {
/* close frames are echoed back */
error_setg(errp, "websocket closed by peer");
if (payload_len) {
/* echo client status */
qio_channel_websock_encode_buffer(
ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE,
&ioc->encinput);
qio_channel_websock_write_wire(ioc, NULL);
qio_channel_shutdown(ioc->master, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
} else {
/* send our own status */
qio_channel_websock_write_close(
ioc, QIO_CHANNEL_WEBSOCK_STATUS_NORMAL, "peer requested close");
}
return -1;
} else if (ioc->opcode == QIO_CHANNEL_WEBSOCK_OPCODE_PING) {
/* ping frames produce an immediate reply */
buffer_reset(&ioc->ping_reply);
qio_channel_websock_encode_buffer(
ioc, &ioc->ping_reply, QIO_CHANNEL_WEBSOCK_OPCODE_PONG,
&ioc->encinput);
} /* pong frames are ignored */
if (payload_len) {
buffer_advance(&ioc->encinput, payload_len);
}
return 0;
}
QIOChannelWebsock *
qio_channel_websock_new_server(QIOChannel *master)
{
QIOChannelWebsock *wioc;
QIOChannel *ioc;
wioc = QIO_CHANNEL_WEBSOCK(object_new(TYPE_QIO_CHANNEL_WEBSOCK));
ioc = QIO_CHANNEL(wioc);
wioc->master = master;
if (qio_channel_has_feature(master, QIO_CHANNEL_FEATURE_SHUTDOWN)) {
qio_channel_set_feature(ioc, QIO_CHANNEL_FEATURE_SHUTDOWN);
}
object_ref(OBJECT(master));
trace_qio_channel_websock_new_server(wioc, master);
return wioc;
}
void qio_channel_websock_handshake(QIOChannelWebsock *ioc,
QIOTaskFunc func,
gpointer opaque,
GDestroyNotify destroy)
{
QIOTask *task;
task = qio_task_new(OBJECT(ioc),
func,
opaque,
destroy);
trace_qio_channel_websock_handshake_start(ioc);
trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN);
qio_channel_add_watch(ioc->master,
G_IO_IN,
qio_channel_websock_handshake_io,
task,
NULL);
}
static void qio_channel_websock_finalize(Object *obj)
{
QIOChannelWebsock *ioc = QIO_CHANNEL_WEBSOCK(obj);
buffer_free(&ioc->encinput);
buffer_free(&ioc->encoutput);
buffer_free(&ioc->rawinput);
buffer_free(&ioc->rawoutput);
buffer_free(&ioc->ping_reply);
object_unref(OBJECT(ioc->master));
if (ioc->io_tag) {
g_source_remove(ioc->io_tag);
}
if (ioc->io_err) {
error_free(ioc->io_err);
}
}
static ssize_t qio_channel_websock_read_wire(QIOChannelWebsock *ioc,
Error **errp)
{
ssize_t ret;
if (ioc->encinput.offset < 4096) {
size_t want = 4096 - ioc->encinput.offset;
buffer_reserve(&ioc->encinput, want);
ret = qio_channel_read(ioc->master,
(char *)ioc->encinput.buffer +
ioc->encinput.offset,
want,
errp);
if (ret < 0) {
return ret;
}
if (ret == 0 && ioc->encinput.offset == 0) {
ioc->io_eof = TRUE;
return 0;
}
ioc->encinput.offset += ret;
}
while (ioc->encinput.offset != 0) {
if (ioc->payload_remain == 0) {
ret = qio_channel_websock_decode_header(ioc, errp);
if (ret < 0) {
return ret;
}
}
ret = qio_channel_websock_decode_payload(ioc, errp);
if (ret < 0) {
return ret;
}
}
return 1;
}
static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *ioc,
Error **errp)
{
ssize_t ret;
ssize_t done = 0;
/* ping replies take priority over binary data */
if (!ioc->ping_reply.offset) {
qio_channel_websock_encode(ioc);
} else if (!ioc->encoutput.offset) {
buffer_move_empty(&ioc->encoutput, &ioc->ping_reply);
}
while (ioc->encoutput.offset > 0) {
ret = qio_channel_write(ioc->master,
(char *)ioc->encoutput.buffer,
ioc->encoutput.offset,
errp);
if (ret < 0) {
if (ret == QIO_CHANNEL_ERR_BLOCK &&
done > 0) {
return done;
} else {
return ret;
}
}
buffer_advance(&ioc->encoutput, ret);
done += ret;
}
return done;
}
static void qio_channel_websock_flush_free(gpointer user_data)
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(user_data);
object_unref(OBJECT(wioc));
}
static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc);
static gboolean qio_channel_websock_flush(QIOChannel *ioc,
GIOCondition condition,
gpointer user_data)
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(user_data);
ssize_t ret;
if (condition & G_IO_OUT) {
ret = qio_channel_websock_write_wire(wioc, &wioc->io_err);
if (ret < 0) {
goto cleanup;
}
}
if (condition & G_IO_IN) {
ret = qio_channel_websock_read_wire(wioc, &wioc->io_err);
if (ret < 0) {
goto cleanup;
}
}
cleanup:
qio_channel_websock_set_watch(wioc);
return FALSE;
}
static void qio_channel_websock_unset_watch(QIOChannelWebsock *ioc)
{
if (ioc->io_tag) {
g_source_remove(ioc->io_tag);
ioc->io_tag = 0;
}
}
static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc)
{
GIOCondition cond = 0;
qio_channel_websock_unset_watch(ioc);
if (ioc->io_err) {
return;
}
if (ioc->encoutput.offset || ioc->ping_reply.offset) {
cond |= G_IO_OUT;
}
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER &&
!ioc->io_eof) {
cond |= G_IO_IN;
}
if (cond) {
object_ref(OBJECT(ioc));
ioc->io_tag =
qio_channel_add_watch(ioc->master,
cond,
qio_channel_websock_flush,
ioc,
qio_channel_websock_flush_free);
}
}
static ssize_t qio_channel_websock_readv(QIOChannel *ioc,
const struct iovec *iov,
size_t niov,
int **fds,
size_t *nfds,
Error **errp)
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
size_t i;
ssize_t got = 0;
ssize_t ret;
if (wioc->io_err) {
error_propagate(errp, error_copy(wioc->io_err));
return -1;
}
if (!wioc->rawinput.offset) {
ret = qio_channel_websock_read_wire(QIO_CHANNEL_WEBSOCK(ioc), errp);
if (ret < 0) {
return ret;
}
}
for (i = 0 ; i < niov ; i++) {
size_t want = iov[i].iov_len;
if (want > (wioc->rawinput.offset - got)) {
want = (wioc->rawinput.offset - got);
}
memcpy(iov[i].iov_base,
wioc->rawinput.buffer + got,
want);
got += want;
if (want < iov[i].iov_len) {
break;
}
}
buffer_advance(&wioc->rawinput, got);
qio_channel_websock_set_watch(wioc);
return got;
}
static ssize_t qio_channel_websock_writev(QIOChannel *ioc,
const struct iovec *iov,
size_t niov,
int *fds,
size_t nfds,
Error **errp)
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
size_t i;
ssize_t done = 0;
ssize_t ret;
if (wioc->io_err) {
error_propagate(errp, error_copy(wioc->io_err));
return -1;
}
if (wioc->io_eof) {
error_setg(errp, "%s", "Broken pipe");
return -1;
}
for (i = 0; i < niov; i++) {
size_t want = iov[i].iov_len;
if ((want + wioc->rawoutput.offset) > QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
want = (QIO_CHANNEL_WEBSOCK_MAX_BUFFER - wioc->rawoutput.offset);
}
if (want == 0) {
goto done;
}
buffer_reserve(&wioc->rawoutput, want);
buffer_append(&wioc->rawoutput, iov[i].iov_base, want);
done += want;
if (want < iov[i].iov_len) {
break;
}
}
done:
ret = qio_channel_websock_write_wire(wioc, errp);
if (ret < 0 &&
ret != QIO_CHANNEL_ERR_BLOCK) {
qio_channel_websock_unset_watch(wioc);
return -1;
}
qio_channel_websock_set_watch(wioc);
if (done == 0) {
return QIO_CHANNEL_ERR_BLOCK;
}
return done;
}
static int qio_channel_websock_set_blocking(QIOChannel *ioc,
bool enabled,
Error **errp)
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
qio_channel_set_blocking(wioc->master, enabled, errp);
return 0;
}
static void qio_channel_websock_set_delay(QIOChannel *ioc,
bool enabled)
{
QIOChannelWebsock *tioc = QIO_CHANNEL_WEBSOCK(ioc);
qio_channel_set_delay(tioc->master, enabled);
}
static void qio_channel_websock_set_cork(QIOChannel *ioc,
bool enabled)
{
QIOChannelWebsock *tioc = QIO_CHANNEL_WEBSOCK(ioc);
qio_channel_set_cork(tioc->master, enabled);
}
static int qio_channel_websock_shutdown(QIOChannel *ioc,
QIOChannelShutdown how,
Error **errp)
{
QIOChannelWebsock *tioc = QIO_CHANNEL_WEBSOCK(ioc);
return qio_channel_shutdown(tioc->master, how, errp);
}
static int qio_channel_websock_close(QIOChannel *ioc,
Error **errp)
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
trace_qio_channel_websock_close(ioc);
return qio_channel_close(wioc->master, errp);
}
typedef struct QIOChannelWebsockSource QIOChannelWebsockSource;
struct QIOChannelWebsockSource {
GSource parent;
QIOChannelWebsock *wioc;
GIOCondition condition;
};
static gboolean
qio_channel_websock_source_check(GSource *source)
{
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
GIOCondition cond = 0;
if (wsource->wioc->rawinput.offset || wsource->wioc->io_eof) {
cond |= G_IO_IN;
}
if (wsource->wioc->encoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
cond |= G_IO_OUT;
}
return cond & wsource->condition;
}
static gboolean
qio_channel_websock_source_prepare(GSource *source,
gint *timeout)
{
*timeout = -1;
return qio_channel_websock_source_check(source);
}
static gboolean
qio_channel_websock_source_dispatch(GSource *source,
GSourceFunc callback,
gpointer user_data)
{
QIOChannelFunc func = (QIOChannelFunc)callback;
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
return (*func)(QIO_CHANNEL(wsource->wioc),
qio_channel_websock_source_check(source),
user_data);
}
static void
qio_channel_websock_source_finalize(GSource *source)
{
QIOChannelWebsockSource *ssource = (QIOChannelWebsockSource *)source;
object_unref(OBJECT(ssource->wioc));
}
GSourceFuncs qio_channel_websock_source_funcs = {
qio_channel_websock_source_prepare,
qio_channel_websock_source_check,
qio_channel_websock_source_dispatch,
qio_channel_websock_source_finalize
};
static GSource *qio_channel_websock_create_watch(QIOChannel *ioc,
GIOCondition condition)
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
QIOChannelWebsockSource *ssource;
GSource *source;
source = g_source_new(&qio_channel_websock_source_funcs,
sizeof(QIOChannelWebsockSource));
ssource = (QIOChannelWebsockSource *)source;
ssource->wioc = wioc;
object_ref(OBJECT(wioc));
ssource->condition = condition;
qio_channel_websock_set_watch(wioc);
return source;
}
static void qio_channel_websock_class_init(ObjectClass *klass,
void *class_data G_GNUC_UNUSED)
{
QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
ioc_klass->io_writev = qio_channel_websock_writev;
ioc_klass->io_readv = qio_channel_websock_readv;
ioc_klass->io_set_blocking = qio_channel_websock_set_blocking;
ioc_klass->io_set_cork = qio_channel_websock_set_cork;
ioc_klass->io_set_delay = qio_channel_websock_set_delay;
ioc_klass->io_close = qio_channel_websock_close;
ioc_klass->io_shutdown = qio_channel_websock_shutdown;
ioc_klass->io_create_watch = qio_channel_websock_create_watch;
}
static const TypeInfo qio_channel_websock_info = {
.parent = TYPE_QIO_CHANNEL,
.name = TYPE_QIO_CHANNEL_WEBSOCK,
.instance_size = sizeof(QIOChannelWebsock),
.instance_finalize = qio_channel_websock_finalize,
.class_init = qio_channel_websock_class_init,
};
static void qio_channel_websock_register_types(void)
{
type_register_static(&qio_channel_websock_info);
}
type_init(qio_channel_websock_register_types);