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