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