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