Annotation of src/usr.bin/ssh/sshbuf.c, Revision 1.3
1.3 ! deraadt 1: /* $OpenBSD: sshbuf.c,v 1.2 2014/06/25 14:16:09 deraadt 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:
1.3 ! deraadt 18: #include <sys/param.h> /* roundup */
1.1 djm 19: #include <sys/types.h>
20: #include <signal.h>
21: #include <stdlib.h>
22: #include <stdio.h>
23: #include <string.h>
24:
25: #include "ssherr.h"
26: #define SSHBUF_INTERNAL
27: #include "sshbuf.h"
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: {
135: bzero(ret, sizeof(*ret));
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: */
164: if (buf->parent != NULL) {
165: sshbuf_free(buf->parent);
166: buf->parent = NULL;
167: }
168: /*
169: * If we are a parent with still-extant children, then don't free just
170: * yet. The last child's call to sshbuf_free should decrement our
171: * refcount to 0 and trigger the actual free.
172: */
173: buf->refcount--;
174: if (buf->refcount > 0)
175: return;
176: dont_free = buf->dont_free;
177: if (!buf->readonly) {
178: bzero(buf->d, buf->alloc);
179: free(buf->d);
180: }
181: bzero(buf, sizeof(*buf));
182: if (!dont_free)
183: free(buf);
184: }
185:
186: void
187: sshbuf_reset(struct sshbuf *buf)
188: {
189: u_char *d;
190:
191: if (buf->readonly || buf->refcount > 1) {
192: /* Nonsensical. Just make buffer appear empty */
193: buf->off = buf->size;
194: return;
195: }
196: if (sshbuf_check_sanity(buf) == 0)
197: bzero(buf->d, buf->alloc);
198: buf->off = buf->size = 0;
199: if (buf->alloc != SSHBUF_SIZE_INIT) {
200: if ((d = realloc(buf->d, SSHBUF_SIZE_INIT)) != NULL) {
201: buf->cd = buf->d = d;
202: buf->alloc = SSHBUF_SIZE_INIT;
203: }
204: }
205: }
206:
207: size_t
208: sshbuf_max_size(const struct sshbuf *buf)
209: {
210: return buf->max_size;
211: }
212:
213: size_t
214: sshbuf_alloc(const struct sshbuf *buf)
215: {
216: return buf->alloc;
217: }
218:
219: const struct sshbuf *
220: sshbuf_parent(const struct sshbuf *buf)
221: {
222: return buf->parent;
223: }
224:
225: u_int
226: sshbuf_refcount(const struct sshbuf *buf)
227: {
228: return buf->refcount;
229: }
230:
231: int
232: sshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
233: {
234: size_t rlen;
235: u_char *dp;
236: int r;
237:
238: SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
239: if ((r = sshbuf_check_sanity(buf)) != 0)
240: return r;
241: if (max_size == buf->max_size)
242: return 0;
243: if (buf->readonly || buf->refcount > 1)
244: return SSH_ERR_BUFFER_READ_ONLY;
245: if (max_size > SSHBUF_SIZE_MAX)
246: return SSH_ERR_NO_BUFFER_SPACE;
247: /* pack and realloc if necessary */
248: sshbuf_maybe_pack(buf, max_size < buf->size);
249: if (max_size < buf->alloc && max_size > buf->size) {
250: if (buf->size < SSHBUF_SIZE_INIT)
251: rlen = SSHBUF_SIZE_INIT;
252: else
253: rlen = roundup(buf->size, SSHBUF_SIZE_INC);
254: if (rlen > max_size)
255: rlen = max_size;
256: bzero(buf->d + buf->size, buf->alloc - buf->size);
257: SSHBUF_DBG(("new alloc = %zu", rlen));
258: if ((dp = realloc(buf->d, rlen)) == NULL)
259: return SSH_ERR_ALLOC_FAIL;
260: buf->cd = buf->d = dp;
261: buf->alloc = rlen;
262: }
263: SSHBUF_TELL("new-max");
264: if (max_size < buf->alloc)
265: return SSH_ERR_NO_BUFFER_SPACE;
266: buf->max_size = max_size;
267: return 0;
268: }
269:
270: size_t
271: sshbuf_len(const struct sshbuf *buf)
272: {
273: if (sshbuf_check_sanity(buf) != 0)
274: return 0;
275: return buf->size - buf->off;
276: }
277:
278: size_t
279: sshbuf_avail(const struct sshbuf *buf)
280: {
281: if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
282: return 0;
283: return buf->max_size - (buf->size - buf->off);
284: }
285:
286: const u_char *
287: sshbuf_ptr(const struct sshbuf *buf)
288: {
289: if (sshbuf_check_sanity(buf) != 0)
290: return NULL;
291: return buf->cd + buf->off;
292: }
293:
294: u_char *
295: sshbuf_mutable_ptr(const struct sshbuf *buf)
296: {
297: if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
298: return NULL;
299: return buf->d + buf->off;
300: }
301:
302: int
303: sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
304: {
305: int r;
306:
307: if ((r = sshbuf_check_sanity(buf)) != 0)
308: return r;
309: if (buf->readonly || buf->refcount > 1)
310: return SSH_ERR_BUFFER_READ_ONLY;
311: SSHBUF_TELL("check");
312: /* Check that len is reasonable and that max_size + available < len */
313: if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
314: return SSH_ERR_NO_BUFFER_SPACE;
315: return 0;
316: }
317:
318: int
319: sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
320: {
321: size_t rlen, need;
322: u_char *dp;
323: int r;
324:
325: if (dpp != NULL)
326: *dpp = NULL;
327:
328: SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
329: if ((r = sshbuf_check_reserve(buf, len)) != 0)
330: return r;
331: /*
332: * If the requested allocation appended would push us past max_size
333: * then pack the buffer, zeroing buf->off.
334: */
335: sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
336: SSHBUF_TELL("reserve");
337: if (len + buf->size > buf->alloc) {
338: /*
339: * Prefer to alloc in SSHBUF_SIZE_INC units, but
340: * allocate less if doing so would overflow max_size.
341: */
342: need = len + buf->size - buf->alloc;
343: rlen = roundup(buf->alloc + need, SSHBUF_SIZE_INC);
344: SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
345: if (rlen > buf->max_size)
346: rlen = buf->alloc + need;
347: SSHBUF_DBG(("adjusted rlen %zu", rlen));
348: if ((dp = realloc(buf->d, rlen)) == NULL) {
349: SSHBUF_DBG(("realloc fail"));
350: if (dpp != NULL)
351: *dpp = NULL;
352: return SSH_ERR_ALLOC_FAIL;
353: }
354: buf->alloc = rlen;
355: buf->cd = buf->d = dp;
356: if ((r = sshbuf_check_reserve(buf, len)) < 0) {
357: /* shouldn't fail */
358: if (dpp != NULL)
359: *dpp = NULL;
360: return r;
361: }
362: }
363: dp = buf->d + buf->size;
364: buf->size += len;
365: SSHBUF_TELL("done");
366: if (dpp != NULL)
367: *dpp = dp;
368: return 0;
369: }
370:
371: int
372: sshbuf_consume(struct sshbuf *buf, size_t len)
373: {
374: int r;
375:
376: SSHBUF_DBG(("len = %zu", len));
377: if ((r = sshbuf_check_sanity(buf)) != 0)
378: return r;
379: if (len == 0)
380: return 0;
381: if (len > sshbuf_len(buf))
382: return SSH_ERR_MESSAGE_INCOMPLETE;
383: buf->off += len;
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: