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