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