Commit message: stratum: parse_notify(): Don't die on malformed bbversion/prev_hash/nbit/ntime.

Might have introduced a memory leak, don't have time to check. :(

Should the other hex2bin()'s be checked?

Thanks to Mick Ayzenberg <mick.dejavusecurity.com> for finding this.
CVE: CVE-2014-4503
CWE: CWE-20
Vulnerability description: The parse_notify function in util.c in sgminer before 4.2.2 and cgminer 3.3.0 through 4.0.1 allows man-in-the-middle attackers to cause a denial of service (application exit) via a crafted (1) bbversion, (2) prev_hash, (3) nbit, or (4) ntime parameter in a mining.notify action stratum message.

Project: sgminer
File: util.c

static char *__json_array_string(json_t *val, unsigned int entry)
{
	json_t *arr_entry;

	if (json_is_null(val))
		return NULL;
	if (!json_is_array(val))
		return NULL;
	if (entry > json_array_size(val))
		return NULL;
	arr_entry = json_array_get(val, entry);
	if (!json_is_string(arr_entry))
		return NULL;

	return (char *)json_string_value(arr_entry);
}

Project: sgminer
File: util.c

void _cgsem_post(cgsem_t *cgsem, const char *file, const char *func, const int line)
{
	const char buf = 1;
	int ret;

retry:
	ret = write(cgsem->pipefd[1], &buf, 1);
	if (unlikely(ret == 0))
		applog(LOG_WARNING, "Failed to write errno=%d" IN_FMT_FFL, errno, file, func, line);
	else if (unlikely(ret < 0 && interrupted))
		goto retry;
}

Project: sgminer
File: util.c

void _cgsem_wait(cgsem_t *cgsem, const char *file, const char *func, const int line)
{
retry:
	if (unlikely(sem_wait(cgsem))) {
		if (interrupted())
			goto retry;
		quitfrom(1, file, func, line, "Failed to sem_wait errno=%d cgsem=0x%p", errno, cgsem);
	}
}

Project: sgminer
File: util.c

char *bin2hex(const unsigned char *p, size_t len)
{
	ssize_t slen;
	char *s;

	slen = len * 2 + 1;
	if (slen % 4)
		slen += 4 - (slen % 4);
	s = (char *)calloc(slen, 1);
	if (unlikely(!s))
		quithere(1, "Failed to calloc");

	__bin2hex(s, p, len);

	return s;
}

Project: sgminer
File: util.c

void cgsem_reset(cgsem_t *cgsem)
{
	int ret;

	do {
		ret = sem_trywait(cgsem);
		if (unlikely(ret < 0 && interrupted()))
			ret = 0;
	} while (!ret);
}

Project: sgminer
File: util.c

void cgsleep_ms_r(cgtimer_t *ts_start, int ms)
{
	LARGE_INTEGER li;

	li.QuadPart = ts_start->QuadPart + (int64_t)ms * 10000LL;
	liSleep(&li, ms);
}

Project: sgminer
File: util.c

void cgsleep_ms_r(cgtimer_t *ts_start, int ms)
{
	struct timespec ts_diff;

	ms_to_timespec(&ts_diff, ms);
	cgsleep_spec(&ts_diff, ts_start);
}

Project: sgminer
File: util.c

void cgtimer_time(cgtimer_t *ts_start)
{
	struct timeval tv;

	cgtime(&tv);
	ts_start->tv_sec = tv->tv_sec;
	ts_start->tv_nsec = tv->tv_usec * 1000;
}

Project: sgminer
File: util.c

bool fulltest(const unsigned char *hash, const unsigned char *target)
{
	uint32_t *hash32 = (uint32_t *)hash;
	uint32_t *target32 = (uint32_t *)target;
	bool rc = true;
	int i;

	for (i = 28 / 4; i >= 0; i--) {
		uint32_t h32tmp = le32toh(hash32[i]);
		uint32_t t32tmp = le32toh(target32[i]);

		if (h32tmp > t32tmp) {
			rc = false;
			break;
		}
		if (h32tmp < t32tmp) {
			rc = true;
			break;
		}
	}

	if (opt_debug) {
		unsigned char hash_swap[32], target_swap[32];
		char *hash_str, *target_str;

		swab256(hash_swap, hash);
		swab256(target_swap, target);
		hash_str = bin2hex(hash_swap, 32);
		target_str = bin2hex(target_swap, 32);

		applog(LOG_DEBUG, " Proof: %s\nTarget: %s\nTrgVal? %s",
			hash_str,
			target_str,
			rc ? "YES (hash <= target)" :
			     "no (false positive; hash > target)");

		free(hash_str);
		free(target_str);
	}

	return rc;
}

Project: sgminer

json_t *json_rpc_call(CURL *curl, const char *url,
		      const char *userpass, const char *rpc_req,
		      bool probe, bool longpoll, int *rolltime,
		      struct pool *pool, bool share)
{
	long timeout = longpoll ? (60 * 60) : 60;
	struct data_buffer all_data = {NULL, 0};
	struct header_info hi = {NULL, 0, NULL, NULL, false, false, false};
	char len_hdr[64], user_agent_hdr[128];
	char curl_err_str[CURL_ERROR_SIZE];
	struct curl_slist *headers = NULL;
	struct upload_buffer upload_data;
	json_t *val, *err_val, *res_val;
	bool probing = false;
	double byte_count;
	json_error_t err;
	int rc;

	memset(&err, 0, sizeof(err));

	/* it is assumed that 'curl' is freshly [re]initialized at this pt */

	if (probe)
		probing = !pool->probed;
	curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);

	curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_cb);
	curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *)pool);
	curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);

	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
	curl_easy_setopt(curl, CURLOPT_URL, url);
	curl_easy_setopt(curl, CURLOPT_ENCODING, "");
	curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);

	/* Shares are staggered already and delays in submission can be costly
	 * so do not delay them */
	if (!opt_delaynet || share)
		curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, all_data_cb);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &all_data);
	curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_data_cb);
	curl_easy_setopt(curl, CURLOPT_READDATA, &upload_data);
	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err_str);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
	curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, resp_hdr_cb);
	curl_easy_setopt(curl, CURLOPT_HEADERDATA, &hi);
	curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
	if (pool->rpc_proxy) {
		curl_easy_setopt(curl, CURLOPT_PROXY, pool->rpc_proxy);
		curl_easy_setopt(curl, CURLOPT_PROXYTYPE, pool->rpc_proxytype);
	} else if (opt_socks_proxy) {
		curl_easy_setopt(curl, CURLOPT_PROXY, opt_socks_proxy);
		curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
	}
	if (userpass) {
		curl_easy_setopt(curl, CURLOPT_USERPWD, userpass);
		curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
	}
	if (longpoll)
		keep_curlalive(curl);
	curl_easy_setopt(curl, CURLOPT_POST, 1);

	if (opt_protocol)
		applog(LOG_DEBUG, "JSON protocol request:\n%s", rpc_req);

	upload_data.buf = rpc_req;
	upload_data.len = strlen(rpc_req);
	sprintf(len_hdr, "Content-Length: %lu",
		(unsigned long) upload_data.len);
	sprintf(user_agent_hdr, "User-Agent: %s", PACKAGE_STRING);

	headers = curl_slist_append(headers,
		"Content-type: application/json");
	headers = curl_slist_append(headers,
		"X-Mining-Extensions: longpoll midstate rollntime submitold");

	if (likely(global_hashrate)) {
		char ghashrate[255];

		sprintf(ghashrate, "X-Mining-Hashrate: %llu", global_hashrate);
		headers = curl_slist_append(headers, ghashrate);
	}

	headers = curl_slist_append(headers, len_hdr);
	headers = curl_slist_append(headers, user_agent_hdr);
	headers = curl_slist_append(headers, "Expect:"); /* disable Expect hdr*/

	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

	if (opt_delaynet) {
		/* Don't delay share submission, but still track the nettime */
		if (!share) {
			long long now_msecs, last_msecs;
			struct timeval now, last;

			cgtime(&now);
			last_nettime(&last);
			now_msecs = (long long)now.tv_sec * 1000;
			now_msecs += now.tv_usec / 1000;
			last_msecs = (long long)last.tv_sec * 1000;
			last_msecs += last.tv_usec / 1000;
			if (now_msecs > last_msecs && now_msecs - last_msecs < 250) {
				struct timespec rgtp;

				rgtp.tv_sec = 0;
				rgtp.tv_nsec = (250 - (now_msecs - last_msecs)) * 1000000;
				nanosleep(&rgtp, NULL);
			}
		}
		set_nettime();
	}

	rc = curl_easy_perform(curl);
	if (rc) {
		applog(LOG_INFO, "HTTP request failed: %s", curl_err_str);
		goto err_out;
	}

	if (!all_data.buf) {
		applog(LOG_DEBUG, "Empty data received in json_rpc_call.");
		goto err_out;
	}

	pool->sgminer_pool_stats.times_sent++;
	if (curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD, &byte_count) == CURLE_OK)
		pool->sgminer_pool_stats.bytes_sent += byte_count;
	pool->sgminer_pool_stats.times_received++;
	if (curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &byte_count) == CURLE_OK)
		pool->sgminer_pool_stats.bytes_received += byte_count;

	if (probing) {
		pool->probed = true;
		/* If X-Long-Polling was found, activate long polling */
		if (hi.lp_path) {
			if (pool->hdr_path != NULL)
				free(pool->hdr_path);
			pool->hdr_path = hi.lp_path;
		} else
			pool->hdr_path = NULL;
		if (hi.stratum_url) {
			pool->stratum_url = hi.stratum_url;
			hi.stratum_url = NULL;
		}
	} else {
		if (hi.lp_path) {
			free(hi.lp_path);
			hi.lp_path = NULL;
		}
		if (hi.stratum_url) {
			free(hi.stratum_url);
			hi.stratum_url = NULL;
		}
	}

	*rolltime = hi.rolltime;
	pool->sgminer_pool_stats.rolltime = hi.rolltime;
	pool->sgminer_pool_stats.hadrolltime = hi.hadrolltime;
	pool->sgminer_pool_stats.canroll = hi.canroll;
	pool->sgminer_pool_stats.hadexpire = hi.hadexpire;

	val = JSON_LOADS((const char *)all_data.buf, &err);
	if (!val) {
		applog(LOG_INFO, "JSON decode failed(%d): %s", err.line, err.text);

		if (opt_protocol)
			applog(LOG_DEBUG, "JSON protocol response:\n%s", (char *)(all_data.buf));

		goto err_out;
	}

	if (opt_protocol) {
		char *s = json_dumps(val, JSON_INDENT(3));

		applog(LOG_DEBUG, "JSON protocol response:\n%s", s);
		free(s);
	}

	/* JSON-RPC valid response returns a non-null 'result',
	 * and a null 'error'.
	 */
	res_val = json_object_get(val, "result");
	err_val = json_object_get(val, "error");

	if (!res_val ||(err_val && !json_is_null(err_val))) {
		char *s;

		if (err_val)
			s = json_dumps(err_val, JSON_INDENT(3));
		else
			s = strdup("(unknown reason)");

		applog(LOG_INFO, "JSON-RPC call failed: %s", s);

		free(s);

		goto err_out;
	}

	if (hi.reason) {
		json_object_set_new(val, "reject-reason", json_string(hi.reason));
		free(hi.reason);
		hi.reason = NULL;
	}
	successful_connect = true;
	databuf_free(&all_data);
	curl_slist_free_all(headers);
	curl_easy_reset(curl);
	return val;

err_out:
	databuf_free(&all_data);
	curl_slist_free_all(headers);
	curl_easy_reset(curl);
	if (!successful_connect)
		applog(LOG_DEBUG, "Failed to connect in json_rpc_call");
	curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1);
	return NULL;
}

Project: sgminer
File: util.c

static bool socks5_negotiate(struct pool *pool, int sockd)
{
	unsigned char atyp, uclen;
	unsigned short port;
	char buf[515];
	int i, len;

	buf[0] = 0x05;
	buf[1] = 0x01;
	buf[2] = 0x00;
	applog(LOG_DEBUG, "Attempting to negotiate with %s:%s SOCKS5 proxy",
	       pool->sockaddr_proxy_url, pool->sockaddr_proxy_port );
	send(sockd, buf, 3, 0);
	if (recv_byte(sockd) != 0x05 || recv_byte(sockd) != buf[2]) {
		applog(LOG_WARNING, "Bad response from %s:%s SOCKS5 server",
		       pool->sockaddr_proxy_url, pool->sockaddr_proxy_port );
		return false;
	}

	buf[0] = 0x05;
	buf[1] = 0x01;
	buf[2] = 0x00;
	buf[3] = 0x03;
	len = (strlen(pool->sockaddr_url));
	if (len > 255)
		len = 255;
	uclen = len;
	buf[4] = (uclen & 0xff);
	memcpy(buf + 5, pool->sockaddr_url, len);
	port = atoi(pool->stratum_port);
	buf[5 + len] = (port >> 8);
	buf[6 + len] = (port & 0xff);
	send(sockd, buf, (7 + len), 0);
	if (recv_byte(sockd) != 0x05 || recv_byte(sockd) != 0x00) {
		applog(LOG_WARNING, "Bad response from %s:%s SOCKS5 server",
			pool->sockaddr_proxy_url, pool->sockaddr_proxy_port );
		return false;
	}

	recv_byte(sockd);
	atyp = recv_byte(sockd);
	if (atyp == 0x01) {
		for (i = 0; i < 4; i++)
			recv_byte(sockd);
	} else if (atyp == 0x03) {
		len = recv_byte(sockd);
		for (i = 0; i < len; i++)
			recv_byte(sockd);
	} else {
		applog(LOG_WARNING, "Bad response from %s:%s SOCKS5 server",
			pool->sockaddr_proxy_url, pool->sockaddr_proxy_port );
		return false;
	}
	for (i = 0; i < 2; i++)
		recv_byte(sockd);

	applog(LOG_DEBUG, "Success negotiating with %s:%s SOCKS5 proxy",
	       pool->sockaddr_proxy_url, pool->sockaddr_proxy_port);
	return true;
}

Project: sgminer
File: util.c

void suspend_stratum(struct pool *pool)
{
	applog(LOG_INFO, "Closing socket for stratum %s", get_pool_name(pool));

	mutex_lock(&pool->stratum_lock);
	__suspend_stratum(pool);
	mutex_unlock(&pool->stratum_lock);
}

Project: sgminer
File: util.c

int thr_info_create(struct thr_info *thr, pthread_attr_t *attr, void *(*start) (void *), void *arg)
{
	cgsem_init(&thr->sem);

	return pthread_create(&thr->pth, attr, start, arg);
}

Project: sgminer
File: util.c

static int __maybe_unused timespec_to_ms(struct timespec *ts)
{
	return ts->tv_sec * 1000 + ts->tv_nsec / 1000000;
}

Project: sgminer
File: util.c

struct thread_q *tq_new(void)
{
	struct thread_q *tq;

	tq = (struct thread_q *)calloc(1, sizeof(*tq));
	if (!tq)
		return NULL;

	INIT_LIST_HEAD(&tq->q);
	pthread_mutex_init(&tq->mutex, NULL);
	pthread_cond_init(&tq->cond, NULL);

	return tq;
}