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