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