1296781Sdes/* $OpenBSD: sshbuf.c,v 1.6 2016/01/12 23:42:54 djm Exp $ */ 2276707Sdes/* 3276707Sdes * Copyright (c) 2011 Damien Miller 4276707Sdes * 5276707Sdes * Permission to use, copy, modify, and distribute this software for any 6276707Sdes * purpose with or without fee is hereby granted, provided that the above 7276707Sdes * copyright notice and this permission notice appear in all copies. 8276707Sdes * 9276707Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10276707Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11276707Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12276707Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13276707Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14276707Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15276707Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16276707Sdes */ 17276707Sdes 18276707Sdes#define SSHBUF_INTERNAL 19276707Sdes#include "includes.h" 20276707Sdes 21295367Sdes#include <sys/param.h> /* roundup */ 22276707Sdes#include <sys/types.h> 23276707Sdes#include <signal.h> 24276707Sdes#include <stdlib.h> 25276707Sdes#include <stdio.h> 26276707Sdes#include <string.h> 27276707Sdes 28276707Sdes#include "ssherr.h" 29276707Sdes#include "sshbuf.h" 30276707Sdes 31276707Sdesstatic inline int 32276707Sdessshbuf_check_sanity(const struct sshbuf *buf) 33276707Sdes{ 34276707Sdes SSHBUF_TELL("sanity"); 35276707Sdes if (__predict_false(buf == NULL || 36276707Sdes (!buf->readonly && buf->d != buf->cd) || 37276707Sdes buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX || 38276707Sdes buf->cd == NULL || 39276707Sdes (buf->dont_free && (buf->readonly || buf->parent != NULL)) || 40276707Sdes buf->max_size > SSHBUF_SIZE_MAX || 41276707Sdes buf->alloc > buf->max_size || 42276707Sdes buf->size > buf->alloc || 43276707Sdes buf->off > buf->size)) { 44276707Sdes /* Do not try to recover from corrupted buffer internals */ 45276707Sdes SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR")); 46276707Sdes signal(SIGSEGV, SIG_DFL); 47276707Sdes raise(SIGSEGV); 48276707Sdes return SSH_ERR_INTERNAL_ERROR; 49276707Sdes } 50276707Sdes return 0; 51276707Sdes} 52276707Sdes 53276707Sdesstatic void 54276707Sdessshbuf_maybe_pack(struct sshbuf *buf, int force) 55276707Sdes{ 56276707Sdes SSHBUF_DBG(("force %d", force)); 57276707Sdes SSHBUF_TELL("pre-pack"); 58276707Sdes if (buf->off == 0 || buf->readonly || buf->refcount > 1) 59276707Sdes return; 60276707Sdes if (force || 61276707Sdes (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) { 62276707Sdes memmove(buf->d, buf->d + buf->off, buf->size - buf->off); 63276707Sdes buf->size -= buf->off; 64276707Sdes buf->off = 0; 65276707Sdes SSHBUF_TELL("packed"); 66276707Sdes } 67276707Sdes} 68276707Sdes 69276707Sdesstruct sshbuf * 70276707Sdessshbuf_new(void) 71276707Sdes{ 72276707Sdes struct sshbuf *ret; 73276707Sdes 74276707Sdes if ((ret = calloc(sizeof(*ret), 1)) == NULL) 75276707Sdes return NULL; 76276707Sdes ret->alloc = SSHBUF_SIZE_INIT; 77276707Sdes ret->max_size = SSHBUF_SIZE_MAX; 78276707Sdes ret->readonly = 0; 79276707Sdes ret->refcount = 1; 80276707Sdes ret->parent = NULL; 81276707Sdes if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) { 82276707Sdes free(ret); 83276707Sdes return NULL; 84276707Sdes } 85276707Sdes return ret; 86276707Sdes} 87276707Sdes 88276707Sdesstruct sshbuf * 89276707Sdessshbuf_from(const void *blob, size_t len) 90276707Sdes{ 91276707Sdes struct sshbuf *ret; 92276707Sdes 93276707Sdes if (blob == NULL || len > SSHBUF_SIZE_MAX || 94276707Sdes (ret = calloc(sizeof(*ret), 1)) == NULL) 95276707Sdes return NULL; 96276707Sdes ret->alloc = ret->size = ret->max_size = len; 97276707Sdes ret->readonly = 1; 98276707Sdes ret->refcount = 1; 99276707Sdes ret->parent = NULL; 100276707Sdes ret->cd = blob; 101276707Sdes ret->d = NULL; 102276707Sdes return ret; 103276707Sdes} 104276707Sdes 105276707Sdesint 106276707Sdessshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent) 107276707Sdes{ 108276707Sdes int r; 109276707Sdes 110276707Sdes if ((r = sshbuf_check_sanity(child)) != 0 || 111276707Sdes (r = sshbuf_check_sanity(parent)) != 0) 112276707Sdes return r; 113276707Sdes child->parent = parent; 114276707Sdes child->parent->refcount++; 115276707Sdes return 0; 116276707Sdes} 117276707Sdes 118276707Sdesstruct sshbuf * 119276707Sdessshbuf_fromb(struct sshbuf *buf) 120276707Sdes{ 121276707Sdes struct sshbuf *ret; 122276707Sdes 123276707Sdes if (sshbuf_check_sanity(buf) != 0) 124276707Sdes return NULL; 125276707Sdes if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL) 126276707Sdes return NULL; 127276707Sdes if (sshbuf_set_parent(ret, buf) != 0) { 128276707Sdes sshbuf_free(ret); 129276707Sdes return NULL; 130276707Sdes } 131276707Sdes return ret; 132276707Sdes} 133276707Sdes 134276707Sdesvoid 135276707Sdessshbuf_init(struct sshbuf *ret) 136276707Sdes{ 137295367Sdes explicit_bzero(ret, sizeof(*ret)); 138276707Sdes ret->alloc = SSHBUF_SIZE_INIT; 139276707Sdes ret->max_size = SSHBUF_SIZE_MAX; 140276707Sdes ret->readonly = 0; 141276707Sdes ret->dont_free = 1; 142276707Sdes ret->refcount = 1; 143276707Sdes if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) 144276707Sdes ret->alloc = 0; 145276707Sdes} 146276707Sdes 147276707Sdesvoid 148276707Sdessshbuf_free(struct sshbuf *buf) 149276707Sdes{ 150276707Sdes int dont_free = 0; 151276707Sdes 152276707Sdes if (buf == NULL) 153276707Sdes return; 154276707Sdes /* 155276707Sdes * The following will leak on insane buffers, but this is the safest 156276707Sdes * course of action - an invalid pointer or already-freed pointer may 157276707Sdes * have been passed to us and continuing to scribble over memory would 158276707Sdes * be bad. 159276707Sdes */ 160276707Sdes if (sshbuf_check_sanity(buf) != 0) 161276707Sdes return; 162276707Sdes /* 163276707Sdes * If we are a child, the free our parent to decrement its reference 164276707Sdes * count and possibly free it. 165276707Sdes */ 166296781Sdes sshbuf_free(buf->parent); 167296781Sdes buf->parent = NULL; 168276707Sdes /* 169276707Sdes * If we are a parent with still-extant children, then don't free just 170276707Sdes * yet. The last child's call to sshbuf_free should decrement our 171276707Sdes * refcount to 0 and trigger the actual free. 172276707Sdes */ 173276707Sdes buf->refcount--; 174276707Sdes if (buf->refcount > 0) 175276707Sdes return; 176276707Sdes dont_free = buf->dont_free; 177276707Sdes if (!buf->readonly) { 178295367Sdes explicit_bzero(buf->d, buf->alloc); 179276707Sdes free(buf->d); 180276707Sdes } 181295367Sdes explicit_bzero(buf, sizeof(*buf)); 182276707Sdes if (!dont_free) 183276707Sdes free(buf); 184276707Sdes} 185276707Sdes 186276707Sdesvoid 187276707Sdessshbuf_reset(struct sshbuf *buf) 188276707Sdes{ 189276707Sdes u_char *d; 190276707Sdes 191276707Sdes if (buf->readonly || buf->refcount > 1) { 192276707Sdes /* Nonsensical. Just make buffer appear empty */ 193276707Sdes buf->off = buf->size; 194276707Sdes return; 195276707Sdes } 196276707Sdes if (sshbuf_check_sanity(buf) == 0) 197295367Sdes explicit_bzero(buf->d, buf->alloc); 198276707Sdes buf->off = buf->size = 0; 199276707Sdes if (buf->alloc != SSHBUF_SIZE_INIT) { 200276707Sdes if ((d = realloc(buf->d, SSHBUF_SIZE_INIT)) != NULL) { 201276707Sdes buf->cd = buf->d = d; 202276707Sdes buf->alloc = SSHBUF_SIZE_INIT; 203276707Sdes } 204276707Sdes } 205276707Sdes} 206276707Sdes 207276707Sdessize_t 208276707Sdessshbuf_max_size(const struct sshbuf *buf) 209276707Sdes{ 210276707Sdes return buf->max_size; 211276707Sdes} 212276707Sdes 213276707Sdessize_t 214276707Sdessshbuf_alloc(const struct sshbuf *buf) 215276707Sdes{ 216276707Sdes return buf->alloc; 217276707Sdes} 218276707Sdes 219276707Sdesconst struct sshbuf * 220276707Sdessshbuf_parent(const struct sshbuf *buf) 221276707Sdes{ 222276707Sdes return buf->parent; 223276707Sdes} 224276707Sdes 225276707Sdesu_int 226276707Sdessshbuf_refcount(const struct sshbuf *buf) 227276707Sdes{ 228276707Sdes return buf->refcount; 229276707Sdes} 230276707Sdes 231276707Sdesint 232276707Sdessshbuf_set_max_size(struct sshbuf *buf, size_t max_size) 233276707Sdes{ 234276707Sdes size_t rlen; 235276707Sdes u_char *dp; 236276707Sdes int r; 237276707Sdes 238276707Sdes SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size)); 239276707Sdes if ((r = sshbuf_check_sanity(buf)) != 0) 240276707Sdes return r; 241276707Sdes if (max_size == buf->max_size) 242276707Sdes return 0; 243276707Sdes if (buf->readonly || buf->refcount > 1) 244276707Sdes return SSH_ERR_BUFFER_READ_ONLY; 245276707Sdes if (max_size > SSHBUF_SIZE_MAX) 246276707Sdes return SSH_ERR_NO_BUFFER_SPACE; 247276707Sdes /* pack and realloc if necessary */ 248276707Sdes sshbuf_maybe_pack(buf, max_size < buf->size); 249276707Sdes if (max_size < buf->alloc && max_size > buf->size) { 250276707Sdes if (buf->size < SSHBUF_SIZE_INIT) 251276707Sdes rlen = SSHBUF_SIZE_INIT; 252276707Sdes else 253276707Sdes rlen = roundup(buf->size, SSHBUF_SIZE_INC); 254276707Sdes if (rlen > max_size) 255276707Sdes rlen = max_size; 256295367Sdes explicit_bzero(buf->d + buf->size, buf->alloc - buf->size); 257276707Sdes SSHBUF_DBG(("new alloc = %zu", rlen)); 258276707Sdes if ((dp = realloc(buf->d, rlen)) == NULL) 259276707Sdes return SSH_ERR_ALLOC_FAIL; 260276707Sdes buf->cd = buf->d = dp; 261276707Sdes buf->alloc = rlen; 262276707Sdes } 263276707Sdes SSHBUF_TELL("new-max"); 264276707Sdes if (max_size < buf->alloc) 265276707Sdes return SSH_ERR_NO_BUFFER_SPACE; 266276707Sdes buf->max_size = max_size; 267276707Sdes return 0; 268276707Sdes} 269276707Sdes 270276707Sdessize_t 271276707Sdessshbuf_len(const struct sshbuf *buf) 272276707Sdes{ 273276707Sdes if (sshbuf_check_sanity(buf) != 0) 274276707Sdes return 0; 275276707Sdes return buf->size - buf->off; 276276707Sdes} 277276707Sdes 278276707Sdessize_t 279276707Sdessshbuf_avail(const struct sshbuf *buf) 280276707Sdes{ 281276707Sdes if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1) 282276707Sdes return 0; 283276707Sdes return buf->max_size - (buf->size - buf->off); 284276707Sdes} 285276707Sdes 286276707Sdesconst u_char * 287276707Sdessshbuf_ptr(const struct sshbuf *buf) 288276707Sdes{ 289276707Sdes if (sshbuf_check_sanity(buf) != 0) 290276707Sdes return NULL; 291276707Sdes return buf->cd + buf->off; 292276707Sdes} 293276707Sdes 294276707Sdesu_char * 295276707Sdessshbuf_mutable_ptr(const struct sshbuf *buf) 296276707Sdes{ 297276707Sdes if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1) 298276707Sdes return NULL; 299276707Sdes return buf->d + buf->off; 300276707Sdes} 301276707Sdes 302276707Sdesint 303276707Sdessshbuf_check_reserve(const struct sshbuf *buf, size_t len) 304276707Sdes{ 305276707Sdes int r; 306276707Sdes 307276707Sdes if ((r = sshbuf_check_sanity(buf)) != 0) 308276707Sdes return r; 309276707Sdes if (buf->readonly || buf->refcount > 1) 310276707Sdes return SSH_ERR_BUFFER_READ_ONLY; 311276707Sdes SSHBUF_TELL("check"); 312276707Sdes /* Check that len is reasonable and that max_size + available < len */ 313276707Sdes if (len > buf->max_size || buf->max_size - len < buf->size - buf->off) 314276707Sdes return SSH_ERR_NO_BUFFER_SPACE; 315276707Sdes return 0; 316276707Sdes} 317276707Sdes 318276707Sdesint 319276707Sdessshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp) 320276707Sdes{ 321276707Sdes size_t rlen, need; 322276707Sdes u_char *dp; 323276707Sdes int r; 324276707Sdes 325276707Sdes if (dpp != NULL) 326276707Sdes *dpp = NULL; 327276707Sdes 328276707Sdes SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len)); 329276707Sdes if ((r = sshbuf_check_reserve(buf, len)) != 0) 330276707Sdes return r; 331276707Sdes /* 332276707Sdes * If the requested allocation appended would push us past max_size 333276707Sdes * then pack the buffer, zeroing buf->off. 334276707Sdes */ 335276707Sdes sshbuf_maybe_pack(buf, buf->size + len > buf->max_size); 336276707Sdes SSHBUF_TELL("reserve"); 337276707Sdes if (len + buf->size > buf->alloc) { 338276707Sdes /* 339276707Sdes * Prefer to alloc in SSHBUF_SIZE_INC units, but 340276707Sdes * allocate less if doing so would overflow max_size. 341276707Sdes */ 342276707Sdes need = len + buf->size - buf->alloc; 343276707Sdes rlen = roundup(buf->alloc + need, SSHBUF_SIZE_INC); 344276707Sdes SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen)); 345276707Sdes if (rlen > buf->max_size) 346276707Sdes rlen = buf->alloc + need; 347276707Sdes SSHBUF_DBG(("adjusted rlen %zu", rlen)); 348276707Sdes if ((dp = realloc(buf->d, rlen)) == NULL) { 349276707Sdes SSHBUF_DBG(("realloc fail")); 350276707Sdes if (dpp != NULL) 351276707Sdes *dpp = NULL; 352276707Sdes return SSH_ERR_ALLOC_FAIL; 353276707Sdes } 354276707Sdes buf->alloc = rlen; 355276707Sdes buf->cd = buf->d = dp; 356276707Sdes if ((r = sshbuf_check_reserve(buf, len)) < 0) { 357276707Sdes /* shouldn't fail */ 358276707Sdes if (dpp != NULL) 359276707Sdes *dpp = NULL; 360276707Sdes return r; 361276707Sdes } 362276707Sdes } 363276707Sdes dp = buf->d + buf->size; 364276707Sdes buf->size += len; 365276707Sdes SSHBUF_TELL("done"); 366276707Sdes if (dpp != NULL) 367276707Sdes *dpp = dp; 368276707Sdes return 0; 369276707Sdes} 370276707Sdes 371276707Sdesint 372276707Sdessshbuf_consume(struct sshbuf *buf, size_t len) 373276707Sdes{ 374276707Sdes int r; 375276707Sdes 376276707Sdes SSHBUF_DBG(("len = %zu", len)); 377276707Sdes if ((r = sshbuf_check_sanity(buf)) != 0) 378276707Sdes return r; 379276707Sdes if (len == 0) 380276707Sdes return 0; 381276707Sdes if (len > sshbuf_len(buf)) 382276707Sdes return SSH_ERR_MESSAGE_INCOMPLETE; 383276707Sdes buf->off += len; 384276707Sdes SSHBUF_TELL("done"); 385276707Sdes return 0; 386276707Sdes} 387276707Sdes 388276707Sdesint 389276707Sdessshbuf_consume_end(struct sshbuf *buf, size_t len) 390276707Sdes{ 391276707Sdes int r; 392276707Sdes 393276707Sdes SSHBUF_DBG(("len = %zu", len)); 394276707Sdes if ((r = sshbuf_check_sanity(buf)) != 0) 395276707Sdes return r; 396276707Sdes if (len == 0) 397276707Sdes return 0; 398276707Sdes if (len > sshbuf_len(buf)) 399276707Sdes return SSH_ERR_MESSAGE_INCOMPLETE; 400276707Sdes buf->size -= len; 401276707Sdes SSHBUF_TELL("done"); 402276707Sdes return 0; 403276707Sdes} 404276707Sdes 405