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