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