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