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