Commit message: knc: fix a couple of memory leaks.

One of these can be remotely triggered during the authentication
phase which leads to a remote DoS possibility.

Pointed out by: Imre Rad <radimre83@gmail.com>
CVE: CVE-2017-9732
CWE: CWE-400
Vulnerability description: The read_packet function in knc (Kerberised NetCat) before 1.11-1 is vulnerable to denial of service (memory exhaustion) that can be exploited remotely without authentication, possibly affecting another services running on the targeted host.

Project: knc
File: gssstdio.c

gstd_close(void *the_tok)
{
	struct gstd_tok	*tok = the_tok;
	OM_uint32	 min;

	gss_delete_sec_context(&min, &tok->gstd_ctx, GSS_C_NO_BUFFER);
	if (tok->gstd_inbuf.length > 0)
		gss_release_buffer(&min, &tok->gstd_inbuf);
	close(tok->gstd_fd);
	return 0;
}

Project: knc
File: gssstdio.c

gstd_error(int pri, int min_stat, const char *s)
{
	char *t1;

	if (gstd_errstring(&t1, min_stat) < 1)
		LOG(pri, ("%s: couldn't form GSSAPI error string", s));
	else {
		LOG(pri, ("%s: %s", s, t1));
		free(t1);
	}
}

Project: knc
File: gssstdio.c

gstd_errstring(char **str, int min_stat)
{
	gss_buffer_desc	 status;
	OM_uint32	 new_stat;
	OM_uint32	 msg_ctx = 0;
	OM_uint32	 ret;
	int		 len = 0;
	char		*tmp;
	char		*statstr;

	/* XXXrcd this is not correct yet */
	/* XXXwps ...and now it is. */

	if (!str)
		return -1;

	*str = NULL;
	tmp = NULL;

	do {
		ret = gss_display_status(&new_stat, min_stat,
		    GSS_C_MECH_CODE, GSS_C_NO_OID, &msg_ctx,
		    &status);

		/* GSSAPI strings are not NUL terminated */
		if ((statstr = (char *)malloc(status.length + 1)) == NULL) {
			LOG(LOG_ERR, ("unable to malloc status string "
				      "of length %ld", status.length));
			gss_release_buffer(&new_stat, &status);
			free(statstr);
			free(tmp);
			return 0;
		}

		memcpy(statstr, status.value, status.length);
		statstr[status.length] = '\0';

		if (GSS_ERROR(ret)) {
			free(statstr);
			free(tmp);
			break;
		}

		if (*str) {
			if ((*str = malloc(strlen(*str) + status.length +
					   3)) == NULL) {
				LOG(LOG_ERR, ("unable to malloc error "
						"string"));
				gss_release_buffer(&new_stat, &status);
				free(statstr);
				free(tmp);
				return 0;
			}

			len = sprintf(*str, "%s, %s", tmp, statstr);
		} else {
			*str = malloc(status.length + 1);
			if (!*str) {
				LOG(LOG_ERR, ("unable to malloc error "
						"string"));
				gss_release_buffer(&new_stat, &status);
				free(statstr);
				free(tmp);
				return 0;
			}
			len = sprintf(*str, "%s", (char *)statstr);
		}

		gss_release_buffer(&new_stat, &status);
		free(statstr);
		free(tmp);

		tmp = *str;
	} while (msg_ctx != 0);

	return len;
}

Project: knc
File: gssstdio.c

gstd_get_display_name(gss_name_t client)
{
	OM_uint32	maj;
	OM_uint32	min;
	gss_buffer_desc	buf;
	char		*ret;

	maj = gss_display_name(&min, client, &buf, NULL);
	GSTD_GSS_ERROR(maj, min, NULL, "gss_display_name");

	if ((ret = (char *)malloc(buf.length + 1)) == NULL) {
		LOG(LOG_ERR, ("unable to malloc"));
		gss_release_buffer(&min, &buf);
		return NULL;
	}

	memcpy(ret, buf.value, buf.length);
	ret[buf.length] = '\0';

	gss_release_buffer(&min, &buf);

	return ret;
}

Project: knc
File: gssstdio.c

gstd_get_export_name(gss_name_t client)
{
	OM_uint32	maj;
	OM_uint32	min;
	gss_buffer_desc	buf;
	unsigned char   *bufp;
	unsigned char   nibble;
	char		*ret;
	size_t	  i, k;

	maj = gss_export_name(&min, client, &buf);
	GSTD_GSS_ERROR(maj, min, NULL, "gss_export_name");

	if ((ret = (char *)malloc(buf.length * 2 + 1)) == NULL) {
		LOG(LOG_ERR, ("unable to malloc"));
		gss_release_buffer(&min, &buf);
		return NULL;
	}

	for (bufp = buf.value, i = 0, k = 0; i < buf.length; i++) {
		nibble = bufp[i] >> 4;
		ret[k++] = "0123456789ABCDEF"[nibble];
		nibble = bufp[i] & 0x0f;
		ret[k++] = "0123456789ABCDEF"[nibble];
	}

	ret[k] = '\0';
	gss_release_buffer(&min, &buf);

	return ret;
}

Project: knc
File: gssstdio.c

gstd_get_mech(gss_OID mech_oid)
{
#ifdef HAVE_GSS_OID_TO_STR
	OM_uint32	maj;
	OM_uint32	min;
#endif
	gss_buffer_desc	buf;
	unsigned char   *bufp;
	unsigned char   nibble;
	char		*ret;
	size_t		i, k;

	if (mech_oid->length == sizeof(KNC_KRB5_MECH_OID) - 1 &&
	    memcmp(mech_oid->elements, KNC_KRB5_MECH_OID,
		   sizeof(KNC_KRB5_MECH_OID) - 1) == 0) {
		if ((ret = strdup("krb5")) == NULL) {
			LOG(LOG_ERR, ("unable to malloc"));
			return NULL;
		}
		return ret;
	}

#ifdef HAVE_GSS_OID_TO_STR
	maj = gss_oid_to_str(&min, mech_oid, &buf);
	if (maj != GSS_S_COMPLETE) {
		LOG(LOG_ERR, ("unable to display mechanism OID"));
		return NULL;
	}
	ret = strndup(buf.value, buf.length);
#else
	ret = strdup("");
#endif
	if (!ret)
		LOG(LOG_ERR, ("unable to malloc"));
	return ret;
}

Project: knc
File: gssstdio.c

gstd_initiate(const char *hostname, const char *service, const char *princ,
	      int fd)
{
	struct gstd_tok	*tok;
	gss_ctx_id_t	ctx = GSS_C_NO_CONTEXT;
	gss_buffer_desc	in, out;
	gss_OID		type;
	OM_uint32	maj, min;
	gss_buffer_desc	name;
	gss_name_t	server;
	int		ret;

	if (!princ) {
		if ((name.value = malloc(strlen(service) + strlen(hostname)
					 + 2)) == NULL) {
			LOG(LOG_ERR, ("unable to malloc service name"));
			return NULL;
		}

		name.length = sprintf((char *)name.value, "%s@%s",
				      service, hostname);
		LOG(LOG_DEBUG, ("going to get tickets for: %s",
		    (char *)name.value));
		fflush(stderr);
		if (!name.value)
			return NULL;
		type = GSS_C_NT_HOSTBASED_SERVICE;
	} else {
		name.value = (char *) princ;
		name.length = strlen(princ);
		type = (gss_OID) GSS_C_NO_OID;
	}

	maj = gss_import_name(&min, &name, type, &server);
	GSTD_GSS_ERROR(maj, min, NULL, "gss_import_name");

	in.length = 0;
	out.length = 0;

again:
	maj = gss_init_sec_context(&min, GSS_C_NO_CREDENTIAL, &ctx, server,
	    GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 0,
	    GSS_C_NO_CHANNEL_BINDINGS, &in, NULL, &out, NULL, NULL);

	if (out.length && write_packet(fd, &out))
		return NULL;

	GSTD_GSS_ERROR(maj, min, NULL, "gss_init_sec_context");

	if (GSS_ERROR(maj) && ctx != GSS_C_NO_CONTEXT) {
		gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
		return NULL;
	}

	if (maj & GSS_S_CONTINUE_NEEDED) {
		LOG(LOG_DEBUG, ("continuing gstd_initiate"));
		while ((ret = read_packet(fd, &in, 0, 0)) == -2)
			;

		if (ret < 1) {
			LOG(LOG_ERR, ("continuation failed"));
			return NULL;
		}

		goto again;
	}

	LOG(LOG_DEBUG, ("authenticated"));
	SETUP_GSTD_TOK(tok, ctx, fd, "gstd_connect");
	return tok;
}

Project: knc
File: gssstdio.c

gstd_read(void *the_tok, char *buf, int length)
{
	struct gstd_tok	*tok = the_tok;
	gss_buffer_desc	in;
	OM_uint32	maj, min;
	int		bufpos = tok->gstd_inbufpos;
	int		ret;

	/*
	 * If we have no buffered data, read another packet and
	 * reset the buffer.
	 */

	if (bufpos == -1 || bufpos >= tok->gstd_inbuf.length) {
		if (tok->gstd_inbuf.length > 0)
			gss_release_buffer(&min, &tok->gstd_inbuf);

		/*
		 * If we encounter a protocol botch or if the other side has
		 * closed the connection, we return that fact here
		 */
		ret = read_packet(tok->gstd_fd, &in, 0, 0);
		if (ret <= 0)
			return ret;

		maj = gss_unwrap(&min, tok->gstd_ctx, &in, &tok->gstd_inbuf,
		    NULL, NULL);
		if (maj != GSS_S_COMPLETE) {
			gstd_error(LOG_ERR, min, "gss_unwrap");
			return -1;
		}
		gss_release_buffer(&min, &in);
		bufpos = 0;
	}

	/*
	 * Now we know that we have a buffered packet, so return
	 * as much of it as we can.  We do not need to fill the
	 * requestor's buffer, because stdio can deal with short
	 * reads.
	 */

	length = MIN(length, tok->gstd_inbuf.length - bufpos);
	memcpy(buf, tok->gstd_inbuf.value + bufpos, length);
	tok->gstd_inbufpos = bufpos + length;
	LOG(LOG_DEBUG, ("read %d bytes", length));
	return length;
}

Project: knc
File: gssstdio.c

gstd_release_context(void *ctx) {
	OM_uint32	min;

	gss_delete_sec_context(&min, (gss_ctx_id_t *)ctx, GSS_C_NO_BUFFER);
}

Project: knc
File: gssstdio.c

timed_read(int fd, void *buf, size_t bytes, int timeout)
{
	struct pollfd		fds[1];
	int			ret;

	if (timeout > 0) {
		fds[0].fd = fd;
		fds[0].events = POLLIN;

		ret = poll(fds, 1, timeout);

		if (ret == -1)
			return -1;

		if (ret != 1) {
			errno = ETIMEDOUT;
			return -1;
		}
	}

	return read(fd, buf, bytes);
}

Project: knc
File: gssstdio.c

write_packet(int fd, gss_buffer_t buf)
{
	uint32_t	len;
	OM_uint32	min_stat;
	int		ret = 0;

	len = htonl(buf->length);
	if ((writen(fd, &len, 4) != 4) ||
	    (writen(fd, buf->value, buf->length) != buf->length))
		ret = -1;

	gss_release_buffer (&min_stat, buf);
	return ret;
}

Project: knc
File: gssstdio.c

writen(int fd, const void *buf, ssize_t len) {
	int	nleft;
	int	nwritten;
	char *	buffer = (char *)buf;

	nleft = len;
	while (nleft > 0) {
		nwritten = write(fd, buffer, len);

		if (nwritten < 0) {
			if (errno == EINTR || errno == EAGAIN)
				continue;
			else {
				LOG_ERRNO(LOG_ERR, ("write failed"));
				return -1;
			}
		} else {
			nleft -= nwritten;
			buffer += nwritten;
		}
	}

	LOG(LOG_DEBUG, ("wrote %d bytes", len));
	return len;
}