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: