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