Commit message: Do some random sanity checking for stratum message parsing
CVE: CVE-2014-4502
CWE: CWE-119
Vulnerability description: Multiple heap-based buffer overflows in the parse_notify function in sgminer before 4.2.2, cgminer before 4.3.5, and BFGMiner before 4.1.0 allow remote pool servers to have unspecified impact via a (1) large or (2) negative value in the Extranonc2_size parameter in a mining.subscribe response and a crafted mining.notify request.

Project: cgminer
File: util.c

bool extract_sockaddr(char *url, char **sockaddr_url, char **sockaddr_port)
{
	char *url_begin, *url_end, *ipv6_begin, *ipv6_end, *port_start = NULL;
	char url_address[256], port[6];
	int url_len, port_len = 0;

	*sockaddr_url = url;
	url_begin = strstr(url, "//");
	if (!url_begin)
		url_begin = url;
	else
		url_begin += 2;

	/* Look for numeric ipv6 entries */
	ipv6_begin = strstr(url_begin, "[");
	ipv6_end = strstr(url_begin, "]");
	if (ipv6_begin && ipv6_end && ipv6_end > ipv6_begin)
		url_end = strstr(ipv6_end, ":");
	else
		url_end = strstr(url_begin, ":");
	if (url_end) {
		url_len = url_end - url_begin;
		port_len = strlen(url_begin) - url_len - 1;
		if (port_len < 1)
			return false;
		port_start = url_end + 1;
	} else
		url_len = strlen(url_begin);

 	if (url_len < 1)
 		return false;
 
	snprintf(url_address, 254, "%.*s", url_len, url_begin);
 
 	if (port_len) {
 		char *slash;

		snprintf(port, 6, "%.*s", port_len, port_start);
		slash = strchr(port, '/');
		if (slash)
			*slash = '\0';
	} else
		strcpy(port, "80");

	*sockaddr_port = strdup(port);
	*sockaddr_url = strdup(url_address);

	return true;
}

Project: cgminer
File: util.c

static bool parse_notify(struct pool *pool, json_t *val)
{
	char *job_id, *prev_hash, *coinbase1, *coinbase2, *bbversion, *nbit,
	     *ntime, header[228];
	unsigned char *cb1 = NULL, *cb2 = NULL;
	size_t cb1_len, cb2_len, alloc_len;
	bool clean, ret = false;
	int merkles, i;
	json_t *arr;

	arr = json_array_get(val, 4);
	if (!arr || !json_is_array(arr))
		goto out;

	merkles = json_array_size(arr);

	job_id = json_array_string(val, 0);
	prev_hash = __json_array_string(val, 1);
	coinbase1 = json_array_string(val, 2);
	coinbase2 = json_array_string(val, 3);
	bbversion = __json_array_string(val, 5);
	nbit = __json_array_string(val, 6);
 	ntime = __json_array_string(val, 7);
 	clean = json_is_true(json_array_get(val, 8));
 
	if (!valid_hex(job_id) || !valid_hex(prev_hash) || !valid_hex(coinbase1) ||
	    !valid_hex(coinbase2) || !valid_hex(bbversion) || !valid_hex(nbit) ||
	    !valid_hex(ntime)) {
 		/* Annoying but we must not leak memory */
		free(job_id);
		free(coinbase1);
		free(coinbase2);
 		goto out;
 	}
 
	cg_wlock(&pool->data_lock);
	free(pool->swork.job_id);
	pool->swork.job_id = job_id;
	snprintf(pool->prev_hash, 65, "%s", prev_hash);
	cb1_len = strlen(coinbase1) / 2;
	cb2_len = strlen(coinbase2) / 2;
	snprintf(pool->bbversion, 9, "%s", bbversion);
	snprintf(pool->nbit, 9, "%s", nbit);
	snprintf(pool->ntime, 9, "%s", ntime);
	pool->swork.clean = clean;
	alloc_len = pool->coinbase_len = cb1_len + pool->n1_len + pool->n2size + cb2_len;
	pool->nonce2_offset = cb1_len + pool->n1_len;

	for (i = 0; i < pool->merkles; i++)
		free(pool->swork.merkle_bin[i]);
	if (merkles) {
		pool->swork.merkle_bin = realloc(pool->swork.merkle_bin,
						 sizeof(char *) * merkles + 1);
		for (i = 0; i < merkles; i++) {
			char *merkle = json_array_string(arr, i);

			pool->swork.merkle_bin[i] = malloc(32);
			if (unlikely(!pool->swork.merkle_bin[i]))
				quit(1, "Failed to malloc pool swork merkle_bin");
			if (opt_protocol)
				applog(LOG_DEBUG, "merkle %d: %s", i, merkle);
			ret = hex2bin(pool->swork.merkle_bin[i], merkle, 32);
			free(merkle);
			if (unlikely(!ret)) {
				applog(LOG_ERR, "Failed to convert merkle to merkle_bin in parse_notify");
				goto out_unlock;
			}
		}
	}
	pool->merkles = merkles;
	if (clean)
		pool->nonce2 = 0;
#if 0
	header_len = 		 strlen(pool->bbversion) +
				 strlen(pool->prev_hash);
	/* merkle_hash */	 32 +
				 strlen(pool->ntime) +
				 strlen(pool->nbit) +
	/* nonce */		 8 +
	/* workpadding */	 96;
#endif
	snprintf(header, 225,
		"%s%s%s%s%s%s%s",
		pool->bbversion,
		pool->prev_hash,
		blank_merkle,
		pool->ntime,
		pool->nbit,
		"00000000", /* nonce */
		workpadding);
	ret = hex2bin(pool->header_bin, header, 112);
	if (unlikely(!ret)) {
		applog(LOG_ERR, "Failed to convert header to header_bin in parse_notify");
		goto out_unlock;
	}

	cb1 = alloca(cb1_len);
	ret = hex2bin(cb1, coinbase1, cb1_len);
	if (unlikely(!ret)) {
		applog(LOG_ERR, "Failed to convert cb1 to cb1_bin in parse_notify");
		goto out_unlock;
	}
	cb2 = alloca(cb2_len);
	ret = hex2bin(cb2, coinbase2, cb2_len);
	if (unlikely(!ret)) {
		applog(LOG_ERR, "Failed to convert cb2 to cb2_bin in parse_notify");
		goto out_unlock;
	}
	free(pool->coinbase);
	align_len(&alloc_len);
	pool->coinbase = calloc(alloc_len, 1);
	if (unlikely(!pool->coinbase))
		quit(1, "Failed to calloc pool coinbase in parse_notify");
	memcpy(pool->coinbase, cb1, cb1_len);
	memcpy(pool->coinbase + cb1_len, pool->nonce1bin, pool->n1_len);
	memcpy(pool->coinbase + cb1_len + pool->n1_len + pool->n2size, cb2, cb2_len);
	if (opt_debug) {
		char *cb = bin2hex(pool->coinbase, pool->coinbase_len);

		applog(LOG_DEBUG, "Pool %d coinbase %s", pool->pool_no, cb);
		free(cb);
	}
out_unlock:
	cg_wunlock(&pool->data_lock);

	if (opt_protocol) {
		applog(LOG_DEBUG, "job_id: %s", job_id);
		applog(LOG_DEBUG, "prev_hash: %s", prev_hash);
		applog(LOG_DEBUG, "coinbase1: %s", coinbase1);
		applog(LOG_DEBUG, "coinbase2: %s", coinbase2);
		applog(LOG_DEBUG, "bbversion: %s", bbversion);
		applog(LOG_DEBUG, "nbit: %s", nbit);
		applog(LOG_DEBUG, "ntime: %s", ntime);
		applog(LOG_DEBUG, "clean: %s", clean ? "yes" : "no");
	}
	free(coinbase1);
	free(coinbase2);

	/* A notify message is the closest stratum gets to a getwork */
	pool->getwork_requested++;
	total_getworks++;
	if (pool == current_pool())
		opt_work_update = true;
out:
	return ret;
}

Project: cgminer
File: util.c

static bool parse_reconnect(struct pool *pool, json_t *val)
{
	char *sockaddr_url, *stratum_port, *tmp;
	char *url, *port, address[256];

	memset(address, 0, 255);
	url = (char *)json_string_value(json_array_get(val, 0));
	if (!url)
		url = pool->sockaddr_url;
	else {
		char *dot_pool, *dot_reconnect;
		dot_pool = strchr(pool->sockaddr_url, '.');
		if (!dot_pool) {
			applog(LOG_ERR, "Denied stratum reconnect request for pool without domain '%s'",
			       pool->sockaddr_url);
			return false;
		}
		dot_reconnect = strchr(url, '.');
		if (!dot_reconnect) {
			applog(LOG_ERR, "Denied stratum reconnect request to url without domain '%s'",
			       url);
			return false;
		}
		if (strcmp(dot_pool, dot_reconnect)) {
			applog(LOG_ERR, "Denied stratum reconnect request to non-matching domain url '%s'",
				pool->sockaddr_url);
			return false;
		}
	}

	port = (char *)json_string_value(json_array_get(val, 1));
 	if (!port)
 		port = pool->stratum_port;
 
	snprintf(address, 254, "%s:%s", url, port);
 
 	if (!extract_sockaddr(address, &sockaddr_url, &stratum_port))
 		return false;

	applog(LOG_WARNING, "Stratum reconnect requested from pool %d to %s", pool->pool_no, address);

	clear_pool_work(pool);

	mutex_lock(&pool->stratum_lock);
	__suspend_stratum(pool);
	tmp = pool->sockaddr_url;
	pool->sockaddr_url = sockaddr_url;
	pool->stratum_url = pool->sockaddr_url;
	free(tmp);
	tmp = pool->stratum_port;
	pool->stratum_port = stratum_port;
	free(tmp);
	mutex_unlock(&pool->stratum_lock);

	if (!restart_stratum(pool)) {
		pool_failed(pool);
		return false;
	}

	return true;
}