[BACK]Return to sshbuf.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ssh

Annotation of src/usr.bin/ssh/sshbuf.c, Revision 1.19

1.19    ! djm         1: /*     $OpenBSD: sshbuf.c,v 1.18 2022/05/25 06:03:44 djm Exp $ */
1.1       djm         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 <signal.h>
                     20: #include <stdlib.h>
                     21: #include <stdio.h>
                     22: #include <string.h>
                     23:
                     24: #include "ssherr.h"
                     25: #define SSHBUF_INTERNAL
                     26: #include "sshbuf.h"
1.7       deraadt    27: #include "misc.h"
1.19    ! djm        28:
        !            29: #ifdef SSHBUF_DEBUG
        !            30: # define SSHBUF_TELL(what) do { \
        !            31:                printf("%s:%d %s: %s size %zu alloc %zu off %zu max %zu\n", \
        !            32:                    __FILE__, __LINE__, __func__, what, \
        !            33:                    buf->size, buf->alloc, buf->off, buf->max_size); \
        !            34:                fflush(stdout); \
        !            35:        } while (0)
        !            36: #else
        !            37: # define SSHBUF_TELL(what)
        !            38: #endif
        !            39:
        !            40: struct sshbuf {
        !            41:        u_char *d;              /* Data */
        !            42:        const u_char *cd;       /* Const data */
        !            43:        size_t off;             /* First available byte is buf->d + buf->off */
        !            44:        size_t size;            /* Last byte is buf->d + buf->size - 1 */
        !            45:        size_t max_size;        /* Maximum size of buffer */
        !            46:        size_t alloc;           /* Total bytes allocated to buf->d */
        !            47:        int readonly;           /* Refers to external, const data */
        !            48:        u_int refcount;         /* Tracks self and number of child buffers */
        !            49:        struct sshbuf *parent;  /* If child, pointer to parent */
        !            50: };
1.1       djm        51:
                     52: static inline int
                     53: sshbuf_check_sanity(const struct sshbuf *buf)
                     54: {
                     55:        SSHBUF_TELL("sanity");
                     56:        if (__predict_false(buf == NULL ||
                     57:            (!buf->readonly && buf->d != buf->cd) ||
                     58:            buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
                     59:            buf->cd == NULL ||
                     60:            buf->max_size > SSHBUF_SIZE_MAX ||
                     61:            buf->alloc > buf->max_size ||
                     62:            buf->size > buf->alloc ||
                     63:            buf->off > buf->size)) {
                     64:                /* Do not try to recover from corrupted buffer internals */
1.18      djm        65:                SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
1.14      dtucker    66:                ssh_signal(SIGSEGV, SIG_DFL);
1.1       djm        67:                raise(SIGSEGV);
                     68:                return SSH_ERR_INTERNAL_ERROR;
                     69:        }
                     70:        return 0;
                     71: }
                     72:
                     73: static void
                     74: sshbuf_maybe_pack(struct sshbuf *buf, int force)
                     75: {
1.18      djm        76:        SSHBUF_DBG(("force %d", force));
1.1       djm        77:        SSHBUF_TELL("pre-pack");
                     78:        if (buf->off == 0 || buf->readonly || buf->refcount > 1)
                     79:                return;
                     80:        if (force ||
                     81:            (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
                     82:                memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
                     83:                buf->size -= buf->off;
                     84:                buf->off = 0;
                     85:                SSHBUF_TELL("packed");
                     86:        }
                     87: }
                     88:
                     89: struct sshbuf *
                     90: sshbuf_new(void)
                     91: {
                     92:        struct sshbuf *ret;
                     93:
                     94:        if ((ret = calloc(sizeof(*ret), 1)) == NULL)
                     95:                return NULL;
                     96:        ret->alloc = SSHBUF_SIZE_INIT;
                     97:        ret->max_size = SSHBUF_SIZE_MAX;
                     98:        ret->readonly = 0;
                     99:        ret->refcount = 1;
                    100:        ret->parent = NULL;
                    101:        if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
                    102:                free(ret);
                    103:                return NULL;
                    104:        }
                    105:        return ret;
                    106: }
                    107:
                    108: struct sshbuf *
                    109: sshbuf_from(const void *blob, size_t len)
                    110: {
                    111:        struct sshbuf *ret;
                    112:
                    113:        if (blob == NULL || len > SSHBUF_SIZE_MAX ||
                    114:            (ret = calloc(sizeof(*ret), 1)) == NULL)
                    115:                return NULL;
                    116:        ret->alloc = ret->size = ret->max_size = len;
                    117:        ret->readonly = 1;
                    118:        ret->refcount = 1;
                    119:        ret->parent = NULL;
                    120:        ret->cd = blob;
                    121:        ret->d = NULL;
                    122:        return ret;
                    123: }
                    124:
                    125: int
                    126: sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
                    127: {
                    128:        int r;
                    129:
                    130:        if ((r = sshbuf_check_sanity(child)) != 0 ||
                    131:            (r = sshbuf_check_sanity(parent)) != 0)
                    132:                return r;
1.16      djm       133:        if (child->parent != NULL && child->parent != parent)
                    134:                return SSH_ERR_INTERNAL_ERROR;
1.1       djm       135:        child->parent = parent;
                    136:        child->parent->refcount++;
                    137:        return 0;
                    138: }
                    139:
                    140: struct sshbuf *
                    141: sshbuf_fromb(struct sshbuf *buf)
                    142: {
                    143:        struct sshbuf *ret;
                    144:
                    145:        if (sshbuf_check_sanity(buf) != 0)
                    146:                return NULL;
                    147:        if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
                    148:                return NULL;
                    149:        if (sshbuf_set_parent(ret, buf) != 0) {
                    150:                sshbuf_free(ret);
                    151:                return NULL;
                    152:        }
                    153:        return ret;
                    154: }
                    155:
                    156: void
                    157: sshbuf_free(struct sshbuf *buf)
                    158: {
                    159:        if (buf == NULL)
                    160:                return;
                    161:        /*
                    162:         * The following will leak on insane buffers, but this is the safest
                    163:         * course of action - an invalid pointer or already-freed pointer may
                    164:         * have been passed to us and continuing to scribble over memory would
                    165:         * be bad.
                    166:         */
                    167:        if (sshbuf_check_sanity(buf) != 0)
                    168:                return;
1.13      djm       169:
1.1       djm       170:        /*
                    171:         * If we are a parent with still-extant children, then don't free just
                    172:         * yet. The last child's call to sshbuf_free should decrement our
                    173:         * refcount to 0 and trigger the actual free.
                    174:         */
                    175:        buf->refcount--;
                    176:        if (buf->refcount > 0)
                    177:                return;
1.13      djm       178:
                    179:        /*
                    180:         * If we are a child, the free our parent to decrement its reference
                    181:         * count and possibly free it.
                    182:         */
                    183:        sshbuf_free(buf->parent);
                    184:        buf->parent = NULL;
                    185:
1.1       djm       186:        if (!buf->readonly) {
1.4       djm       187:                explicit_bzero(buf->d, buf->alloc);
1.1       djm       188:                free(buf->d);
                    189:        }
1.15      jsg       190:        freezero(buf, sizeof(*buf));
1.1       djm       191: }
                    192:
                    193: void
                    194: sshbuf_reset(struct sshbuf *buf)
                    195: {
                    196:        u_char *d;
                    197:
                    198:        if (buf->readonly || buf->refcount > 1) {
                    199:                /* Nonsensical. Just make buffer appear empty */
                    200:                buf->off = buf->size;
                    201:                return;
                    202:        }
1.16      djm       203:        if (sshbuf_check_sanity(buf) != 0)
                    204:                return;
1.1       djm       205:        buf->off = buf->size = 0;
                    206:        if (buf->alloc != SSHBUF_SIZE_INIT) {
1.10      deraadt   207:                if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
                    208:                    1)) != NULL) {
1.1       djm       209:                        buf->cd = buf->d = d;
                    210:                        buf->alloc = SSHBUF_SIZE_INIT;
                    211:                }
1.11      djm       212:        }
1.16      djm       213:        explicit_bzero(buf->d, buf->alloc);
1.1       djm       214: }
                    215:
                    216: size_t
                    217: sshbuf_max_size(const struct sshbuf *buf)
                    218: {
                    219:        return buf->max_size;
                    220: }
                    221:
                    222: size_t
                    223: sshbuf_alloc(const struct sshbuf *buf)
                    224: {
                    225:        return buf->alloc;
                    226: }
                    227:
                    228: const struct sshbuf *
                    229: sshbuf_parent(const struct sshbuf *buf)
                    230: {
                    231:        return buf->parent;
                    232: }
                    233:
                    234: u_int
                    235: sshbuf_refcount(const struct sshbuf *buf)
                    236: {
                    237:        return buf->refcount;
                    238: }
                    239:
                    240: int
                    241: sshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
                    242: {
                    243:        size_t rlen;
                    244:        u_char *dp;
                    245:        int r;
                    246:
1.18      djm       247:        SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
1.1       djm       248:        if ((r = sshbuf_check_sanity(buf)) != 0)
                    249:                return r;
                    250:        if (max_size == buf->max_size)
                    251:                return 0;
                    252:        if (buf->readonly || buf->refcount > 1)
                    253:                return SSH_ERR_BUFFER_READ_ONLY;
                    254:        if (max_size > SSHBUF_SIZE_MAX)
                    255:                return SSH_ERR_NO_BUFFER_SPACE;
                    256:        /* pack and realloc if necessary */
                    257:        sshbuf_maybe_pack(buf, max_size < buf->size);
                    258:        if (max_size < buf->alloc && max_size > buf->size) {
                    259:                if (buf->size < SSHBUF_SIZE_INIT)
                    260:                        rlen = SSHBUF_SIZE_INIT;
                    261:                else
1.7       deraadt   262:                        rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC);
1.1       djm       263:                if (rlen > max_size)
                    264:                        rlen = max_size;
1.18      djm       265:                SSHBUF_DBG(("new alloc = %zu", rlen));
1.10      deraadt   266:                if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL)
1.1       djm       267:                        return SSH_ERR_ALLOC_FAIL;
                    268:                buf->cd = buf->d = dp;
                    269:                buf->alloc = rlen;
                    270:        }
                    271:        SSHBUF_TELL("new-max");
                    272:        if (max_size < buf->alloc)
                    273:                return SSH_ERR_NO_BUFFER_SPACE;
                    274:        buf->max_size = max_size;
                    275:        return 0;
                    276: }
                    277:
                    278: size_t
                    279: sshbuf_len(const struct sshbuf *buf)
                    280: {
                    281:        if (sshbuf_check_sanity(buf) != 0)
                    282:                return 0;
                    283:        return buf->size - buf->off;
                    284: }
                    285:
                    286: size_t
                    287: sshbuf_avail(const struct sshbuf *buf)
                    288: {
                    289:        if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
                    290:                return 0;
                    291:        return buf->max_size - (buf->size - buf->off);
                    292: }
                    293:
                    294: const u_char *
                    295: sshbuf_ptr(const struct sshbuf *buf)
                    296: {
                    297:        if (sshbuf_check_sanity(buf) != 0)
                    298:                return NULL;
                    299:        return buf->cd + buf->off;
                    300: }
                    301:
                    302: u_char *
                    303: sshbuf_mutable_ptr(const struct sshbuf *buf)
                    304: {
                    305:        if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
                    306:                return NULL;
                    307:        return buf->d + buf->off;
                    308: }
                    309:
                    310: int
                    311: sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
                    312: {
                    313:        int r;
                    314:
                    315:        if ((r = sshbuf_check_sanity(buf)) != 0)
                    316:                return r;
                    317:        if (buf->readonly || buf->refcount > 1)
                    318:                return SSH_ERR_BUFFER_READ_ONLY;
                    319:        SSHBUF_TELL("check");
                    320:        /* Check that len is reasonable and that max_size + available < len */
                    321:        if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
                    322:                return SSH_ERR_NO_BUFFER_SPACE;
                    323:        return 0;
                    324: }
                    325:
                    326: int
1.8       djm       327: sshbuf_allocate(struct sshbuf *buf, size_t len)
1.1       djm       328: {
                    329:        size_t rlen, need;
                    330:        u_char *dp;
                    331:        int r;
                    332:
1.18      djm       333:        SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
1.1       djm       334:        if ((r = sshbuf_check_reserve(buf, len)) != 0)
                    335:                return r;
                    336:        /*
                    337:         * If the requested allocation appended would push us past max_size
                    338:         * then pack the buffer, zeroing buf->off.
                    339:         */
                    340:        sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
1.8       djm       341:        SSHBUF_TELL("allocate");
                    342:        if (len + buf->size <= buf->alloc)
                    343:                return 0; /* already have it. */
                    344:
                    345:        /*
                    346:         * Prefer to alloc in SSHBUF_SIZE_INC units, but
                    347:         * allocate less if doing so would overflow max_size.
                    348:         */
                    349:        need = len + buf->size - buf->alloc;
                    350:        rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
1.18      djm       351:        SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
1.8       djm       352:        if (rlen > buf->max_size)
                    353:                rlen = buf->alloc + need;
1.18      djm       354:        SSHBUF_DBG(("adjusted rlen %zu", rlen));
1.10      deraadt   355:        if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) {
1.18      djm       356:                SSHBUF_DBG(("realloc fail"));
1.8       djm       357:                return SSH_ERR_ALLOC_FAIL;
                    358:        }
                    359:        buf->alloc = rlen;
                    360:        buf->cd = buf->d = dp;
                    361:        if ((r = sshbuf_check_reserve(buf, len)) < 0) {
                    362:                /* shouldn't fail */
                    363:                return r;
1.1       djm       364:        }
1.8       djm       365:        SSHBUF_TELL("done");
                    366:        return 0;
                    367: }
                    368:
                    369: int
                    370: sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
                    371: {
                    372:        u_char *dp;
                    373:        int r;
                    374:
                    375:        if (dpp != NULL)
                    376:                *dpp = NULL;
                    377:
1.18      djm       378:        SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
1.8       djm       379:        if ((r = sshbuf_allocate(buf, len)) != 0)
                    380:                return r;
                    381:
1.1       djm       382:        dp = buf->d + buf->size;
                    383:        buf->size += len;
                    384:        if (dpp != NULL)
                    385:                *dpp = dp;
                    386:        return 0;
                    387: }
                    388:
                    389: int
                    390: sshbuf_consume(struct sshbuf *buf, size_t len)
                    391: {
                    392:        int r;
                    393:
1.18      djm       394:        SSHBUF_DBG(("len = %zu", len));
1.1       djm       395:        if ((r = sshbuf_check_sanity(buf)) != 0)
                    396:                return r;
                    397:        if (len == 0)
                    398:                return 0;
                    399:        if (len > sshbuf_len(buf))
                    400:                return SSH_ERR_MESSAGE_INCOMPLETE;
                    401:        buf->off += len;
1.9       markus    402:        /* deal with empty buffer */
                    403:        if (buf->off == buf->size)
                    404:                buf->off = buf->size = 0;
1.1       djm       405:        SSHBUF_TELL("done");
                    406:        return 0;
                    407: }
                    408:
                    409: int
                    410: sshbuf_consume_end(struct sshbuf *buf, size_t len)
                    411: {
                    412:        int r;
                    413:
1.18      djm       414:        SSHBUF_DBG(("len = %zu", len));
1.1       djm       415:        if ((r = sshbuf_check_sanity(buf)) != 0)
                    416:                return r;
                    417:        if (len == 0)
                    418:                return 0;
                    419:        if (len > sshbuf_len(buf))
                    420:                return SSH_ERR_MESSAGE_INCOMPLETE;
                    421:        buf->size -= len;
                    422:        SSHBUF_TELL("done");
                    423:        return 0;
                    424: }
                    425: