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