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