Home | History | Annotate | Download | only in openssh
      1 /*	$OpenBSD: sshbuf.c,v 1.8 2016/11/25 23:22:04 djm Exp $	*/
      2 /*
      3  * Copyright (c) 2011 Damien Miller
      4  *
      5  * Permission to use, copy, modify, and distribute this software for any
      6  * purpose with or without fee is hereby granted, provided that the above
      7  * copyright notice and this permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16  */
     17 
     18 #define SSHBUF_INTERNAL
     19 #include "includes.h"
     20 
     21 #include <sys/types.h>
     22 #include <signal.h>
     23 #include <stdlib.h>
     24 #include <stdio.h>
     25 #include <string.h>
     26 
     27 #include "ssherr.h"
     28 #include "sshbuf.h"
     29 #include "misc.h"
     30 
     31 static inline int
     32 sshbuf_check_sanity(const struct sshbuf *buf)
     33 {
     34 	SSHBUF_TELL("sanity");
     35 	if (__predict_false(buf == NULL ||
     36 	    (!buf->readonly && buf->d != buf->cd) ||
     37 	    buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
     38 	    buf->cd == NULL ||
     39 	    (buf->dont_free && (buf->readonly || buf->parent != NULL)) ||
     40 	    buf->max_size > SSHBUF_SIZE_MAX ||
     41 	    buf->alloc > buf->max_size ||
     42 	    buf->size > buf->alloc ||
     43 	    buf->off > buf->size)) {
     44 		/* Do not try to recover from corrupted buffer internals */
     45 		SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
     46 		signal(SIGSEGV, SIG_DFL);
     47 		raise(SIGSEGV);
     48 		return SSH_ERR_INTERNAL_ERROR;
     49 	}
     50 	return 0;
     51 }
     52 
     53 static void
     54 sshbuf_maybe_pack(struct sshbuf *buf, int force)
     55 {
     56 	SSHBUF_DBG(("force %d", force));
     57 	SSHBUF_TELL("pre-pack");
     58 	if (buf->off == 0 || buf->readonly || buf->refcount > 1)
     59 		return;
     60 	if (force ||
     61 	    (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
     62 		memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
     63 		buf->size -= buf->off;
     64 		buf->off = 0;
     65 		SSHBUF_TELL("packed");
     66 	}
     67 }
     68 
     69 struct sshbuf *
     70 sshbuf_new(void)
     71 {
     72 	struct sshbuf *ret;
     73 
     74 	if ((ret = calloc(sizeof(*ret), 1)) == NULL)
     75 		return NULL;
     76 	ret->alloc = SSHBUF_SIZE_INIT;
     77 	ret->max_size = SSHBUF_SIZE_MAX;
     78 	ret->readonly = 0;
     79 	ret->refcount = 1;
     80 	ret->parent = NULL;
     81 	if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
     82 		free(ret);
     83 		return NULL;
     84 	}
     85 	return ret;
     86 }
     87 
     88 struct sshbuf *
     89 sshbuf_from(const void *blob, size_t len)
     90 {
     91 	struct sshbuf *ret;
     92 
     93 	if (blob == NULL || len > SSHBUF_SIZE_MAX ||
     94 	    (ret = calloc(sizeof(*ret), 1)) == NULL)
     95 		return NULL;
     96 	ret->alloc = ret->size = ret->max_size = len;
     97 	ret->readonly = 1;
     98 	ret->refcount = 1;
     99 	ret->parent = NULL;
    100 	ret->cd = blob;
    101 	ret->d = NULL;
    102 	return ret;
    103 }
    104 
    105 int
    106 sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
    107 {
    108 	int r;
    109 
    110 	if ((r = sshbuf_check_sanity(child)) != 0 ||
    111 	    (r = sshbuf_check_sanity(parent)) != 0)
    112 		return r;
    113 	child->parent = parent;
    114 	child->parent->refcount++;
    115 	return 0;
    116 }
    117 
    118 struct sshbuf *
    119 sshbuf_fromb(struct sshbuf *buf)
    120 {
    121 	struct sshbuf *ret;
    122 
    123 	if (sshbuf_check_sanity(buf) != 0)
    124 		return NULL;
    125 	if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
    126 		return NULL;
    127 	if (sshbuf_set_parent(ret, buf) != 0) {
    128 		sshbuf_free(ret);
    129 		return NULL;
    130 	}
    131 	return ret;
    132 }
    133 
    134 void
    135 sshbuf_init(struct sshbuf *ret)
    136 {
    137 	explicit_bzero(ret, sizeof(*ret));
    138 	ret->alloc = SSHBUF_SIZE_INIT;
    139 	ret->max_size = SSHBUF_SIZE_MAX;
    140 	ret->readonly = 0;
    141 	ret->dont_free = 1;
    142 	ret->refcount = 1;
    143 	if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL)
    144 		ret->alloc = 0;
    145 }
    146 
    147 void
    148 sshbuf_free(struct sshbuf *buf)
    149 {
    150 	int dont_free = 0;
    151 
    152 	if (buf == NULL)
    153 		return;
    154 	/*
    155 	 * The following will leak on insane buffers, but this is the safest
    156 	 * course of action - an invalid pointer or already-freed pointer may
    157 	 * have been passed to us and continuing to scribble over memory would
    158 	 * be bad.
    159 	 */
    160 	if (sshbuf_check_sanity(buf) != 0)
    161 		return;
    162 	/*
    163 	 * If we are a child, the free our parent to decrement its reference
    164 	 * count and possibly free it.
    165 	 */
    166 	sshbuf_free(buf->parent);
    167 	buf->parent = NULL;
    168 	/*
    169 	 * If we are a parent with still-extant children, then don't free just
    170 	 * yet. The last child's call to sshbuf_free should decrement our
    171 	 * refcount to 0 and trigger the actual free.
    172 	 */
    173 	buf->refcount--;
    174 	if (buf->refcount > 0)
    175 		return;
    176 	dont_free = buf->dont_free;
    177 	if (!buf->readonly) {
    178 		explicit_bzero(buf->d, buf->alloc);
    179 		free(buf->d);
    180 	}
    181 	explicit_bzero(buf, sizeof(*buf));
    182 	if (!dont_free)
    183 		free(buf);
    184 }
    185 
    186 void
    187 sshbuf_reset(struct sshbuf *buf)
    188 {
    189 	u_char *d;
    190 
    191 	if (buf->readonly || buf->refcount > 1) {
    192 		/* Nonsensical. Just make buffer appear empty */
    193 		buf->off = buf->size;
    194 		return;
    195 	}
    196 	if (sshbuf_check_sanity(buf) == 0)
    197 		explicit_bzero(buf->d, buf->alloc);
    198 	buf->off = buf->size = 0;
    199 	if (buf->alloc != SSHBUF_SIZE_INIT) {
    200 		if ((d = realloc(buf->d, SSHBUF_SIZE_INIT)) != NULL) {
    201 			buf->cd = buf->d = d;
    202 			buf->alloc = SSHBUF_SIZE_INIT;
    203 		}
    204 	}
    205 }
    206 
    207 size_t
    208 sshbuf_max_size(const struct sshbuf *buf)
    209 {
    210 	return buf->max_size;
    211 }
    212 
    213 size_t
    214 sshbuf_alloc(const struct sshbuf *buf)
    215 {
    216 	return buf->alloc;
    217 }
    218 
    219 const struct sshbuf *
    220 sshbuf_parent(const struct sshbuf *buf)
    221 {
    222 	return buf->parent;
    223 }
    224 
    225 u_int
    226 sshbuf_refcount(const struct sshbuf *buf)
    227 {
    228 	return buf->refcount;
    229 }
    230 
    231 int
    232 sshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
    233 {
    234 	size_t rlen;
    235 	u_char *dp;
    236 	int r;
    237 
    238 	SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
    239 	if ((r = sshbuf_check_sanity(buf)) != 0)
    240 		return r;
    241 	if (max_size == buf->max_size)
    242 		return 0;
    243 	if (buf->readonly || buf->refcount > 1)
    244 		return SSH_ERR_BUFFER_READ_ONLY;
    245 	if (max_size > SSHBUF_SIZE_MAX)
    246 		return SSH_ERR_NO_BUFFER_SPACE;
    247 	/* pack and realloc if necessary */
    248 	sshbuf_maybe_pack(buf, max_size < buf->size);
    249 	if (max_size < buf->alloc && max_size > buf->size) {
    250 		if (buf->size < SSHBUF_SIZE_INIT)
    251 			rlen = SSHBUF_SIZE_INIT;
    252 		else
    253 			rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC);
    254 		if (rlen > max_size)
    255 			rlen = max_size;
    256 		explicit_bzero(buf->d + buf->size, buf->alloc - buf->size);
    257 		SSHBUF_DBG(("new alloc = %zu", rlen));
    258 		if ((dp = realloc(buf->d, rlen)) == NULL)
    259 			return SSH_ERR_ALLOC_FAIL;
    260 		buf->cd = buf->d = dp;
    261 		buf->alloc = rlen;
    262 	}
    263 	SSHBUF_TELL("new-max");
    264 	if (max_size < buf->alloc)
    265 		return SSH_ERR_NO_BUFFER_SPACE;
    266 	buf->max_size = max_size;
    267 	return 0;
    268 }
    269 
    270 size_t
    271 sshbuf_len(const struct sshbuf *buf)
    272 {
    273 	if (sshbuf_check_sanity(buf) != 0)
    274 		return 0;
    275 	return buf->size - buf->off;
    276 }
    277 
    278 size_t
    279 sshbuf_avail(const struct sshbuf *buf)
    280 {
    281 	if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
    282 		return 0;
    283 	return buf->max_size - (buf->size - buf->off);
    284 }
    285 
    286 const u_char *
    287 sshbuf_ptr(const struct sshbuf *buf)
    288 {
    289 	if (sshbuf_check_sanity(buf) != 0)
    290 		return NULL;
    291 	return buf->cd + buf->off;
    292 }
    293 
    294 u_char *
    295 sshbuf_mutable_ptr(const struct sshbuf *buf)
    296 {
    297 	if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
    298 		return NULL;
    299 	return buf->d + buf->off;
    300 }
    301 
    302 int
    303 sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
    304 {
    305 	int r;
    306 
    307 	if ((r = sshbuf_check_sanity(buf)) != 0)
    308 		return r;
    309 	if (buf->readonly || buf->refcount > 1)
    310 		return SSH_ERR_BUFFER_READ_ONLY;
    311 	SSHBUF_TELL("check");
    312 	/* Check that len is reasonable and that max_size + available < len */
    313 	if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
    314 		return SSH_ERR_NO_BUFFER_SPACE;
    315 	return 0;
    316 }
    317 
    318 int
    319 sshbuf_allocate(struct sshbuf *buf, size_t len)
    320 {
    321 	size_t rlen, need;
    322 	u_char *dp;
    323 	int r;
    324 
    325 	SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
    326 	if ((r = sshbuf_check_reserve(buf, len)) != 0)
    327 		return r;
    328 	/*
    329 	 * If the requested allocation appended would push us past max_size
    330 	 * then pack the buffer, zeroing buf->off.
    331 	 */
    332 	sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
    333 	SSHBUF_TELL("allocate");
    334 	if (len + buf->size <= buf->alloc)
    335 		return 0; /* already have it. */
    336 
    337 	/*
    338 	 * Prefer to alloc in SSHBUF_SIZE_INC units, but
    339 	 * allocate less if doing so would overflow max_size.
    340 	 */
    341 	need = len + buf->size - buf->alloc;
    342 	rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
    343 	SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
    344 	if (rlen > buf->max_size)
    345 		rlen = buf->alloc + need;
    346 	SSHBUF_DBG(("adjusted rlen %zu", rlen));
    347 	if ((dp = realloc(buf->d, rlen)) == NULL) {
    348 		SSHBUF_DBG(("realloc fail"));
    349 		return SSH_ERR_ALLOC_FAIL;
    350 	}
    351 	buf->alloc = rlen;
    352 	buf->cd = buf->d = dp;
    353 	if ((r = sshbuf_check_reserve(buf, len)) < 0) {
    354 		/* shouldn't fail */
    355 		return r;
    356 	}
    357 	SSHBUF_TELL("done");
    358 	return 0;
    359 }
    360 
    361 int
    362 sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
    363 {
    364 	u_char *dp;
    365 	int r;
    366 
    367 	if (dpp != NULL)
    368 		*dpp = NULL;
    369 
    370 	SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
    371 	if ((r = sshbuf_allocate(buf, len)) != 0)
    372 		return r;
    373 
    374 	dp = buf->d + buf->size;
    375 	buf->size += len;
    376 	if (dpp != NULL)
    377 		*dpp = dp;
    378 	return 0;
    379 }
    380 
    381 int
    382 sshbuf_consume(struct sshbuf *buf, size_t len)
    383 {
    384 	int r;
    385 
    386 	SSHBUF_DBG(("len = %zu", len));
    387 	if ((r = sshbuf_check_sanity(buf)) != 0)
    388 		return r;
    389 	if (len == 0)
    390 		return 0;
    391 	if (len > sshbuf_len(buf))
    392 		return SSH_ERR_MESSAGE_INCOMPLETE;
    393 	buf->off += len;
    394 	SSHBUF_TELL("done");
    395 	return 0;
    396 }
    397 
    398 int
    399 sshbuf_consume_end(struct sshbuf *buf, size_t len)
    400 {
    401 	int r;
    402 
    403 	SSHBUF_DBG(("len = %zu", len));
    404 	if ((r = sshbuf_check_sanity(buf)) != 0)
    405 		return r;
    406 	if (len == 0)
    407 		return 0;
    408 	if (len > sshbuf_len(buf))
    409 		return SSH_ERR_MESSAGE_INCOMPLETE;
    410 	buf->size -= len;
    411 	SSHBUF_TELL("done");
    412 	return 0;
    413 }
    414 
    415