Commit message: None
CVE: CVE-2018-6651
CWE: CWE-352
Vulnerability description: In the uncurl_ws_accept function in uncurl.c in uncurl before 0.07, as used in Parsec before 140-3, insufficient Origin header validation (accepting an arbitrary substring match) for WebSocket API requests allows remote attackers to bypass intended access restrictions. In Parsec, this means full control over the victim's computer.

Project: uncurl

UNCURL_EXPORT int32_t uncurl_accept(struct uncurl_tls_ctx *uc_tls, struct uncurl_conn *ucc,
	struct uncurl_conn **ucc_new_in, int32_t scheme)
{
	int32_t r = UNCURL_ERR_DEFAULT;
	int32_t e;

	struct uncurl_conn *ucc_new = *ucc_new_in = uncurl_new_conn(ucc);

	struct net_context *new_net = NULL;

	e = net_accept(ucc->net, &new_net);
	if (e != UNCURL_OK) {r = e; goto uncurl_accept_end;}

	ucc_new->net = new_net;
	uncurl_attach_net(ucc_new);

	if (scheme == UNCURL_HTTPS || scheme == UNCURL_WSS) {
		if (!uc_tls) return UNCURL_TLS_ERR_CONTEXT;

		e = tls_accept(&ucc_new->tls, uc_tls->tlss, ucc_new->net, &ucc_new->topts);
		if (e != UNCURL_OK) {r = e; goto uncurl_accept_end;}

		uncurl_attach_tls(ucc_new);
	}

	return UNCURL_OK;

	uncurl_accept_end:

	free(ucc_new);
	*ucc_new_in = NULL;

	return r;
}

Project: uncurl

static void uncurl_attach_net(struct uncurl_conn *ucc)
{
	ucc->ctx = ucc->net;
	ucc->read = net_read;
	ucc->write = net_write;
}

Project: uncurl

UNCURL_EXPORT int8_t uncurl_check_header(struct uncurl_conn *ucc, char *name, char *subval)
{
	int32_t e;
	char *val = NULL;

	e = uncurl_get_header_str(ucc, name, &val);
	if (e == UNCURL_OK && strstr(http_lc(val), subval)) return 1;

	return 0;
}

Project: uncurl

UNCURL_EXPORT void uncurl_close(struct uncurl_conn *ucc)
{
	if (!ucc) return;

	tls_close(ucc->tls);
	net_close(ucc->net);
	free(ucc->host);
	http_free_header(ucc->hin);
	free(ucc->hout);
	free(ucc->netbuf);
	free(ucc);
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_connect(struct uncurl_tls_ctx *uc_tls, struct uncurl_conn *ucc,
	int32_t scheme, char *host, uint16_t port)
{
	int32_t e;

	ucc->host = strdup(host);
	ucc->port = port;

	char ip4[LEN_IP4];
	e = net_getip4(ucc->host, ip4, LEN_IP4);
	if (e != UNCURL_OK) return e;

	e = net_connect(&ucc->net, ip4, ucc->port, &ucc->nopts);
	if (e != UNCURL_OK) return e;

	uncurl_attach_net(ucc);

	if (scheme == UNCURL_HTTPS || scheme == UNCURL_WSS) {
		if (!uc_tls) return UNCURL_TLS_ERR_CONTEXT;

		e = tls_connect(&ucc->tls, uc_tls->tlss, ucc->net, ucc->host, &ucc->topts);
		if (e != UNCURL_OK) return e;

		uncurl_attach_tls(ucc);
	}

	return UNCURL_OK;
}

Project: uncurl

static void uncurl_default_opts(struct uncurl_opts *opts)
{
	opts->max_header = 1024;
	opts->max_body = 128 * 1024 * 1024;
}

Project: uncurl

UNCURL_EXPORT void uncurl_free_header(struct uncurl_conn *ucc)
{
	free(ucc->hout);
	ucc->hout = NULL;
}

Project: uncurl

UNCURL_EXPORT void uncurl_free_info(struct uncurl_info *uci)
{
	free(uci->host);
	free(uci->path);
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_get_header(struct uncurl_conn *ucc, char *key, int32_t *val_int, char **val_str)
{
	return http_get_header(ucc->hin, key, val_int, val_str);
}

Project: uncurl

UNCURL_EXPORT void uncurl_get_socket(struct uncurl_conn *ucc, void *socket)
{
	net_get_socket(ucc->net, socket);
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_get_status_code(struct uncurl_conn *ucc, int32_t *status_code)
{
	*status_code = 0;
	return http_get_status_code(ucc->hin, status_code);
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_listen(struct uncurl_conn *ucc, char *bind_ip4, uint16_t port)
{
	int32_t e;

	ucc->port = port;

	e = net_listen(&ucc->net, bind_ip4, ucc->port, &ucc->nopts);
	if (e != UNCURL_OK) return e;

	return UNCURL_OK;
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_new_tls_ctx(struct uncurl_tls_ctx **uc_tls_in)
{
	int32_t e;

	struct uncurl_tls_ctx *uc_tls = *uc_tls_in = calloc(1, sizeof(struct uncurl_tls_ctx));

	e = tlss_alloc(&uc_tls->tlss);
	if (e == UNCURL_OK) return e;

	uncurl_free_tls_ctx(uc_tls);
	*uc_tls_in = NULL;

	return e;
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_parse_url(char *url, struct uncurl_info *uci)
{
	memset(uci, 0, sizeof(struct uncurl_info));

	return http_parse_url(url, &uci->scheme, &uci->host, &uci->port, &uci->path);
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_poll(struct uncurl_conn *ucc, int32_t timeout_ms)
{
	return net_poll(ucc->net, NET_POLLIN, timeout_ms);
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_read_body_all(struct uncurl_conn *ucc, char **body, uint32_t *body_len)
{
	int32_t r = UNCURL_ERR_DEFAULT;
	int32_t e;

	*body = NULL;
	*body_len = 0;

	if (uncurl_check_header(ucc, "Transfer-Encoding", "chunked")) {
		e = uncurl_response_body_chunked(ucc, body, body_len);
		if (e != UNCURL_OK) {r = e; goto uncurl_response_body_end;}

		r = UNCURL_OK;
	}

	if (r != UNCURL_OK) {
		e = uncurl_get_header_int(ucc, "Content-Length", (int32_t *) body_len);
		if (e != UNCURL_OK) {r = e; goto uncurl_response_body_end;}

		if (*body_len == 0) {r = UNCURL_ERR_NO_BODY; goto uncurl_response_body_end;}
		if (*body_len > ucc->opts.max_body) {r = UNCURL_ERR_MAX_BODY; goto uncurl_response_body_end;}

		*body = calloc(*body_len + 1, 1);

		e = ucc->read(ucc->ctx, *body, *body_len);

		if (e != UNCURL_OK) {r = e; goto uncurl_response_body_end;}

		r = UNCURL_OK;
	}

	uncurl_response_body_end:

	if (r != UNCURL_OK) {
		free(*body);
		*body = NULL;
	}

	return r;
}

Project: uncurl

static int32_t uncurl_read_chunk_len(struct uncurl_conn *ucc, uint32_t *len)
{
	int32_t r = UNCURL_ERR_MAX_CHUNK;

	char chunk_len[LEN_CHUNK];
	memset(chunk_len, 0, LEN_CHUNK);

	for (uint32_t x = 0; x < LEN_CHUNK - 1; x++) {
		int32_t e;

		e = ucc->read(ucc->ctx, chunk_len + x, 1);
		if (e != UNCURL_OK) {r = e; break;}

		if (x > 0 && chunk_len[x - 1] == '\r' && chunk_len[x] == '\n') {
			chunk_len[x - 1] = '\0';
			*len = strtoul(chunk_len, NULL, 16);
			return UNCURL_OK;
		}
	}

	*len = 0;

	return r;
}

Project: uncurl

static int32_t uncurl_read_header_(struct uncurl_conn *ucc, char **header)
{
	int32_t r = UNCURL_ERR_MAX_HEADER;

	uint32_t max_header = ucc->opts.max_header;
	char *h = *header = calloc(max_header, 1);

	uint32_t x = 0;
	for (; x < max_header - 1; x++) {
		int32_t e;

		e = ucc->read(ucc->ctx, h + x, 1);
		if (e != UNCURL_OK) {r = e; break;}

		if (x > 2 && h[x - 3] == '\r' && h[x - 2] == '\n' && h[x - 1] == '\r' && h[x] == '\n')
			return UNCURL_OK;
	}

	free(h);
	*header = NULL;

	return r;
}

Project: uncurl

static int32_t uncurl_response_body_chunked(struct uncurl_conn *ucc, char **body, uint32_t *body_len)
{
	uint32_t offset = 0;
	uint32_t chunk_len = 0;

	do {
		int32_t e;

		e = uncurl_read_chunk_len(ucc, &chunk_len);
		if (e != UNCURL_OK) return e;
		if (offset + chunk_len > ucc->opts.max_body) return UNCURL_ERR_MAX_BODY;

		*body = realloc(*body, offset + chunk_len + 2);

		e = ucc->read(ucc->ctx, *body + offset, chunk_len + 2);
		if (e != UNCURL_OK) return e;

		offset += chunk_len;

	} while (chunk_len > 0);

	(*body)[offset] = '\0';
	*body_len = offset;

	return UNCURL_OK;
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_set_cacert(struct uncurl_tls_ctx *uc_tls, char *cacert, size_t size)
{
	return tlss_load_cacert(uc_tls->tlss, cacert, size);
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_set_cacert_file(struct uncurl_tls_ctx *uc_tls, char *cacert_file)
{
	return tlss_load_cacert_file(uc_tls->tlss, cacert_file);
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_set_cert_and_key_file(struct uncurl_tls_ctx *uc_tls, char *cert_file, char *key_file)
{
	return tlss_load_cert_and_key_file(uc_tls->tlss, cert_file, key_file);
}

Project: uncurl

UNCURL_EXPORT void uncurl_set_header_int(struct uncurl_conn *ucc, char *name, int32_t value)
{
	ucc->hout = http_set_header(ucc->hout, name, HTTP_INT, &value);
}

Project: uncurl

UNCURL_EXPORT void uncurl_set_option(struct uncurl_conn *ucc, int32_t opt, int32_t val)
{
	switch (opt) {
		case UNCURL_OPT_MAX_HEADER:
			ucc->opts.max_header = (uint32_t) val; break;
		case UNCURL_OPT_MAX_BODY:
			ucc->opts.max_body = (uint32_t) val; break;

		case UNCURL_NOPT_READ_TIMEOUT:
			ucc->nopts.read_timeout = val; break;
		case UNCURL_NOPT_CONNECT_TIMEOUT:
			ucc->nopts.connect_timeout = val; break;
		case UNCURL_NOPT_ACCEPT_TIMEOUT:
			ucc->nopts.accept_timeout = val; break;
		case UNCURL_NOPT_READ_BUF:
			ucc->nopts.read_buf = val; break;
		case UNCURL_NOPT_WRITE_BUF:
			ucc->nopts.write_buf = val; break;
		case UNCURL_NOPT_KEEPALIVE:
			ucc->nopts.keepalive = val; break;
		case UNCURL_NOPT_TCP_NODELAY:
			ucc->nopts.tcp_nodelay = val; break;
		case UNCURL_NOPT_REUSEADDR:
			ucc->nopts.reuseaddr = val; break;

		case UNCURL_TOPT_VERIFY_HOST:
			ucc->topts.verify_host = val; break;
	}
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_write_body(struct uncurl_conn *ucc, char *body, uint32_t body_len)
{
	int32_t e;

	e = ucc->write(ucc->ctx, body, body_len);

	return e;
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_write_header(struct uncurl_conn *ucc, char *str0, char *str1, int32_t type)
{
	int32_t e;

	char *h = (type == UNCURL_REQUEST) ?
		http_request(str0, ucc->host, str1, ucc->hout) :
		http_response(str0, str1, ucc->hout);

	e = ucc->write(ucc->ctx, h, (uint32_t) strlen(h));

	free(h);

	return e;
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_ws_close(struct uncurl_conn *ucc, uint16_t status_code)
{
	uint16_t status_code_be = htons(status_code);

	return uncurl_ws_write(ucc, (char *) &status_code_be, sizeof(uint16_t), UNCURL_WSOP_CLOSE);
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_ws_connect(struct uncurl_conn *ucc, char *path, char *origin)
{
	int32_t e;
	int32_t r = UNCURL_ERR_DEFAULT;

	char *sec_key = ws_create_key(&ucc->seed);
	uncurl_set_header_str(ucc, "Upgrade", "websocket");
	uncurl_set_header_str(ucc, "Connection", "Upgrade");
	uncurl_set_header_str(ucc, "Sec-WebSocket-Key", sec_key);
	uncurl_set_header_str(ucc, "Sec-WebSocket-Version", "13");

	if (origin)
		uncurl_set_header_str(ucc, "Origin", origin);

	e = uncurl_write_header(ucc, "GET", path, UNCURL_REQUEST);
	if (e != UNCURL_OK) {r = e; goto uncurl_ws_connect_end;}

	e = uncurl_read_header(ucc);
	if (e != UNCURL_OK) {r = e; goto uncurl_ws_connect_end;}

	int32_t status_code = 0;
	e = uncurl_get_status_code(ucc, &status_code);
	if (e != UNCURL_OK) {r = e; goto uncurl_ws_connect_end;}
	if (status_code != 101) {r = UNCURL_WS_ERR_STATUS; goto uncurl_ws_connect_end;}

	char *server_sec_key = NULL;
	e = uncurl_get_header_str(ucc, "Sec-WebSocket-Accept", &server_sec_key);
	if (e != UNCURL_OK) {r = e; goto uncurl_ws_connect_end;}
	if (!ws_validate_key(sec_key, server_sec_key)) {r = UNCURL_WS_ERR_KEY; goto uncurl_ws_connect_end;}

	ucc->ws_mask = 1;

	r = UNCURL_OK;

	uncurl_ws_connect_end:

	free(sec_key);

	return r;
}

Project: uncurl

UNCURL_EXPORT int32_t uncurl_ws_write(struct uncurl_conn *ucc, char *buf, uint32_t buf_len, uint8_t opcode)
{
	struct ws_header h;
	memset(&h, 0, sizeof(struct ws_header));

	h.fin = 1;
	h.mask = ucc->ws_mask;
	h.opcode = opcode;
	h.payload_len = buf_len;

	if (h.payload_len + WS_HEADER_SIZE > ucc->netbuf_size) {
		free(ucc->netbuf);
		ucc->netbuf_size = h.payload_len + WS_HEADER_SIZE;
		ucc->netbuf = malloc((size_t) ucc->netbuf_size);
	}

	uint64_t out_size = 0;
	ws_serialize(&h, &ucc->seed, buf, ucc->netbuf, &out_size);

	return ucc->write(ucc->ctx, ucc->netbuf, (uint32_t) out_size);
}