sshbuf.c revision 296853
159477Swpaul/* $OpenBSD: sshbuf.c,v 1.6 2016/01/12 23:42:54 djm Exp $ */ 259477Swpaul/* 359477Swpaul * Copyright (c) 2011 Damien Miller 459477Swpaul * 559477Swpaul * Permission to use, copy, modify, and distribute this software for any 659477Swpaul * purpose with or without fee is hereby granted, provided that the above 759477Swpaul * copyright notice and this permission notice appear in all copies. 859477Swpaul * 959477Swpaul * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1059477Swpaul * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1159477Swpaul * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1259477Swpaul * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1359477Swpaul * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1459477Swpaul * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1559477Swpaul * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1659477Swpaul */ 1759477Swpaul 1859477Swpaul#define SSHBUF_INTERNAL 1959477Swpaul#include "includes.h" 2059477Swpaul 2159477Swpaul#include <sys/param.h> /* roundup */ 2259477Swpaul#include <sys/types.h> 2359477Swpaul#include <signal.h> 2459477Swpaul#include <stdlib.h> 2559477Swpaul#include <stdio.h> 2659477Swpaul#include <string.h> 2759477Swpaul 2859477Swpaul#include "ssherr.h" 2959477Swpaul#include "sshbuf.h" 3059477Swpaul 3159477Swpaulstatic inline int 3259477Swpaulsshbuf_check_sanity(const struct sshbuf *buf) 3359477Swpaul{ 3459477Swpaul SSHBUF_TELL("sanity"); 3559477Swpaul if (__predict_false(buf == NULL || 3659477Swpaul (!buf->readonly && buf->d != buf->cd) || 3759477Swpaul buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX || 3859477Swpaul buf->cd == NULL || 3959477Swpaul (buf->dont_free && (buf->readonly || buf->parent != NULL)) || 4059477Swpaul buf->max_size > SSHBUF_SIZE_MAX || 4159477Swpaul buf->alloc > buf->max_size || 4259477Swpaul buf->size > buf->alloc || 4359477Swpaul buf->off > buf->size)) { 4459477Swpaul /* Do not try to recover from corrupted buffer internals */ 4559477Swpaul SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR")); 4659477Swpaul signal(SIGSEGV, SIG_DFL); 4783029Swpaul raise(SIGSEGV); 4859477Swpaul return SSH_ERR_INTERNAL_ERROR; 4959477Swpaul } 5059477Swpaul return 0; 5159477Swpaul} 5259477Swpaul 5359477Swpaulstatic void 5459477Swpaulsshbuf_maybe_pack(struct sshbuf *buf, int force) 5559477Swpaul{ 5659477Swpaul SSHBUF_DBG(("force %d", force)); 5759477Swpaul SSHBUF_TELL("pre-pack"); 5859477Swpaul if (buf->off == 0 || buf->readonly || buf->refcount > 1) 5959477Swpaul return; 6059477Swpaul if (force || 6159477Swpaul (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) { 6259477Swpaul memmove(buf->d, buf->d + buf->off, buf->size - buf->off); 6359477Swpaul buf->size -= buf->off; 6459477Swpaul buf->off = 0; 6559477Swpaul SSHBUF_TELL("packed"); 6659477Swpaul } 6759477Swpaul} 6859477Swpaul 6959477Swpaulstruct sshbuf * 7059477Swpaulsshbuf_new(void) 7159477Swpaul{ 7259477Swpaul struct sshbuf *ret; 7359477Swpaul 7459477Swpaul if ((ret = calloc(sizeof(*ret), 1)) == NULL) 7559477Swpaul return NULL; 7659477Swpaul ret->alloc = SSHBUF_SIZE_INIT; 7759477Swpaul ret->max_size = SSHBUF_SIZE_MAX; 7859477Swpaul ret->readonly = 0; 7959477Swpaul ret->refcount = 1; 8059477Swpaul ret->parent = NULL; 8159477Swpaul if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) { 8259477Swpaul free(ret); 8359477Swpaul return NULL; 8459477Swpaul } 8559477Swpaul return ret; 8659477Swpaul} 8759477Swpaul 8859477Swpaulstruct sshbuf * 8959477Swpaulsshbuf_from(const void *blob, size_t len) 9059477Swpaul{ 9159477Swpaul struct sshbuf *ret; 9259477Swpaul 9359477Swpaul if (blob == NULL || len > SSHBUF_SIZE_MAX || 9459477Swpaul (ret = calloc(sizeof(*ret), 1)) == NULL) 9559477Swpaul return NULL; 9659477Swpaul ret->alloc = ret->size = ret->max_size = len; 9759477Swpaul ret->readonly = 1; 9859477Swpaul ret->refcount = 1; 9959477Swpaul ret->parent = NULL; 10059477Swpaul ret->cd = blob; 10183029Swpaul ret->d = NULL; 10283029Swpaul return ret; 10383029Swpaul} 10483029Swpaul 10583029Swpaulint 10659477Swpaulsshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent) 10783029Swpaul{ 10883029Swpaul int r; 10983029Swpaul 11083029Swpaul if ((r = sshbuf_check_sanity(child)) != 0 || 11183029Swpaul (r = sshbuf_check_sanity(parent)) != 0) 11259477Swpaul return r; 11383029Swpaul child->parent = parent; 11483029Swpaul child->parent->refcount++; 11583029Swpaul return 0; 11683029Swpaul} 11783029Swpaul 11883029Swpaulstruct sshbuf * 11983029Swpaulsshbuf_fromb(struct sshbuf *buf) 12059477Swpaul{ 12159477Swpaul struct sshbuf *ret; 12259477Swpaul 12359477Swpaul if (sshbuf_check_sanity(buf) != 0) 12459477Swpaul return NULL; 12559477Swpaul if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL) 12659477Swpaul return NULL; 12759477Swpaul if (sshbuf_set_parent(ret, buf) != 0) { 12859477Swpaul sshbuf_free(ret); 12959477Swpaul return NULL; 13059477Swpaul } 13159477Swpaul return ret; 13259477Swpaul} 13359477Swpaul 13459477Swpaulvoid 13559477Swpaulsshbuf_init(struct sshbuf *ret) 13659477Swpaul{ 13759477Swpaul explicit_bzero(ret, sizeof(*ret)); 13859477Swpaul ret->alloc = SSHBUF_SIZE_INIT; 13959477Swpaul ret->max_size = SSHBUF_SIZE_MAX; 14059477Swpaul ret->readonly = 0; 14159477Swpaul ret->dont_free = 1; 14259477Swpaul ret->refcount = 1; 14359477Swpaul if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) 14459477Swpaul ret->alloc = 0; 14559477Swpaul} 14659477Swpaul 14759477Swpaulvoid 14859477Swpaulsshbuf_free(struct sshbuf *buf) 14959477Swpaul{ 15059477Swpaul int dont_free = 0; 15159477Swpaul 15259477Swpaul if (buf == NULL) 15359477Swpaul return; 15459477Swpaul /* 15559477Swpaul * The following will leak on insane buffers, but this is the safest 15683029Swpaul * course of action - an invalid pointer or already-freed pointer may 15783029Swpaul * have been passed to us and continuing to scribble over memory would 15883029Swpaul * be bad. 15959477Swpaul */ 16083029Swpaul if (sshbuf_check_sanity(buf) != 0) 16183029Swpaul return; 16283029Swpaul /* 16359477Swpaul * If we are a child, the free our parent to decrement its reference 16459477Swpaul * count and possibly free it. 16583029Swpaul */ 16659477Swpaul sshbuf_free(buf->parent); 16759477Swpaul buf->parent = NULL; 16859477Swpaul /* 16959477Swpaul * If we are a parent with still-extant children, then don't free just 17059477Swpaul * yet. The last child's call to sshbuf_free should decrement our 17159477Swpaul * refcount to 0 and trigger the actual free. 17259477Swpaul */ 17359477Swpaul buf->refcount--; 17459477Swpaul if (buf->refcount > 0) 17559477Swpaul return; 17659477Swpaul dont_free = buf->dont_free; 17759477Swpaul if (!buf->readonly) { 17859477Swpaul explicit_bzero(buf->d, buf->alloc); 17959477Swpaul free(buf->d); 18059477Swpaul } 18159477Swpaul explicit_bzero(buf, sizeof(*buf)); 18259477Swpaul if (!dont_free) 18359477Swpaul free(buf); 18459477Swpaul} 18559477Swpaul 18659477Swpaulvoid 18769925Swpaulsshbuf_reset(struct sshbuf *buf) 18869925Swpaul{ 18983029Swpaul u_char *d; 19059477Swpaul 19159477Swpaul if (buf->readonly || buf->refcount > 1) { 19259477Swpaul /* Nonsensical. Just make buffer appear empty */ 19359477Swpaul buf->off = buf->size; 19459477Swpaul return; 19559477Swpaul } 19659477Swpaul if (sshbuf_check_sanity(buf) == 0) 19759477Swpaul explicit_bzero(buf->d, buf->alloc); 19859477Swpaul buf->off = buf->size = 0; 19959477Swpaul if (buf->alloc != SSHBUF_SIZE_INIT) { 20059477Swpaul if ((d = realloc(buf->d, SSHBUF_SIZE_INIT)) != NULL) { 20159477Swpaul buf->cd = buf->d = d; 20283029Swpaul buf->alloc = SSHBUF_SIZE_INIT; 20359477Swpaul } 20459477Swpaul } 20559477Swpaul} 20659477Swpaul 20759477Swpaulsize_t 20859477Swpaulsshbuf_max_size(const struct sshbuf *buf) 20959477Swpaul{ 21059477Swpaul return buf->max_size; 21159477Swpaul} 21259477Swpaul 21359477Swpaulsize_t 21459477Swpaulsshbuf_alloc(const struct sshbuf *buf) 21559477Swpaul{ 21659477Swpaul return buf->alloc; 21759477Swpaul} 21859477Swpaul 21959477Swpaulconst struct sshbuf * 22059477Swpaulsshbuf_parent(const struct sshbuf *buf) 22159477Swpaul{ 22259477Swpaul return buf->parent; 22359477Swpaul} 22459477Swpaul 22559477Swpaulu_int 22659477Swpaulsshbuf_refcount(const struct sshbuf *buf) 22759477Swpaul{ 22859477Swpaul return buf->refcount; 22959477Swpaul} 23059477Swpaul 23159477Swpaulint 23259477Swpaulsshbuf_set_max_size(struct sshbuf *buf, size_t max_size) 23359477Swpaul{ 23459477Swpaul size_t rlen; 23559477Swpaul u_char *dp; 23659477Swpaul int r; 23759477Swpaul 23859477Swpaul SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size)); 23959477Swpaul if ((r = sshbuf_check_sanity(buf)) != 0) 24059477Swpaul return r; 24159477Swpaul if (max_size == buf->max_size) 24259477Swpaul return 0; 24359477Swpaul if (buf->readonly || buf->refcount > 1) 24459477Swpaul return SSH_ERR_BUFFER_READ_ONLY; 24559477Swpaul if (max_size > SSHBUF_SIZE_MAX) 24659477Swpaul return SSH_ERR_NO_BUFFER_SPACE; 24759477Swpaul /* pack and realloc if necessary */ 24883029Swpaul sshbuf_maybe_pack(buf, max_size < buf->size); 24983029Swpaul if (max_size < buf->alloc && max_size > buf->size) { 25083029Swpaul if (buf->size < SSHBUF_SIZE_INIT) 25183029Swpaul rlen = SSHBUF_SIZE_INIT; 25283029Swpaul else 25383029Swpaul rlen = roundup(buf->size, SSHBUF_SIZE_INC); 25483029Swpaul if (rlen > max_size) 25583029Swpaul rlen = max_size; 25659477Swpaul explicit_bzero(buf->d + buf->size, buf->alloc - buf->size); 25759477Swpaul SSHBUF_DBG(("new alloc = %zu", rlen)); 25883029Swpaul if ((dp = realloc(buf->d, rlen)) == NULL) 25959477Swpaul return SSH_ERR_ALLOC_FAIL; 26083029Swpaul buf->cd = buf->d = dp; 26159477Swpaul buf->alloc = rlen; 26259477Swpaul } 26359477Swpaul SSHBUF_TELL("new-max"); 26483029Swpaul if (max_size < buf->alloc) 26583029Swpaul return SSH_ERR_NO_BUFFER_SPACE; 26683029Swpaul buf->max_size = max_size; 26759477Swpaul return 0; 26859477Swpaul} 26959477Swpaul 27059477Swpaulsize_t 27159477Swpaulsshbuf_len(const struct sshbuf *buf) 27259477Swpaul{ 27359477Swpaul if (sshbuf_check_sanity(buf) != 0) 27459477Swpaul return 0; 27559477Swpaul return buf->size - buf->off; 27659477Swpaul} 27759477Swpaul 27859477Swpaulsize_t 27959477Swpaulsshbuf_avail(const struct sshbuf *buf) 28059477Swpaul{ 28159477Swpaul if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1) 28259477Swpaul return 0; 28383597Swpaul return buf->max_size - (buf->size - buf->off); 28483597Swpaul} 28583597Swpaul 28683597Swpaulconst u_char * 28783597Swpaulsshbuf_ptr(const struct sshbuf *buf) 28859477Swpaul{ 28959477Swpaul if (sshbuf_check_sanity(buf) != 0) 29059477Swpaul return NULL; 29159477Swpaul return buf->cd + buf->off; 29259477Swpaul} 29359477Swpaul 29459477Swpaulu_char * 29559477Swpaulsshbuf_mutable_ptr(const struct sshbuf *buf) 29659477Swpaul{ 29759477Swpaul if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1) 29859477Swpaul return NULL; 29959477Swpaul return buf->d + buf->off; 30059477Swpaul} 30159477Swpaul 30259477Swpaulint 30359477Swpaulsshbuf_check_reserve(const struct sshbuf *buf, size_t len) 30459477Swpaul{ 30559477Swpaul int r; 30659477Swpaul 30759477Swpaul if ((r = sshbuf_check_sanity(buf)) != 0) 30859477Swpaul return r; 30959477Swpaul if (buf->readonly || buf->refcount > 1) 31059477Swpaul return SSH_ERR_BUFFER_READ_ONLY; 31159477Swpaul SSHBUF_TELL("check"); 31259477Swpaul /* Check that len is reasonable and that max_size + available < len */ 31359477Swpaul if (len > buf->max_size || buf->max_size - len < buf->size - buf->off) 31459477Swpaul return SSH_ERR_NO_BUFFER_SPACE; 31559477Swpaul return 0; 31659477Swpaul} 31759477Swpaul 31859477Swpaulint 31959477Swpaulsshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp) 32059477Swpaul{ 32159477Swpaul size_t rlen, need; 32259477Swpaul u_char *dp; 32359477Swpaul int r; 32459477Swpaul 32559477Swpaul if (dpp != NULL) 32659477Swpaul *dpp = NULL; 32759477Swpaul 32859477Swpaul SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len)); 32959477Swpaul if ((r = sshbuf_check_reserve(buf, len)) != 0) 33059477Swpaul return r; 33159477Swpaul /* 33259477Swpaul * If the requested allocation appended would push us past max_size 33359477Swpaul * then pack the buffer, zeroing buf->off. 33459477Swpaul */ 33559477Swpaul sshbuf_maybe_pack(buf, buf->size + len > buf->max_size); 33659477Swpaul SSHBUF_TELL("reserve"); 33759477Swpaul if (len + buf->size > buf->alloc) { 33859477Swpaul /* 33959477Swpaul * Prefer to alloc in SSHBUF_SIZE_INC units, but 34059477Swpaul * allocate less if doing so would overflow max_size. 34159477Swpaul */ 34259477Swpaul need = len + buf->size - buf->alloc; 34359477Swpaul rlen = roundup(buf->alloc + need, SSHBUF_SIZE_INC); 34459477Swpaul SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen)); 34559477Swpaul if (rlen > buf->max_size) 34659477Swpaul rlen = buf->alloc + need; 34759477Swpaul SSHBUF_DBG(("adjusted rlen %zu", rlen)); 34859477Swpaul if ((dp = realloc(buf->d, rlen)) == NULL) { 34959477Swpaul SSHBUF_DBG(("realloc fail")); 35059477Swpaul if (dpp != NULL) 35159477Swpaul *dpp = NULL; 35283029Swpaul return SSH_ERR_ALLOC_FAIL; 35383029Swpaul } 35459477Swpaul buf->alloc = rlen; 35559477Swpaul buf->cd = buf->d = dp; 35659477Swpaul if ((r = sshbuf_check_reserve(buf, len)) < 0) { 35759477Swpaul /* shouldn't fail */ 35859477Swpaul if (dpp != NULL) 35959477Swpaul *dpp = NULL; 36059477Swpaul return r; 36159477Swpaul } 36259477Swpaul } 36359477Swpaul dp = buf->d + buf->size; 36459477Swpaul buf->size += len; 36559477Swpaul SSHBUF_TELL("done"); 36659477Swpaul if (dpp != NULL) 36759477Swpaul *dpp = dp; 36859477Swpaul return 0; 36959477Swpaul} 37059477Swpaul 37159477Swpaulint 37259477Swpaulsshbuf_consume(struct sshbuf *buf, size_t len) 37359477Swpaul{ 37483029Swpaul int r; 37583029Swpaul 37683029Swpaul SSHBUF_DBG(("len = %zu", len)); 37783029Swpaul if ((r = sshbuf_check_sanity(buf)) != 0) 37883029Swpaul return r; 37983029Swpaul if (len == 0) 38083029Swpaul return 0; 38183029Swpaul if (len > sshbuf_len(buf)) 38283029Swpaul return SSH_ERR_MESSAGE_INCOMPLETE; 38383029Swpaul buf->off += len; 38483029Swpaul SSHBUF_TELL("done"); 38583029Swpaul return 0; 38683029Swpaul} 38783029Swpaul 38883029Swpaulint 38983029Swpaulsshbuf_consume_end(struct sshbuf *buf, size_t len) 39083029Swpaul{ 39183029Swpaul int r; 39283029Swpaul 39383029Swpaul SSHBUF_DBG(("len = %zu", len)); 39483029Swpaul if ((r = sshbuf_check_sanity(buf)) != 0) 39583029Swpaul return r; 39683029Swpaul if (len == 0) 39783029Swpaul return 0; 39883029Swpaul if (len > sshbuf_len(buf)) 39983029Swpaul return SSH_ERR_MESSAGE_INCOMPLETE; 40083029Swpaul buf->size -= len; 40159477Swpaul SSHBUF_TELL("done"); 40259477Swpaul return 0; 40359477Swpaul} 40483029Swpaul 40559477Swpaul