sshbuf.c revision 296853
159477Swpaul/*	$OpenBSD: sshbuf.c,v 1.6 2016/01/12 23:42:54 djm Exp $	*/
259477Swpaul/*
359477Swpaul * Copyright (c) 2011 Damien Miller
459477Swpaul *
559477Swpaul * Permission to use, copy, modify, and distribute this software for any
659477Swpaul * purpose with or without fee is hereby granted, provided that the above
759477Swpaul * copyright notice and this permission notice appear in all copies.
859477Swpaul *
959477Swpaul * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1059477Swpaul * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1159477Swpaul * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1259477Swpaul * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1359477Swpaul * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1459477Swpaul * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1559477Swpaul * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1659477Swpaul */
1759477Swpaul
1859477Swpaul#define SSHBUF_INTERNAL
1959477Swpaul#include "includes.h"
2059477Swpaul
2159477Swpaul#include <sys/param.h>	/* roundup */
2259477Swpaul#include <sys/types.h>
2359477Swpaul#include <signal.h>
2459477Swpaul#include <stdlib.h>
2559477Swpaul#include <stdio.h>
2659477Swpaul#include <string.h>
2759477Swpaul
2859477Swpaul#include "ssherr.h"
2959477Swpaul#include "sshbuf.h"
3059477Swpaul
3159477Swpaulstatic inline int
3259477Swpaulsshbuf_check_sanity(const struct sshbuf *buf)
3359477Swpaul{
3459477Swpaul	SSHBUF_TELL("sanity");
3559477Swpaul	if (__predict_false(buf == NULL ||
3659477Swpaul	    (!buf->readonly && buf->d != buf->cd) ||
3759477Swpaul	    buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
3859477Swpaul	    buf->cd == NULL ||
3959477Swpaul	    (buf->dont_free && (buf->readonly || buf->parent != NULL)) ||
4059477Swpaul	    buf->max_size > SSHBUF_SIZE_MAX ||
4159477Swpaul	    buf->alloc > buf->max_size ||
4259477Swpaul	    buf->size > buf->alloc ||
4359477Swpaul	    buf->off > buf->size)) {
4459477Swpaul		/* Do not try to recover from corrupted buffer internals */
4559477Swpaul		SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
4659477Swpaul		signal(SIGSEGV, SIG_DFL);
4783029Swpaul		raise(SIGSEGV);
4859477Swpaul		return SSH_ERR_INTERNAL_ERROR;
4959477Swpaul	}
5059477Swpaul	return 0;
5159477Swpaul}
5259477Swpaul
5359477Swpaulstatic void
5459477Swpaulsshbuf_maybe_pack(struct sshbuf *buf, int force)
5559477Swpaul{
5659477Swpaul	SSHBUF_DBG(("force %d", force));
5759477Swpaul	SSHBUF_TELL("pre-pack");
5859477Swpaul	if (buf->off == 0 || buf->readonly || buf->refcount > 1)
5959477Swpaul		return;
6059477Swpaul	if (force ||
6159477Swpaul	    (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
6259477Swpaul		memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
6359477Swpaul		buf->size -= buf->off;
6459477Swpaul		buf->off = 0;
6559477Swpaul		SSHBUF_TELL("packed");
6659477Swpaul	}
6759477Swpaul}
6859477Swpaul
6959477Swpaulstruct sshbuf *
7059477Swpaulsshbuf_new(void)
7159477Swpaul{
7259477Swpaul	struct sshbuf *ret;
7359477Swpaul
7459477Swpaul	if ((ret = calloc(sizeof(*ret), 1)) == NULL)
7559477Swpaul		return NULL;
7659477Swpaul	ret->alloc = SSHBUF_SIZE_INIT;
7759477Swpaul	ret->max_size = SSHBUF_SIZE_MAX;
7859477Swpaul	ret->readonly = 0;
7959477Swpaul	ret->refcount = 1;
8059477Swpaul	ret->parent = NULL;
8159477Swpaul	if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
8259477Swpaul		free(ret);
8359477Swpaul		return NULL;
8459477Swpaul	}
8559477Swpaul	return ret;
8659477Swpaul}
8759477Swpaul
8859477Swpaulstruct sshbuf *
8959477Swpaulsshbuf_from(const void *blob, size_t len)
9059477Swpaul{
9159477Swpaul	struct sshbuf *ret;
9259477Swpaul
9359477Swpaul	if (blob == NULL || len > SSHBUF_SIZE_MAX ||
9459477Swpaul	    (ret = calloc(sizeof(*ret), 1)) == NULL)
9559477Swpaul		return NULL;
9659477Swpaul	ret->alloc = ret->size = ret->max_size = len;
9759477Swpaul	ret->readonly = 1;
9859477Swpaul	ret->refcount = 1;
9959477Swpaul	ret->parent = NULL;
10059477Swpaul	ret->cd = blob;
10183029Swpaul	ret->d = NULL;
10283029Swpaul	return ret;
10383029Swpaul}
10483029Swpaul
10583029Swpaulint
10659477Swpaulsshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
10783029Swpaul{
10883029Swpaul	int r;
10983029Swpaul
11083029Swpaul	if ((r = sshbuf_check_sanity(child)) != 0 ||
11183029Swpaul	    (r = sshbuf_check_sanity(parent)) != 0)
11259477Swpaul		return r;
11383029Swpaul	child->parent = parent;
11483029Swpaul	child->parent->refcount++;
11583029Swpaul	return 0;
11683029Swpaul}
11783029Swpaul
11883029Swpaulstruct sshbuf *
11983029Swpaulsshbuf_fromb(struct sshbuf *buf)
12059477Swpaul{
12159477Swpaul	struct sshbuf *ret;
12259477Swpaul
12359477Swpaul	if (sshbuf_check_sanity(buf) != 0)
12459477Swpaul		return NULL;
12559477Swpaul	if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
12659477Swpaul		return NULL;
12759477Swpaul	if (sshbuf_set_parent(ret, buf) != 0) {
12859477Swpaul		sshbuf_free(ret);
12959477Swpaul		return NULL;
13059477Swpaul	}
13159477Swpaul	return ret;
13259477Swpaul}
13359477Swpaul
13459477Swpaulvoid
13559477Swpaulsshbuf_init(struct sshbuf *ret)
13659477Swpaul{
13759477Swpaul	explicit_bzero(ret, sizeof(*ret));
13859477Swpaul	ret->alloc = SSHBUF_SIZE_INIT;
13959477Swpaul	ret->max_size = SSHBUF_SIZE_MAX;
14059477Swpaul	ret->readonly = 0;
14159477Swpaul	ret->dont_free = 1;
14259477Swpaul	ret->refcount = 1;
14359477Swpaul	if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL)
14459477Swpaul		ret->alloc = 0;
14559477Swpaul}
14659477Swpaul
14759477Swpaulvoid
14859477Swpaulsshbuf_free(struct sshbuf *buf)
14959477Swpaul{
15059477Swpaul	int dont_free = 0;
15159477Swpaul
15259477Swpaul	if (buf == NULL)
15359477Swpaul		return;
15459477Swpaul	/*
15559477Swpaul	 * The following will leak on insane buffers, but this is the safest
15683029Swpaul	 * course of action - an invalid pointer or already-freed pointer may
15783029Swpaul	 * have been passed to us and continuing to scribble over memory would
15883029Swpaul	 * be bad.
15959477Swpaul	 */
16083029Swpaul	if (sshbuf_check_sanity(buf) != 0)
16183029Swpaul		return;
16283029Swpaul	/*
16359477Swpaul	 * If we are a child, the free our parent to decrement its reference
16459477Swpaul	 * count and possibly free it.
16583029Swpaul	 */
16659477Swpaul	sshbuf_free(buf->parent);
16759477Swpaul	buf->parent = NULL;
16859477Swpaul	/*
16959477Swpaul	 * If we are a parent with still-extant children, then don't free just
17059477Swpaul	 * yet. The last child's call to sshbuf_free should decrement our
17159477Swpaul	 * refcount to 0 and trigger the actual free.
17259477Swpaul	 */
17359477Swpaul	buf->refcount--;
17459477Swpaul	if (buf->refcount > 0)
17559477Swpaul		return;
17659477Swpaul	dont_free = buf->dont_free;
17759477Swpaul	if (!buf->readonly) {
17859477Swpaul		explicit_bzero(buf->d, buf->alloc);
17959477Swpaul		free(buf->d);
18059477Swpaul	}
18159477Swpaul	explicit_bzero(buf, sizeof(*buf));
18259477Swpaul	if (!dont_free)
18359477Swpaul		free(buf);
18459477Swpaul}
18559477Swpaul
18659477Swpaulvoid
18769925Swpaulsshbuf_reset(struct sshbuf *buf)
18869925Swpaul{
18983029Swpaul	u_char *d;
19059477Swpaul
19159477Swpaul	if (buf->readonly || buf->refcount > 1) {
19259477Swpaul		/* Nonsensical. Just make buffer appear empty */
19359477Swpaul		buf->off = buf->size;
19459477Swpaul		return;
19559477Swpaul	}
19659477Swpaul	if (sshbuf_check_sanity(buf) == 0)
19759477Swpaul		explicit_bzero(buf->d, buf->alloc);
19859477Swpaul	buf->off = buf->size = 0;
19959477Swpaul	if (buf->alloc != SSHBUF_SIZE_INIT) {
20059477Swpaul		if ((d = realloc(buf->d, SSHBUF_SIZE_INIT)) != NULL) {
20159477Swpaul			buf->cd = buf->d = d;
20283029Swpaul			buf->alloc = SSHBUF_SIZE_INIT;
20359477Swpaul		}
20459477Swpaul	}
20559477Swpaul}
20659477Swpaul
20759477Swpaulsize_t
20859477Swpaulsshbuf_max_size(const struct sshbuf *buf)
20959477Swpaul{
21059477Swpaul	return buf->max_size;
21159477Swpaul}
21259477Swpaul
21359477Swpaulsize_t
21459477Swpaulsshbuf_alloc(const struct sshbuf *buf)
21559477Swpaul{
21659477Swpaul	return buf->alloc;
21759477Swpaul}
21859477Swpaul
21959477Swpaulconst struct sshbuf *
22059477Swpaulsshbuf_parent(const struct sshbuf *buf)
22159477Swpaul{
22259477Swpaul	return buf->parent;
22359477Swpaul}
22459477Swpaul
22559477Swpaulu_int
22659477Swpaulsshbuf_refcount(const struct sshbuf *buf)
22759477Swpaul{
22859477Swpaul	return buf->refcount;
22959477Swpaul}
23059477Swpaul
23159477Swpaulint
23259477Swpaulsshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
23359477Swpaul{
23459477Swpaul	size_t rlen;
23559477Swpaul	u_char *dp;
23659477Swpaul	int r;
23759477Swpaul
23859477Swpaul	SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
23959477Swpaul	if ((r = sshbuf_check_sanity(buf)) != 0)
24059477Swpaul		return r;
24159477Swpaul	if (max_size == buf->max_size)
24259477Swpaul		return 0;
24359477Swpaul	if (buf->readonly || buf->refcount > 1)
24459477Swpaul		return SSH_ERR_BUFFER_READ_ONLY;
24559477Swpaul	if (max_size > SSHBUF_SIZE_MAX)
24659477Swpaul		return SSH_ERR_NO_BUFFER_SPACE;
24759477Swpaul	/* pack and realloc if necessary */
24883029Swpaul	sshbuf_maybe_pack(buf, max_size < buf->size);
24983029Swpaul	if (max_size < buf->alloc && max_size > buf->size) {
25083029Swpaul		if (buf->size < SSHBUF_SIZE_INIT)
25183029Swpaul			rlen = SSHBUF_SIZE_INIT;
25283029Swpaul		else
25383029Swpaul			rlen = roundup(buf->size, SSHBUF_SIZE_INC);
25483029Swpaul		if (rlen > max_size)
25583029Swpaul			rlen = max_size;
25659477Swpaul		explicit_bzero(buf->d + buf->size, buf->alloc - buf->size);
25759477Swpaul		SSHBUF_DBG(("new alloc = %zu", rlen));
25883029Swpaul		if ((dp = realloc(buf->d, rlen)) == NULL)
25959477Swpaul			return SSH_ERR_ALLOC_FAIL;
26083029Swpaul		buf->cd = buf->d = dp;
26159477Swpaul		buf->alloc = rlen;
26259477Swpaul	}
26359477Swpaul	SSHBUF_TELL("new-max");
26483029Swpaul	if (max_size < buf->alloc)
26583029Swpaul		return SSH_ERR_NO_BUFFER_SPACE;
26683029Swpaul	buf->max_size = max_size;
26759477Swpaul	return 0;
26859477Swpaul}
26959477Swpaul
27059477Swpaulsize_t
27159477Swpaulsshbuf_len(const struct sshbuf *buf)
27259477Swpaul{
27359477Swpaul	if (sshbuf_check_sanity(buf) != 0)
27459477Swpaul		return 0;
27559477Swpaul	return buf->size - buf->off;
27659477Swpaul}
27759477Swpaul
27859477Swpaulsize_t
27959477Swpaulsshbuf_avail(const struct sshbuf *buf)
28059477Swpaul{
28159477Swpaul	if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
28259477Swpaul		return 0;
28383597Swpaul	return buf->max_size - (buf->size - buf->off);
28483597Swpaul}
28583597Swpaul
28683597Swpaulconst u_char *
28783597Swpaulsshbuf_ptr(const struct sshbuf *buf)
28859477Swpaul{
28959477Swpaul	if (sshbuf_check_sanity(buf) != 0)
29059477Swpaul		return NULL;
29159477Swpaul	return buf->cd + buf->off;
29259477Swpaul}
29359477Swpaul
29459477Swpaulu_char *
29559477Swpaulsshbuf_mutable_ptr(const struct sshbuf *buf)
29659477Swpaul{
29759477Swpaul	if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
29859477Swpaul		return NULL;
29959477Swpaul	return buf->d + buf->off;
30059477Swpaul}
30159477Swpaul
30259477Swpaulint
30359477Swpaulsshbuf_check_reserve(const struct sshbuf *buf, size_t len)
30459477Swpaul{
30559477Swpaul	int r;
30659477Swpaul
30759477Swpaul	if ((r = sshbuf_check_sanity(buf)) != 0)
30859477Swpaul		return r;
30959477Swpaul	if (buf->readonly || buf->refcount > 1)
31059477Swpaul		return SSH_ERR_BUFFER_READ_ONLY;
31159477Swpaul	SSHBUF_TELL("check");
31259477Swpaul	/* Check that len is reasonable and that max_size + available < len */
31359477Swpaul	if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
31459477Swpaul		return SSH_ERR_NO_BUFFER_SPACE;
31559477Swpaul	return 0;
31659477Swpaul}
31759477Swpaul
31859477Swpaulint
31959477Swpaulsshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
32059477Swpaul{
32159477Swpaul	size_t rlen, need;
32259477Swpaul	u_char *dp;
32359477Swpaul	int r;
32459477Swpaul
32559477Swpaul	if (dpp != NULL)
32659477Swpaul		*dpp = NULL;
32759477Swpaul
32859477Swpaul	SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
32959477Swpaul	if ((r = sshbuf_check_reserve(buf, len)) != 0)
33059477Swpaul		return r;
33159477Swpaul	/*
33259477Swpaul	 * If the requested allocation appended would push us past max_size
33359477Swpaul	 * then pack the buffer, zeroing buf->off.
33459477Swpaul	 */
33559477Swpaul	sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
33659477Swpaul	SSHBUF_TELL("reserve");
33759477Swpaul	if (len + buf->size > buf->alloc) {
33859477Swpaul		/*
33959477Swpaul		 * Prefer to alloc in SSHBUF_SIZE_INC units, but
34059477Swpaul		 * allocate less if doing so would overflow max_size.
34159477Swpaul		 */
34259477Swpaul		need = len + buf->size - buf->alloc;
34359477Swpaul		rlen = roundup(buf->alloc + need, SSHBUF_SIZE_INC);
34459477Swpaul		SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
34559477Swpaul		if (rlen > buf->max_size)
34659477Swpaul			rlen = buf->alloc + need;
34759477Swpaul		SSHBUF_DBG(("adjusted rlen %zu", rlen));
34859477Swpaul		if ((dp = realloc(buf->d, rlen)) == NULL) {
34959477Swpaul			SSHBUF_DBG(("realloc fail"));
35059477Swpaul			if (dpp != NULL)
35159477Swpaul				*dpp = NULL;
35283029Swpaul			return SSH_ERR_ALLOC_FAIL;
35383029Swpaul		}
35459477Swpaul		buf->alloc = rlen;
35559477Swpaul		buf->cd = buf->d = dp;
35659477Swpaul		if ((r = sshbuf_check_reserve(buf, len)) < 0) {
35759477Swpaul			/* shouldn't fail */
35859477Swpaul			if (dpp != NULL)
35959477Swpaul				*dpp = NULL;
36059477Swpaul			return r;
36159477Swpaul		}
36259477Swpaul	}
36359477Swpaul	dp = buf->d + buf->size;
36459477Swpaul	buf->size += len;
36559477Swpaul	SSHBUF_TELL("done");
36659477Swpaul	if (dpp != NULL)
36759477Swpaul		*dpp = dp;
36859477Swpaul	return 0;
36959477Swpaul}
37059477Swpaul
37159477Swpaulint
37259477Swpaulsshbuf_consume(struct sshbuf *buf, size_t len)
37359477Swpaul{
37483029Swpaul	int r;
37583029Swpaul
37683029Swpaul	SSHBUF_DBG(("len = %zu", len));
37783029Swpaul	if ((r = sshbuf_check_sanity(buf)) != 0)
37883029Swpaul		return r;
37983029Swpaul	if (len == 0)
38083029Swpaul		return 0;
38183029Swpaul	if (len > sshbuf_len(buf))
38283029Swpaul		return SSH_ERR_MESSAGE_INCOMPLETE;
38383029Swpaul	buf->off += len;
38483029Swpaul	SSHBUF_TELL("done");
38583029Swpaul	return 0;
38683029Swpaul}
38783029Swpaul
38883029Swpaulint
38983029Swpaulsshbuf_consume_end(struct sshbuf *buf, size_t len)
39083029Swpaul{
39183029Swpaul	int r;
39283029Swpaul
39383029Swpaul	SSHBUF_DBG(("len = %zu", len));
39483029Swpaul	if ((r = sshbuf_check_sanity(buf)) != 0)
39583029Swpaul		return r;
39683029Swpaul	if (len == 0)
39783029Swpaul		return 0;
39883029Swpaul	if (len > sshbuf_len(buf))
39983029Swpaul		return SSH_ERR_MESSAGE_INCOMPLETE;
40083029Swpaul	buf->size -= len;
40159477Swpaul	SSHBUF_TELL("done");
40259477Swpaul	return 0;
40359477Swpaul}
40483029Swpaul
40559477Swpaul