1*b691e04fStobias /* $OpenBSD: sshbuf.c,v 1.23 2024/08/14 15:42:18 tobias Exp $ */ 215b55daeSdjm /* 315b55daeSdjm * Copyright (c) 2011 Damien Miller 415b55daeSdjm * 515b55daeSdjm * Permission to use, copy, modify, and distribute this software for any 615b55daeSdjm * purpose with or without fee is hereby granted, provided that the above 715b55daeSdjm * copyright notice and this permission notice appear in all copies. 815b55daeSdjm * 915b55daeSdjm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1015b55daeSdjm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1115b55daeSdjm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1215b55daeSdjm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1315b55daeSdjm * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1415b55daeSdjm * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1515b55daeSdjm * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1615b55daeSdjm */ 1715b55daeSdjm 1815b55daeSdjm #include <sys/types.h> 1915b55daeSdjm #include <signal.h> 2015b55daeSdjm #include <stdlib.h> 2115b55daeSdjm #include <stdio.h> 2215b55daeSdjm #include <string.h> 2315b55daeSdjm 2415b55daeSdjm #include "ssherr.h" 2515b55daeSdjm #define SSHBUF_INTERNAL 2615b55daeSdjm #include "sshbuf.h" 2703db5a1fSderaadt #include "misc.h" 2815b55daeSdjm 2960dd6e7eSdjm #ifdef SSHBUF_DEBUG 3060dd6e7eSdjm # define SSHBUF_TELL(what) do { \ 3160dd6e7eSdjm printf("%s:%d %s: %s size %zu alloc %zu off %zu max %zu\n", \ 3260dd6e7eSdjm __FILE__, __LINE__, __func__, what, \ 3360dd6e7eSdjm buf->size, buf->alloc, buf->off, buf->max_size); \ 3460dd6e7eSdjm fflush(stdout); \ 3560dd6e7eSdjm } while (0) 3660dd6e7eSdjm #else 3760dd6e7eSdjm # define SSHBUF_TELL(what) 3860dd6e7eSdjm #endif 3960dd6e7eSdjm 4060dd6e7eSdjm struct sshbuf { 4160dd6e7eSdjm u_char *d; /* Data */ 4260dd6e7eSdjm const u_char *cd; /* Const data */ 4360dd6e7eSdjm size_t off; /* First available byte is buf->d + buf->off */ 4460dd6e7eSdjm size_t size; /* Last byte is buf->d + buf->size - 1 */ 4560dd6e7eSdjm size_t max_size; /* Maximum size of buffer */ 4660dd6e7eSdjm size_t alloc; /* Total bytes allocated to buf->d */ 4760dd6e7eSdjm int readonly; /* Refers to external, const data */ 4860dd6e7eSdjm u_int refcount; /* Tracks self and number of child buffers */ 4960dd6e7eSdjm struct sshbuf *parent; /* If child, pointer to parent */ 5060dd6e7eSdjm }; 5160dd6e7eSdjm 5215b55daeSdjm static inline int 5315b55daeSdjm sshbuf_check_sanity(const struct sshbuf *buf) 5415b55daeSdjm { 5515b55daeSdjm SSHBUF_TELL("sanity"); 5615b55daeSdjm if (__predict_false(buf == NULL || 5715b55daeSdjm (!buf->readonly && buf->d != buf->cd) || 582afef00eStobias buf->parent == buf || 5915b55daeSdjm buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX || 6015b55daeSdjm buf->cd == NULL || 6115b55daeSdjm buf->max_size > SSHBUF_SIZE_MAX || 6215b55daeSdjm buf->alloc > buf->max_size || 6315b55daeSdjm buf->size > buf->alloc || 6415b55daeSdjm buf->off > buf->size)) { 6515b55daeSdjm /* Do not try to recover from corrupted buffer internals */ 66a8cbed27Sdjm SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR")); 67e9716d4dSdtucker ssh_signal(SIGSEGV, SIG_DFL); 6815b55daeSdjm raise(SIGSEGV); 6915b55daeSdjm return SSH_ERR_INTERNAL_ERROR; 7015b55daeSdjm } 7115b55daeSdjm return 0; 7215b55daeSdjm } 7315b55daeSdjm 7415b55daeSdjm static void 7515b55daeSdjm sshbuf_maybe_pack(struct sshbuf *buf, int force) 7615b55daeSdjm { 77a8cbed27Sdjm SSHBUF_DBG(("force %d", force)); 7815b55daeSdjm SSHBUF_TELL("pre-pack"); 7915b55daeSdjm if (buf->off == 0 || buf->readonly || buf->refcount > 1) 8015b55daeSdjm return; 8115b55daeSdjm if (force || 8215b55daeSdjm (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) { 8315b55daeSdjm memmove(buf->d, buf->d + buf->off, buf->size - buf->off); 8415b55daeSdjm buf->size -= buf->off; 8515b55daeSdjm buf->off = 0; 8615b55daeSdjm SSHBUF_TELL("packed"); 8715b55daeSdjm } 8815b55daeSdjm } 8915b55daeSdjm 9015b55daeSdjm struct sshbuf * 9115b55daeSdjm sshbuf_new(void) 9215b55daeSdjm { 9315b55daeSdjm struct sshbuf *ret; 9415b55daeSdjm 95*b691e04fStobias if ((ret = calloc(1, sizeof(*ret))) == NULL) 9615b55daeSdjm return NULL; 9715b55daeSdjm ret->alloc = SSHBUF_SIZE_INIT; 9815b55daeSdjm ret->max_size = SSHBUF_SIZE_MAX; 9915b55daeSdjm ret->readonly = 0; 10015b55daeSdjm ret->refcount = 1; 10115b55daeSdjm ret->parent = NULL; 10215b55daeSdjm if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) { 10315b55daeSdjm free(ret); 10415b55daeSdjm return NULL; 10515b55daeSdjm } 10615b55daeSdjm return ret; 10715b55daeSdjm } 10815b55daeSdjm 10915b55daeSdjm struct sshbuf * 11015b55daeSdjm sshbuf_from(const void *blob, size_t len) 11115b55daeSdjm { 11215b55daeSdjm struct sshbuf *ret; 11315b55daeSdjm 11415b55daeSdjm if (blob == NULL || len > SSHBUF_SIZE_MAX || 115*b691e04fStobias (ret = calloc(1, sizeof(*ret))) == NULL) 11615b55daeSdjm return NULL; 11715b55daeSdjm ret->alloc = ret->size = ret->max_size = len; 11815b55daeSdjm ret->readonly = 1; 11915b55daeSdjm ret->refcount = 1; 12015b55daeSdjm ret->parent = NULL; 12115b55daeSdjm ret->cd = blob; 12215b55daeSdjm ret->d = NULL; 12315b55daeSdjm return ret; 12415b55daeSdjm } 12515b55daeSdjm 12615b55daeSdjm int 12715b55daeSdjm sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent) 12815b55daeSdjm { 12915b55daeSdjm int r; 13015b55daeSdjm 13115b55daeSdjm if ((r = sshbuf_check_sanity(child)) != 0 || 13215b55daeSdjm (r = sshbuf_check_sanity(parent)) != 0) 13315b55daeSdjm return r; 1342afef00eStobias if ((child->parent != NULL && child->parent != parent) || 1352afef00eStobias child == parent) 136fe1380caSdjm return SSH_ERR_INTERNAL_ERROR; 13715b55daeSdjm child->parent = parent; 13815b55daeSdjm child->parent->refcount++; 13915b55daeSdjm return 0; 14015b55daeSdjm } 14115b55daeSdjm 14215b55daeSdjm struct sshbuf * 14315b55daeSdjm sshbuf_fromb(struct sshbuf *buf) 14415b55daeSdjm { 14515b55daeSdjm struct sshbuf *ret; 14615b55daeSdjm 14715b55daeSdjm if (sshbuf_check_sanity(buf) != 0) 14815b55daeSdjm return NULL; 14915b55daeSdjm if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL) 15015b55daeSdjm return NULL; 15115b55daeSdjm if (sshbuf_set_parent(ret, buf) != 0) { 15215b55daeSdjm sshbuf_free(ret); 15315b55daeSdjm return NULL; 15415b55daeSdjm } 15515b55daeSdjm return ret; 15615b55daeSdjm } 15715b55daeSdjm 15815b55daeSdjm void 15915b55daeSdjm sshbuf_free(struct sshbuf *buf) 16015b55daeSdjm { 16115b55daeSdjm if (buf == NULL) 16215b55daeSdjm return; 16315b55daeSdjm /* 16415b55daeSdjm * The following will leak on insane buffers, but this is the safest 16515b55daeSdjm * course of action - an invalid pointer or already-freed pointer may 16615b55daeSdjm * have been passed to us and continuing to scribble over memory would 16715b55daeSdjm * be bad. 16815b55daeSdjm */ 16915b55daeSdjm if (sshbuf_check_sanity(buf) != 0) 17015b55daeSdjm return; 17127871900Sdjm 17215b55daeSdjm /* 17315b55daeSdjm * If we are a parent with still-extant children, then don't free just 17415b55daeSdjm * yet. The last child's call to sshbuf_free should decrement our 17515b55daeSdjm * refcount to 0 and trigger the actual free. 17615b55daeSdjm */ 17715b55daeSdjm buf->refcount--; 17815b55daeSdjm if (buf->refcount > 0) 17915b55daeSdjm return; 18027871900Sdjm 18127871900Sdjm /* 18252a90c04Stobias * If we are a child, then free our parent to decrement its reference 18327871900Sdjm * count and possibly free it. 18427871900Sdjm */ 18527871900Sdjm sshbuf_free(buf->parent); 18627871900Sdjm buf->parent = NULL; 18727871900Sdjm 188413e8297Stobias if (!buf->readonly) 189413e8297Stobias freezero(buf->d, buf->alloc); 190c9831b39Sjsg freezero(buf, sizeof(*buf)); 19115b55daeSdjm } 19215b55daeSdjm 19315b55daeSdjm void 19415b55daeSdjm sshbuf_reset(struct sshbuf *buf) 19515b55daeSdjm { 19615b55daeSdjm u_char *d; 19715b55daeSdjm 19815b55daeSdjm if (buf->readonly || buf->refcount > 1) { 19915b55daeSdjm /* Nonsensical. Just make buffer appear empty */ 20015b55daeSdjm buf->off = buf->size; 20115b55daeSdjm return; 20215b55daeSdjm } 203fe1380caSdjm if (sshbuf_check_sanity(buf) != 0) 204fe1380caSdjm return; 20515b55daeSdjm buf->off = buf->size = 0; 20615b55daeSdjm if (buf->alloc != SSHBUF_SIZE_INIT) { 207eaf8e3f6Sderaadt if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT, 208eaf8e3f6Sderaadt 1)) != NULL) { 20915b55daeSdjm buf->cd = buf->d = d; 21015b55daeSdjm buf->alloc = SSHBUF_SIZE_INIT; 21115b55daeSdjm } 212327b1c69Sdjm } 213fe1380caSdjm explicit_bzero(buf->d, buf->alloc); 21415b55daeSdjm } 21515b55daeSdjm 21615b55daeSdjm size_t 21715b55daeSdjm sshbuf_max_size(const struct sshbuf *buf) 21815b55daeSdjm { 21915b55daeSdjm return buf->max_size; 22015b55daeSdjm } 22115b55daeSdjm 22215b55daeSdjm size_t 22315b55daeSdjm sshbuf_alloc(const struct sshbuf *buf) 22415b55daeSdjm { 22515b55daeSdjm return buf->alloc; 22615b55daeSdjm } 22715b55daeSdjm 22815b55daeSdjm const struct sshbuf * 22915b55daeSdjm sshbuf_parent(const struct sshbuf *buf) 23015b55daeSdjm { 23115b55daeSdjm return buf->parent; 23215b55daeSdjm } 23315b55daeSdjm 23415b55daeSdjm u_int 23515b55daeSdjm sshbuf_refcount(const struct sshbuf *buf) 23615b55daeSdjm { 23715b55daeSdjm return buf->refcount; 23815b55daeSdjm } 23915b55daeSdjm 24015b55daeSdjm int 24115b55daeSdjm sshbuf_set_max_size(struct sshbuf *buf, size_t max_size) 24215b55daeSdjm { 24315b55daeSdjm size_t rlen; 24415b55daeSdjm u_char *dp; 24515b55daeSdjm int r; 24615b55daeSdjm 247a8cbed27Sdjm SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size)); 24815b55daeSdjm if ((r = sshbuf_check_sanity(buf)) != 0) 24915b55daeSdjm return r; 25015b55daeSdjm if (max_size == buf->max_size) 25115b55daeSdjm return 0; 25215b55daeSdjm if (buf->readonly || buf->refcount > 1) 25315b55daeSdjm return SSH_ERR_BUFFER_READ_ONLY; 25415b55daeSdjm if (max_size > SSHBUF_SIZE_MAX) 25515b55daeSdjm return SSH_ERR_NO_BUFFER_SPACE; 25615b55daeSdjm /* pack and realloc if necessary */ 25715b55daeSdjm sshbuf_maybe_pack(buf, max_size < buf->size); 25815b55daeSdjm if (max_size < buf->alloc && max_size > buf->size) { 25915b55daeSdjm if (buf->size < SSHBUF_SIZE_INIT) 26015b55daeSdjm rlen = SSHBUF_SIZE_INIT; 26115b55daeSdjm else 26203db5a1fSderaadt rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC); 26315b55daeSdjm if (rlen > max_size) 26415b55daeSdjm rlen = max_size; 265a8cbed27Sdjm SSHBUF_DBG(("new alloc = %zu", rlen)); 266eaf8e3f6Sderaadt if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) 26715b55daeSdjm return SSH_ERR_ALLOC_FAIL; 26815b55daeSdjm buf->cd = buf->d = dp; 26915b55daeSdjm buf->alloc = rlen; 27015b55daeSdjm } 27115b55daeSdjm SSHBUF_TELL("new-max"); 27215b55daeSdjm if (max_size < buf->alloc) 27315b55daeSdjm return SSH_ERR_NO_BUFFER_SPACE; 27415b55daeSdjm buf->max_size = max_size; 27515b55daeSdjm return 0; 27615b55daeSdjm } 27715b55daeSdjm 27815b55daeSdjm size_t 27915b55daeSdjm sshbuf_len(const struct sshbuf *buf) 28015b55daeSdjm { 28115b55daeSdjm if (sshbuf_check_sanity(buf) != 0) 28215b55daeSdjm return 0; 28315b55daeSdjm return buf->size - buf->off; 28415b55daeSdjm } 28515b55daeSdjm 28615b55daeSdjm size_t 28715b55daeSdjm sshbuf_avail(const struct sshbuf *buf) 28815b55daeSdjm { 28915b55daeSdjm if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1) 29015b55daeSdjm return 0; 29115b55daeSdjm return buf->max_size - (buf->size - buf->off); 29215b55daeSdjm } 29315b55daeSdjm 29415b55daeSdjm const u_char * 29515b55daeSdjm sshbuf_ptr(const struct sshbuf *buf) 29615b55daeSdjm { 29715b55daeSdjm if (sshbuf_check_sanity(buf) != 0) 29815b55daeSdjm return NULL; 29915b55daeSdjm return buf->cd + buf->off; 30015b55daeSdjm } 30115b55daeSdjm 30215b55daeSdjm u_char * 30315b55daeSdjm sshbuf_mutable_ptr(const struct sshbuf *buf) 30415b55daeSdjm { 30515b55daeSdjm if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1) 30615b55daeSdjm return NULL; 30715b55daeSdjm return buf->d + buf->off; 30815b55daeSdjm } 30915b55daeSdjm 31015b55daeSdjm int 31115b55daeSdjm sshbuf_check_reserve(const struct sshbuf *buf, size_t len) 31215b55daeSdjm { 31315b55daeSdjm int r; 31415b55daeSdjm 31515b55daeSdjm if ((r = sshbuf_check_sanity(buf)) != 0) 31615b55daeSdjm return r; 31715b55daeSdjm if (buf->readonly || buf->refcount > 1) 31815b55daeSdjm return SSH_ERR_BUFFER_READ_ONLY; 31915b55daeSdjm SSHBUF_TELL("check"); 32015b55daeSdjm /* Check that len is reasonable and that max_size + available < len */ 32115b55daeSdjm if (len > buf->max_size || buf->max_size - len < buf->size - buf->off) 32215b55daeSdjm return SSH_ERR_NO_BUFFER_SPACE; 32315b55daeSdjm return 0; 32415b55daeSdjm } 32515b55daeSdjm 32615b55daeSdjm int 32766d9ceccSdjm sshbuf_allocate(struct sshbuf *buf, size_t len) 32815b55daeSdjm { 32915b55daeSdjm size_t rlen, need; 33015b55daeSdjm u_char *dp; 33115b55daeSdjm int r; 33215b55daeSdjm 333a8cbed27Sdjm SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len)); 33415b55daeSdjm if ((r = sshbuf_check_reserve(buf, len)) != 0) 33515b55daeSdjm return r; 33615b55daeSdjm /* 33715b55daeSdjm * If the requested allocation appended would push us past max_size 33815b55daeSdjm * then pack the buffer, zeroing buf->off. 33915b55daeSdjm */ 34015b55daeSdjm sshbuf_maybe_pack(buf, buf->size + len > buf->max_size); 34166d9ceccSdjm SSHBUF_TELL("allocate"); 34266d9ceccSdjm if (len + buf->size <= buf->alloc) 34366d9ceccSdjm return 0; /* already have it. */ 34466d9ceccSdjm 34515b55daeSdjm /* 34615b55daeSdjm * Prefer to alloc in SSHBUF_SIZE_INC units, but 34715b55daeSdjm * allocate less if doing so would overflow max_size. 34815b55daeSdjm */ 34915b55daeSdjm need = len + buf->size - buf->alloc; 35003db5a1fSderaadt rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC); 351a8cbed27Sdjm SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen)); 35215b55daeSdjm if (rlen > buf->max_size) 35315b55daeSdjm rlen = buf->alloc + need; 354a8cbed27Sdjm SSHBUF_DBG(("adjusted rlen %zu", rlen)); 355eaf8e3f6Sderaadt if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) { 356a8cbed27Sdjm SSHBUF_DBG(("realloc fail")); 35715b55daeSdjm return SSH_ERR_ALLOC_FAIL; 35815b55daeSdjm } 35915b55daeSdjm buf->alloc = rlen; 36015b55daeSdjm buf->cd = buf->d = dp; 36115b55daeSdjm if ((r = sshbuf_check_reserve(buf, len)) < 0) { 36215b55daeSdjm /* shouldn't fail */ 36315b55daeSdjm return r; 36415b55daeSdjm } 36566d9ceccSdjm SSHBUF_TELL("done"); 36666d9ceccSdjm return 0; 36715b55daeSdjm } 36866d9ceccSdjm 36966d9ceccSdjm int 37066d9ceccSdjm sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp) 37166d9ceccSdjm { 37266d9ceccSdjm u_char *dp; 37366d9ceccSdjm int r; 37466d9ceccSdjm 37566d9ceccSdjm if (dpp != NULL) 37666d9ceccSdjm *dpp = NULL; 37766d9ceccSdjm 378a8cbed27Sdjm SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len)); 37966d9ceccSdjm if ((r = sshbuf_allocate(buf, len)) != 0) 38066d9ceccSdjm return r; 38166d9ceccSdjm 38215b55daeSdjm dp = buf->d + buf->size; 38315b55daeSdjm buf->size += len; 38415b55daeSdjm if (dpp != NULL) 38515b55daeSdjm *dpp = dp; 38615b55daeSdjm return 0; 38715b55daeSdjm } 38815b55daeSdjm 38915b55daeSdjm int 39015b55daeSdjm sshbuf_consume(struct sshbuf *buf, size_t len) 39115b55daeSdjm { 39215b55daeSdjm int r; 39315b55daeSdjm 394a8cbed27Sdjm SSHBUF_DBG(("len = %zu", len)); 39515b55daeSdjm if ((r = sshbuf_check_sanity(buf)) != 0) 39615b55daeSdjm return r; 39715b55daeSdjm if (len == 0) 39815b55daeSdjm return 0; 39915b55daeSdjm if (len > sshbuf_len(buf)) 40015b55daeSdjm return SSH_ERR_MESSAGE_INCOMPLETE; 40115b55daeSdjm buf->off += len; 40249ae696bSmarkus /* deal with empty buffer */ 40349ae696bSmarkus if (buf->off == buf->size) 40449ae696bSmarkus buf->off = buf->size = 0; 40515b55daeSdjm SSHBUF_TELL("done"); 40615b55daeSdjm return 0; 40715b55daeSdjm } 40815b55daeSdjm 40915b55daeSdjm int 41015b55daeSdjm sshbuf_consume_end(struct sshbuf *buf, size_t len) 41115b55daeSdjm { 41215b55daeSdjm int r; 41315b55daeSdjm 414a8cbed27Sdjm SSHBUF_DBG(("len = %zu", len)); 41515b55daeSdjm if ((r = sshbuf_check_sanity(buf)) != 0) 41615b55daeSdjm return r; 41715b55daeSdjm if (len == 0) 41815b55daeSdjm return 0; 41915b55daeSdjm if (len > sshbuf_len(buf)) 42015b55daeSdjm return SSH_ERR_MESSAGE_INCOMPLETE; 42115b55daeSdjm buf->size -= len; 42215b55daeSdjm SSHBUF_TELL("done"); 42315b55daeSdjm return 0; 42415b55daeSdjm } 42515b55daeSdjm 426