jcs's openbsd hax
openbsd
at jcs 445 lines 11 kB view raw
1/* $OpenBSD: sshbuf.c,v 1.24 2025/12/29 23:52:09 djm Exp $ */ 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" 27#include "misc.h" 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 40struct 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}; 51 52static inline int 53sshbuf_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->parent == buf || 59 buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX || 60 buf->cd == NULL || 61 buf->max_size > SSHBUF_SIZE_MAX || 62 buf->alloc > buf->max_size || 63 buf->size > buf->alloc || 64 buf->off > buf->size)) { 65 /* Do not try to recover from corrupted buffer internals */ 66 SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR")); 67 ssh_signal(SIGSEGV, SIG_DFL); 68 raise(SIGSEGV); 69 return SSH_ERR_INTERNAL_ERROR; 70 } 71 return 0; 72} 73 74static void 75sshbuf_maybe_pack(struct sshbuf *buf, int force) 76{ 77 SSHBUF_DBG(("force %d", force)); 78 SSHBUF_TELL("pre-pack"); 79 if (buf->off == 0 || buf->readonly || buf->refcount > 1) 80 return; 81 if (force || 82 (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) { 83 memmove(buf->d, buf->d + buf->off, buf->size - buf->off); 84 buf->size -= buf->off; 85 buf->off = 0; 86 SSHBUF_TELL("packed"); 87 } 88} 89 90struct sshbuf * 91sshbuf_new(void) 92{ 93 struct sshbuf *ret; 94 95 if ((ret = calloc(1, sizeof(*ret))) == NULL) 96 return NULL; 97 ret->alloc = SSHBUF_SIZE_INIT; 98 ret->max_size = SSHBUF_SIZE_MAX; 99 ret->readonly = 0; 100 ret->refcount = 1; 101 ret->parent = NULL; 102 if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) { 103 free(ret); 104 return NULL; 105 } 106 return ret; 107} 108 109struct sshbuf * 110sshbuf_from(const void *blob, size_t len) 111{ 112 struct sshbuf *ret; 113 114 if (blob == NULL || len > SSHBUF_SIZE_MAX || 115 (ret = calloc(1, sizeof(*ret))) == NULL) 116 return NULL; 117 ret->alloc = ret->size = ret->max_size = len; 118 ret->readonly = 1; 119 ret->refcount = 1; 120 ret->parent = NULL; 121 ret->cd = blob; 122 ret->d = NULL; 123 return ret; 124} 125 126int 127sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent) 128{ 129 int r; 130 131 if ((r = sshbuf_check_sanity(child)) != 0 || 132 (r = sshbuf_check_sanity(parent)) != 0) 133 return r; 134 if ((child->parent != NULL && child->parent != parent) || 135 child == parent) 136 return SSH_ERR_INTERNAL_ERROR; 137 child->parent = parent; 138 child->parent->refcount++; 139 return 0; 140} 141 142struct sshbuf * 143sshbuf_fromb(struct sshbuf *buf) 144{ 145 struct sshbuf *ret; 146 147 if (sshbuf_check_sanity(buf) != 0) 148 return NULL; 149 if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL) 150 return NULL; 151 if (sshbuf_set_parent(ret, buf) != 0) { 152 sshbuf_free(ret); 153 return NULL; 154 } 155 return ret; 156} 157 158void 159sshbuf_free(struct sshbuf *buf) 160{ 161 if (buf == NULL) 162 return; 163 /* 164 * The following will leak on insane buffers, but this is the safest 165 * course of action - an invalid pointer or already-freed pointer may 166 * have been passed to us and continuing to scribble over memory would 167 * be bad. 168 */ 169 if (sshbuf_check_sanity(buf) != 0) 170 return; 171 172 /* 173 * If we are a parent with still-extant children, then don't free just 174 * yet. The last child's call to sshbuf_free should decrement our 175 * refcount to 0 and trigger the actual free. 176 */ 177 buf->refcount--; 178 if (buf->refcount > 0) 179 return; 180 181 /* 182 * If we are a child, then free our parent to decrement its reference 183 * count and possibly free it. 184 */ 185 sshbuf_free(buf->parent); 186 buf->parent = NULL; 187 188 if (!buf->readonly) 189 freezero(buf->d, buf->alloc); 190 freezero(buf, sizeof(*buf)); 191} 192 193void 194sshbuf_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 } 203 if (sshbuf_check_sanity(buf) != 0) 204 return; 205 buf->off = buf->size = 0; 206 if (buf->alloc != SSHBUF_SIZE_INIT) { 207 if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT, 208 1)) != NULL) { 209 buf->cd = buf->d = d; 210 buf->alloc = SSHBUF_SIZE_INIT; 211 } 212 } 213 explicit_bzero(buf->d, buf->alloc); 214} 215 216size_t 217sshbuf_max_size(const struct sshbuf *buf) 218{ 219 return buf->max_size; 220} 221 222size_t 223sshbuf_alloc(const struct sshbuf *buf) 224{ 225 return buf->alloc; 226} 227 228const struct sshbuf * 229sshbuf_parent(const struct sshbuf *buf) 230{ 231 return buf->parent; 232} 233 234u_int 235sshbuf_refcount(const struct sshbuf *buf) 236{ 237 return buf->refcount; 238} 239 240int 241sshbuf_set_max_size(struct sshbuf *buf, size_t max_size) 242{ 243 size_t rlen; 244 u_char *dp; 245 int r; 246 247 SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size)); 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 262 rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC); 263 if (rlen > max_size) 264 rlen = max_size; 265 SSHBUF_DBG(("new alloc = %zu", rlen)); 266 if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) 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 278size_t 279sshbuf_len(const struct sshbuf *buf) 280{ 281 if (sshbuf_check_sanity(buf) != 0) 282 return 0; 283 return buf->size - buf->off; 284} 285 286size_t 287sshbuf_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 294const u_char * 295sshbuf_ptr(const struct sshbuf *buf) 296{ 297 if (sshbuf_check_sanity(buf) != 0) 298 return NULL; 299 return buf->cd + buf->off; 300} 301 302u_char * 303sshbuf_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 310int 311sshbuf_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 326int 327sshbuf_allocate(struct sshbuf *buf, size_t len) 328{ 329 size_t rlen, need; 330 u_char *dp; 331 int r; 332 333 SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len)); 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); 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); 351 SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen)); 352 if (rlen > buf->max_size) 353 rlen = buf->alloc + need; 354 SSHBUF_DBG(("adjusted rlen %zu", rlen)); 355 if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) { 356 SSHBUF_DBG(("realloc fail")); 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; 364 } 365 SSHBUF_TELL("done"); 366 return 0; 367} 368 369int 370sshbuf_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 378 SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len)); 379 if ((r = sshbuf_allocate(buf, len)) != 0) 380 return r; 381 382 dp = buf->d + buf->size; 383 buf->size += len; 384 if (dpp != NULL) 385 *dpp = dp; 386 return 0; 387} 388 389int 390sshbuf_consume(struct sshbuf *buf, size_t len) 391{ 392 int r; 393 394 SSHBUF_DBG(("len = %zu", len)); 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; 402 /* deal with empty buffer */ 403 if (buf->off == buf->size) 404 buf->off = buf->size = 0; 405 SSHBUF_TELL("done"); 406 return 0; 407} 408 409int 410sshbuf_consume_end(struct sshbuf *buf, size_t len) 411{ 412 int r; 413 414 SSHBUF_DBG(("len = %zu", len)); 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 426int 427sshbuf_consume_upto_child(struct sshbuf *buf, const struct sshbuf *child) 428{ 429 int r; 430 431 if ((r = sshbuf_check_sanity(buf)) != 0 || 432 (r = sshbuf_check_sanity(child)) != 0) 433 return r; 434 /* This function is only used for parent/child buffers */ 435 if (child->parent != buf) 436 return SSH_ERR_INVALID_ARGUMENT; 437 /* Nonsensical if the parent has advanced past the child */ 438 if (sshbuf_len(child) > sshbuf_len(buf)) 439 return SSH_ERR_INVALID_ARGUMENT; 440 /* More paranoia, shouldn't happen */ 441 if (child->cd < buf->cd) 442 return SSH_ERR_INTERNAL_ERROR; 443 /* Advance */ 444 return sshbuf_consume(buf, sshbuf_len(buf) - sshbuf_len(child)); 445}