123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- /* $OpenBSD: sshbuf.c,v 1.15 2020/02/26 13:40:09 jsg Exp $ */
- /*
- * Copyright (c) 2011 Damien Miller
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
- #define SSHBUF_INTERNAL
- #include "includes.h"
- #include <sys/types.h>
- #include <signal.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include "ssherr.h"
- #include "sshbuf.h"
- #include "misc.h"
- static inline int
- sshbuf_check_sanity(const struct sshbuf *buf)
- {
- SSHBUF_TELL("sanity");
- if (__predict_false(buf == NULL ||
- (!buf->readonly && buf->d != buf->cd) ||
- buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
- buf->cd == NULL ||
- buf->max_size > SSHBUF_SIZE_MAX ||
- buf->alloc > buf->max_size ||
- buf->size > buf->alloc ||
- buf->off > buf->size)) {
- /* Do not try to recover from corrupted buffer internals */
- SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
- ssh_signal(SIGSEGV, SIG_DFL);
- raise(SIGSEGV);
- return SSH_ERR_INTERNAL_ERROR;
- }
- return 0;
- }
- static void
- sshbuf_maybe_pack(struct sshbuf *buf, int force)
- {
- SSHBUF_DBG(("force %d", force));
- SSHBUF_TELL("pre-pack");
- if (buf->off == 0 || buf->readonly || buf->refcount > 1)
- return;
- if (force ||
- (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
- memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
- buf->size -= buf->off;
- buf->off = 0;
- SSHBUF_TELL("packed");
- }
- }
- struct sshbuf *
- sshbuf_new(void)
- {
- struct sshbuf *ret;
- if ((ret = calloc(sizeof(*ret), 1)) == NULL)
- return NULL;
- ret->alloc = SSHBUF_SIZE_INIT;
- ret->max_size = SSHBUF_SIZE_MAX;
- ret->readonly = 0;
- ret->refcount = 1;
- ret->parent = NULL;
- if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
- free(ret);
- return NULL;
- }
- return ret;
- }
- struct sshbuf *
- sshbuf_from(const void *blob, size_t len)
- {
- struct sshbuf *ret;
- if (blob == NULL || len > SSHBUF_SIZE_MAX ||
- (ret = calloc(sizeof(*ret), 1)) == NULL)
- return NULL;
- ret->alloc = ret->size = ret->max_size = len;
- ret->readonly = 1;
- ret->refcount = 1;
- ret->parent = NULL;
- ret->cd = blob;
- ret->d = NULL;
- return ret;
- }
- int
- sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
- {
- int r;
- if ((r = sshbuf_check_sanity(child)) != 0 ||
- (r = sshbuf_check_sanity(parent)) != 0)
- return r;
- child->parent = parent;
- child->parent->refcount++;
- return 0;
- }
- struct sshbuf *
- sshbuf_fromb(struct sshbuf *buf)
- {
- struct sshbuf *ret;
- if (sshbuf_check_sanity(buf) != 0)
- return NULL;
- if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
- return NULL;
- if (sshbuf_set_parent(ret, buf) != 0) {
- sshbuf_free(ret);
- return NULL;
- }
- return ret;
- }
- void
- sshbuf_free(struct sshbuf *buf)
- {
- if (buf == NULL)
- return;
- /*
- * The following will leak on insane buffers, but this is the safest
- * course of action - an invalid pointer or already-freed pointer may
- * have been passed to us and continuing to scribble over memory would
- * be bad.
- */
- if (sshbuf_check_sanity(buf) != 0)
- return;
- /*
- * If we are a parent with still-extant children, then don't free just
- * yet. The last child's call to sshbuf_free should decrement our
- * refcount to 0 and trigger the actual free.
- */
- buf->refcount--;
- if (buf->refcount > 0)
- return;
- /*
- * If we are a child, the free our parent to decrement its reference
- * count and possibly free it.
- */
- sshbuf_free(buf->parent);
- buf->parent = NULL;
- if (!buf->readonly) {
- explicit_bzero(buf->d, buf->alloc);
- free(buf->d);
- }
- freezero(buf, sizeof(*buf));
- }
- void
- sshbuf_reset(struct sshbuf *buf)
- {
- u_char *d;
- if (buf->readonly || buf->refcount > 1) {
- /* Nonsensical. Just make buffer appear empty */
- buf->off = buf->size;
- return;
- }
- (void) sshbuf_check_sanity(buf);
- buf->off = buf->size = 0;
- if (buf->alloc != SSHBUF_SIZE_INIT) {
- if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
- 1)) != NULL) {
- buf->cd = buf->d = d;
- buf->alloc = SSHBUF_SIZE_INIT;
- }
- }
- explicit_bzero(buf->d, SSHBUF_SIZE_INIT);
- }
- size_t
- sshbuf_max_size(const struct sshbuf *buf)
- {
- return buf->max_size;
- }
- size_t
- sshbuf_alloc(const struct sshbuf *buf)
- {
- return buf->alloc;
- }
- const struct sshbuf *
- sshbuf_parent(const struct sshbuf *buf)
- {
- return buf->parent;
- }
- u_int
- sshbuf_refcount(const struct sshbuf *buf)
- {
- return buf->refcount;
- }
- int
- sshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
- {
- size_t rlen;
- u_char *dp;
- int r;
- SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
- if ((r = sshbuf_check_sanity(buf)) != 0)
- return r;
- if (max_size == buf->max_size)
- return 0;
- if (buf->readonly || buf->refcount > 1)
- return SSH_ERR_BUFFER_READ_ONLY;
- if (max_size > SSHBUF_SIZE_MAX)
- return SSH_ERR_NO_BUFFER_SPACE;
- /* pack and realloc if necessary */
- sshbuf_maybe_pack(buf, max_size < buf->size);
- if (max_size < buf->alloc && max_size > buf->size) {
- if (buf->size < SSHBUF_SIZE_INIT)
- rlen = SSHBUF_SIZE_INIT;
- else
- rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC);
- if (rlen > max_size)
- rlen = max_size;
- SSHBUF_DBG(("new alloc = %zu", rlen));
- if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL)
- return SSH_ERR_ALLOC_FAIL;
- buf->cd = buf->d = dp;
- buf->alloc = rlen;
- }
- SSHBUF_TELL("new-max");
- if (max_size < buf->alloc)
- return SSH_ERR_NO_BUFFER_SPACE;
- buf->max_size = max_size;
- return 0;
- }
- size_t
- sshbuf_len(const struct sshbuf *buf)
- {
- if (sshbuf_check_sanity(buf) != 0)
- return 0;
- return buf->size - buf->off;
- }
- size_t
- sshbuf_avail(const struct sshbuf *buf)
- {
- if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
- return 0;
- return buf->max_size - (buf->size - buf->off);
- }
- const u_char *
- sshbuf_ptr(const struct sshbuf *buf)
- {
- if (sshbuf_check_sanity(buf) != 0)
- return NULL;
- return buf->cd + buf->off;
- }
- u_char *
- sshbuf_mutable_ptr(const struct sshbuf *buf)
- {
- if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
- return NULL;
- return buf->d + buf->off;
- }
- int
- sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
- {
- int r;
- if ((r = sshbuf_check_sanity(buf)) != 0)
- return r;
- if (buf->readonly || buf->refcount > 1)
- return SSH_ERR_BUFFER_READ_ONLY;
- SSHBUF_TELL("check");
- /* Check that len is reasonable and that max_size + available < len */
- if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
- return SSH_ERR_NO_BUFFER_SPACE;
- return 0;
- }
- int
- sshbuf_allocate(struct sshbuf *buf, size_t len)
- {
- size_t rlen, need;
- u_char *dp;
- int r;
- SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
- if ((r = sshbuf_check_reserve(buf, len)) != 0)
- return r;
- /*
- * If the requested allocation appended would push us past max_size
- * then pack the buffer, zeroing buf->off.
- */
- sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
- SSHBUF_TELL("allocate");
- if (len + buf->size <= buf->alloc)
- return 0; /* already have it. */
- /*
- * Prefer to alloc in SSHBUF_SIZE_INC units, but
- * allocate less if doing so would overflow max_size.
- */
- need = len + buf->size - buf->alloc;
- rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
- SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
- if (rlen > buf->max_size)
- rlen = buf->alloc + need;
- SSHBUF_DBG(("adjusted rlen %zu", rlen));
- if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) {
- SSHBUF_DBG(("realloc fail"));
- return SSH_ERR_ALLOC_FAIL;
- }
- buf->alloc = rlen;
- buf->cd = buf->d = dp;
- if ((r = sshbuf_check_reserve(buf, len)) < 0) {
- /* shouldn't fail */
- return r;
- }
- SSHBUF_TELL("done");
- return 0;
- }
- int
- sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
- {
- u_char *dp;
- int r;
- if (dpp != NULL)
- *dpp = NULL;
- SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
- if ((r = sshbuf_allocate(buf, len)) != 0)
- return r;
- dp = buf->d + buf->size;
- buf->size += len;
- if (dpp != NULL)
- *dpp = dp;
- return 0;
- }
- int
- sshbuf_consume(struct sshbuf *buf, size_t len)
- {
- int r;
- SSHBUF_DBG(("len = %zu", len));
- if ((r = sshbuf_check_sanity(buf)) != 0)
- return r;
- if (len == 0)
- return 0;
- if (len > sshbuf_len(buf))
- return SSH_ERR_MESSAGE_INCOMPLETE;
- buf->off += len;
- /* deal with empty buffer */
- if (buf->off == buf->size)
- buf->off = buf->size = 0;
- SSHBUF_TELL("done");
- return 0;
- }
- int
- sshbuf_consume_end(struct sshbuf *buf, size_t len)
- {
- int r;
- SSHBUF_DBG(("len = %zu", len));
- if ((r = sshbuf_check_sanity(buf)) != 0)
- return r;
- if (len == 0)
- return 0;
- if (len > sshbuf_len(buf))
- return SSH_ERR_MESSAGE_INCOMPLETE;
- buf->size -= len;
- SSHBUF_TELL("done");
- return 0;
- }
|