Commit message: Fix buffer overrun after incomplete read in pullf_read_max().

Most callers pass a stack buffer.  The ensuing stack smash can crash the
server, and we have not ruled out the viability of attacks that lead to
privilege escalation.  Back-patch to 9.0 (all supported versions).

Marko Tiikkaja

Security: CVE-2015-0243
CVE: CVE-2015-0243
CWE: CWE-120
Vulnerability description: Multiple buffer overflows in contrib/pgcrypto in PostgreSQL before 9.0.19, 9.1.x before 9.1.15, 9.2.x before 9.2.10, 9.3.x before 9.3.6, and 9.4.x before 9.4.1 allow remote authenticated users to cause a denial of service (crash) and possibly execute arbitrary code via unspecified vectors.

Project: postgres

pushf_flush(PushFilter *mp)
{
	int			res;

	while (mp)
	{
		if (mp->block_size > 0)
		{
			res = wrap_process(mp, mp->buf, mp->pos);
			if (res < 0)
				return res;
		}

		if (mp->op->flush)
		{
			res = mp->op->flush(mp->next, mp->priv);
			if (res < 0)
				return res;
		}

		mp = mp->next;
	}
	return 0;
}

Project: postgres

pullf_free(PullFilter *pf)
{
	if (pf->op->free)
		pf->op->free(pf->priv);

	if (pf->buf)
	{
		px_memset(pf->buf, 0, pf->buflen);
		px_free(pf->buf);
	}

	px_memset(pf, 0, sizeof(*pf));
	px_free(pf);
}

Project: postgres

mbuf_create_from_data(uint8 *data, int len)
{
	MBuf	   *mbuf;

	mbuf = px_alloc(sizeof *mbuf);
	mbuf->data = (uint8 *) data;
	mbuf->buf_end = mbuf->data + len;
	mbuf->data_end = mbuf->data + len;
	mbuf->read_pos = mbuf->data;

	mbuf->no_write = true;
	mbuf->own_data = false;

	return mbuf;
}

Project: postgres

pullf_read(PullFilter *pf, int len, uint8 **data_p)
{
	int			res;

	if (pf->op->pull)
	{
		if (pf->buflen && len > pf->buflen)
			len = pf->buflen;
		res = pf->op->pull(pf->priv, pf->src, len, data_p,
						   pf->buf, pf->buflen);
	}
	else
		res = pullf_read(pf->src, len, data_p);
	return res;
}

Project: postgres

mbuf_grab(MBuf *mbuf, int len, uint8 **data_p)
{
	if (len > mbuf_avail(mbuf))
		len = mbuf_avail(mbuf);

	mbuf->no_write = true;

	*data_p = mbuf->read_pos;
	mbuf->read_pos += len;
	return len;
}

Project: postgres

pullf_create(PullFilter **pf_p, const PullFilterOps *op, void *init_arg, PullFilter *src)
{
	PullFilter *pf;
	void	   *priv;
	int			res;

	if (op->init != NULL)
	{
		res = op->init(&priv, init_arg, src);
		if (res < 0)
			return res;
	}
	else
	{
		priv = init_arg;
		res = 0;
	}

	pf = px_alloc(sizeof(*pf));
	memset(pf, 0, sizeof(*pf));
	pf->buflen = res;
	pf->op = op;
	pf->priv = priv;
	pf->src = src;
	if (pf->buflen > 0)
	{
		pf->buf = px_alloc(pf->buflen);
		pf->pos = 0;
	}
	else
	{
		pf->buf = NULL;
		pf->pos = 0;
	}
	*pf_p = pf;
	return 0;
}

Project: postgres

pushf_write(PushFilter *mp, const uint8 *data, int len)
{
	int			need,
				res;

	/*
	 * no buffering
	 */
	if (mp->block_size <= 0)
		return wrap_process(mp, data, len);

	/*
	 * try to empty buffer
	 */
	need = mp->block_size - mp->pos;
	if (need > 0)
	{
		if (len < need)
		{
			memcpy(mp->buf + mp->pos, data, len);
			mp->pos += len;
			return 0;
		}
		memcpy(mp->buf + mp->pos, data, need);
		len -= need;
		data += need;
	}

	/*
	 * buffer full, process
	 */
	res = wrap_process(mp, mp->buf, mp->block_size);
	if (res < 0)
		return res;
	mp->pos = 0;

	/*
	 * now process directly from data
	 */
	while (len > 0)
	{
		if (len > mp->block_size)
		{
			res = wrap_process(mp, data, mp->block_size);
			if (res < 0)
				return res;
			data += mp->block_size;
			len -= mp->block_size;
		}
		else
		{
			memcpy(mp->buf, data, len);
			mp->pos += len;
			break;
		}
	}
	return 0;
}

Project: postgres

pullf_create_mbuf_reader(PullFilter **mp_p, MBuf *src)
{
	return pullf_create(mp_p, &mbuf_reader, src, NULL);
}

Project: postgres

pushf_free_all(PushFilter *mp)
{
	PushFilter *tmp;

	while (mp)
	{
		tmp = mp->next;
		pushf_free(mp);
		mp = tmp;
	}
}

Project: postgres

mbuf_avail(MBuf *mbuf)
{
	return mbuf->data_end - mbuf->read_pos;
}

Project: postgres

mbuf_tell(MBuf *mbuf)
{
	return mbuf->read_pos - mbuf->data;
}

Project: postgres

mbuf_rewind(MBuf *mbuf)
{
	mbuf->read_pos = mbuf->data;
	return 0;
}

Project: postgres

pushf_free(PushFilter *mp)
{
	if (mp->op->free)
		mp->op->free(mp->priv);

	if (mp->buf)
	{
		px_memset(mp->buf, 0, mp->block_size);
		px_free(mp->buf);
	}

	px_memset(mp, 0, sizeof(*mp));
	px_free(mp);
}

Project: postgres

mbuf_free(MBuf *mbuf)
{
	if (mbuf->own_data)
	{
		px_memset(mbuf->data, 0, mbuf->buf_end - mbuf->data);
		px_free(mbuf->data);
	}
	px_free(mbuf);
	return 0;
}

Project: postgres

pullf_read_fixed(PullFilter *src, int len, uint8 *dst)
{
	int			res;
	uint8	   *p;

	res = pullf_read_max(src, len, &p, dst);
	if (res < 0)
		return res;
	if (res != len)
	{
		px_debug("pullf_read_fixed: need=%d got=%d", len, res);
		return PXE_MBUF_SHORT_READ;
	}
	if (p != dst)
		memcpy(dst, p, len);
	return 0;
}

Project: postgres

pushf_create_mbuf_writer(PushFilter **res, MBuf *dst)
{
	return pushf_create(res, &mbuf_filter, dst, NULL);
}

Project: postgres

pullf_read_max(PullFilter *pf, int len, uint8 **data_p, uint8 *tmpbuf)
{
	int			res,
				total;
	uint8	   *tmp;

	res = pullf_read(pf, len, data_p);
	if (res <= 0 || res == len)
		return res;

	/* read was shorter, use tmpbuf */
	memcpy(tmpbuf, *data_p, res);
	*data_p = tmpbuf;
	len -= res;
	total = res;

	while (len > 0)
	{
		res = pullf_read(pf, len, &tmp);
		if (res < 0)
		{
			/* so the caller must clear only on success */
			px_memset(tmpbuf, 0, total);
			return res;
		}
		if (res == 0)
			break;
		memcpy(tmpbuf + total, tmp, res);
		total += res;
		len -= res;
	}
	return total;
}

Project: postgres

push_into_mbuf(PushFilter *next, void *arg, const uint8 *data, int len)
{
	int			res = 0;
	MBuf	   *mbuf = arg;

	if (len > 0)
		res = mbuf_append(mbuf, data, len);
	return res < 0 ? res : 0;
}

Project: postgres

wrap_process(PushFilter *mp, const uint8 *data, int len)
{
	int			res;

	if (mp->op->push != NULL)
		res = mp->op->push(mp->next, mp->priv, data, len);
	else
		res = pushf_write(mp->next, data, len);
	if (res > 0)
		return PXE_BUG;
	return res;
}

Project: postgres

mbuf_append(MBuf *dst, const uint8 *buf, int len)
{
	if (dst->no_write)
	{
		px_debug("mbuf_append: no_write");
		return PXE_BUG;
	}

	prepare_room(dst, len);

	memcpy(dst->data_end, buf, len);
	dst->data_end += len;

	return 0;
}

Project: postgres

mbuf_steal_data(MBuf *mbuf, uint8 **data_p)
{
	int			len = mbuf_size(mbuf);

	mbuf->no_write = true;
	mbuf->own_data = false;

	*data_p = mbuf->data;

	mbuf->data = mbuf->data_end = mbuf->read_pos = mbuf->buf_end = NULL;

	return len;
}

Project: postgres

mbuf_create(int len)
{
	MBuf	   *mbuf;

	if (!len)
		len = 8192;

	mbuf = px_alloc(sizeof *mbuf);
	mbuf->data = px_alloc(len);
	mbuf->buf_end = mbuf->data + len;
	mbuf->data_end = mbuf->data;
	mbuf->read_pos = mbuf->data;

	mbuf->no_write = false;
	mbuf->own_data = true;

	return mbuf;
}

Project: postgres

mbuf_size(MBuf *mbuf)
{
	return mbuf->data_end - mbuf->data;
}

Project: postgres

prepare_room(MBuf *mbuf, int block_len)
{
	uint8	   *newbuf;
	unsigned	newlen;

	if (mbuf->data_end + block_len <= mbuf->buf_end)
		return;

	newlen = (mbuf->buf_end - mbuf->data)
		+ ((block_len + STEP + STEP - 1) & -STEP);

	newbuf = px_realloc(mbuf->data, newlen);

	mbuf->buf_end = newbuf + newlen;
	mbuf->data_end = newbuf + (mbuf->data_end - mbuf->data);
	mbuf->read_pos = newbuf + (mbuf->read_pos - mbuf->data);
	mbuf->data = newbuf;

	return;
}

Project: postgres

pull_from_mbuf(void *arg, PullFilter *src, int len,
			   uint8 **data_p, uint8 *buf, int buflen)
{
	MBuf	   *mbuf = arg;

	return mbuf_grab(mbuf, len, data_p);
}

Project: postgres

pushf_create(PushFilter **mp_p, const PushFilterOps *op, void *init_arg, PushFilter *next)
{
	PushFilter *mp;
	void	   *priv;
	int			res;

	if (op->init != NULL)
	{
		res = op->init(next, init_arg, &priv);
		if (res < 0)
			return res;
	}
	else
	{
		priv = init_arg;
		res = 0;
	}

	mp = px_alloc(sizeof(*mp));
	memset(mp, 0, sizeof(*mp));
	mp->block_size = res;
	mp->op = op;
	mp->priv = priv;
	mp->next = next;
	if (mp->block_size > 0)
	{
		mp->buf = px_alloc(mp->block_size);
		mp->pos = 0;
	}
	else
	{
		mp->buf = NULL;
		mp->pos = 0;
	}
	*mp_p = mp;
	return 0;
}