1 /* $OpenBSD: sshbuf.c,v 1.18 2022/05/25 06:03:44 djm Exp $ */
3 * Copyright (c) 2011 Damien Miller
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.
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.
18 #define SSHBUF_INTERNAL
21 #include <sys/types.h>
32 sshbuf_check_sanity(const struct sshbuf
*buf
)
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
||
39 buf
->max_size
> SSHBUF_SIZE_MAX
||
40 buf
->alloc
> buf
->max_size
||
41 buf
->size
> buf
->alloc
||
42 buf
->off
> buf
->size
)) {
43 /* Do not try to recover from corrupted buffer internals */
44 SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
45 ssh_signal(SIGSEGV
, SIG_DFL
);
47 return SSH_ERR_INTERNAL_ERROR
;
53 sshbuf_maybe_pack(struct sshbuf
*buf
, int force
)
55 SSHBUF_DBG(("force %d", force
));
56 SSHBUF_TELL("pre-pack");
57 if (buf
->off
== 0 || buf
->readonly
|| buf
->refcount
> 1)
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
;
64 SSHBUF_TELL("packed");
73 if ((ret
= calloc(sizeof(*ret
), 1)) == NULL
)
75 ret
->alloc
= SSHBUF_SIZE_INIT
;
76 ret
->max_size
= SSHBUF_SIZE_MAX
;
80 if ((ret
->cd
= ret
->d
= calloc(1, ret
->alloc
)) == NULL
) {
88 sshbuf_from(const void *blob
, size_t len
)
92 if (blob
== NULL
|| len
> SSHBUF_SIZE_MAX
||
93 (ret
= calloc(sizeof(*ret
), 1)) == NULL
)
95 ret
->alloc
= ret
->size
= ret
->max_size
= len
;
105 sshbuf_set_parent(struct sshbuf
*child
, struct sshbuf
*parent
)
109 if ((r
= sshbuf_check_sanity(child
)) != 0 ||
110 (r
= sshbuf_check_sanity(parent
)) != 0)
112 if (child
->parent
!= NULL
&& child
->parent
!= parent
)
113 return SSH_ERR_INTERNAL_ERROR
;
114 child
->parent
= parent
;
115 child
->parent
->refcount
++;
120 sshbuf_fromb(struct sshbuf
*buf
)
124 if (sshbuf_check_sanity(buf
) != 0)
126 if ((ret
= sshbuf_from(sshbuf_ptr(buf
), sshbuf_len(buf
))) == NULL
)
128 if (sshbuf_set_parent(ret
, buf
) != 0) {
136 sshbuf_free(struct sshbuf
*buf
)
141 * The following will leak on insane buffers, but this is the safest
142 * course of action - an invalid pointer or already-freed pointer may
143 * have been passed to us and continuing to scribble over memory would
146 if (sshbuf_check_sanity(buf
) != 0)
150 * If we are a parent with still-extant children, then don't free just
151 * yet. The last child's call to sshbuf_free should decrement our
152 * refcount to 0 and trigger the actual free.
155 if (buf
->refcount
> 0)
159 * If we are a child, the free our parent to decrement its reference
160 * count and possibly free it.
162 sshbuf_free(buf
->parent
);
165 if (!buf
->readonly
) {
166 explicit_bzero(buf
->d
, buf
->alloc
);
169 freezero(buf
, sizeof(*buf
));
173 sshbuf_reset(struct sshbuf
*buf
)
177 if (buf
->readonly
|| buf
->refcount
> 1) {
178 /* Nonsensical. Just make buffer appear empty */
179 buf
->off
= buf
->size
;
182 if (sshbuf_check_sanity(buf
) != 0)
184 buf
->off
= buf
->size
= 0;
185 if (buf
->alloc
!= SSHBUF_SIZE_INIT
) {
186 if ((d
= recallocarray(buf
->d
, buf
->alloc
, SSHBUF_SIZE_INIT
,
188 buf
->cd
= buf
->d
= d
;
189 buf
->alloc
= SSHBUF_SIZE_INIT
;
192 explicit_bzero(buf
->d
, buf
->alloc
);
196 sshbuf_max_size(const struct sshbuf
*buf
)
198 return buf
->max_size
;
202 sshbuf_alloc(const struct sshbuf
*buf
)
207 const struct sshbuf
*
208 sshbuf_parent(const struct sshbuf
*buf
)
214 sshbuf_refcount(const struct sshbuf
*buf
)
216 return buf
->refcount
;
220 sshbuf_set_max_size(struct sshbuf
*buf
, size_t max_size
)
226 SSHBUF_DBG(("set max buf = %p len = %zu", buf
, max_size
));
227 if ((r
= sshbuf_check_sanity(buf
)) != 0)
229 if (max_size
== buf
->max_size
)
231 if (buf
->readonly
|| buf
->refcount
> 1)
232 return SSH_ERR_BUFFER_READ_ONLY
;
233 if (max_size
> SSHBUF_SIZE_MAX
)
234 return SSH_ERR_NO_BUFFER_SPACE
;
235 /* pack and realloc if necessary */
236 sshbuf_maybe_pack(buf
, max_size
< buf
->size
);
237 if (max_size
< buf
->alloc
&& max_size
> buf
->size
) {
238 if (buf
->size
< SSHBUF_SIZE_INIT
)
239 rlen
= SSHBUF_SIZE_INIT
;
241 rlen
= ROUNDUP(buf
->size
, SSHBUF_SIZE_INC
);
244 SSHBUF_DBG(("new alloc = %zu", rlen
));
245 if ((dp
= recallocarray(buf
->d
, buf
->alloc
, rlen
, 1)) == NULL
)
246 return SSH_ERR_ALLOC_FAIL
;
247 buf
->cd
= buf
->d
= dp
;
250 SSHBUF_TELL("new-max");
251 if (max_size
< buf
->alloc
)
252 return SSH_ERR_NO_BUFFER_SPACE
;
253 buf
->max_size
= max_size
;
258 sshbuf_len(const struct sshbuf
*buf
)
260 if (sshbuf_check_sanity(buf
) != 0)
262 return buf
->size
- buf
->off
;
266 sshbuf_avail(const struct sshbuf
*buf
)
268 if (sshbuf_check_sanity(buf
) != 0 || buf
->readonly
|| buf
->refcount
> 1)
270 return buf
->max_size
- (buf
->size
- buf
->off
);
274 sshbuf_ptr(const struct sshbuf
*buf
)
276 if (sshbuf_check_sanity(buf
) != 0)
278 return buf
->cd
+ buf
->off
;
282 sshbuf_mutable_ptr(const struct sshbuf
*buf
)
284 if (sshbuf_check_sanity(buf
) != 0 || buf
->readonly
|| buf
->refcount
> 1)
286 return buf
->d
+ buf
->off
;
290 sshbuf_check_reserve(const struct sshbuf
*buf
, size_t len
)
294 if ((r
= sshbuf_check_sanity(buf
)) != 0)
296 if (buf
->readonly
|| buf
->refcount
> 1)
297 return SSH_ERR_BUFFER_READ_ONLY
;
298 SSHBUF_TELL("check");
299 /* Check that len is reasonable and that max_size + available < len */
300 if (len
> buf
->max_size
|| buf
->max_size
- len
< buf
->size
- buf
->off
)
301 return SSH_ERR_NO_BUFFER_SPACE
;
306 sshbuf_allocate(struct sshbuf
*buf
, size_t len
)
312 SSHBUF_DBG(("allocate buf = %p len = %zu", buf
, len
));
313 if ((r
= sshbuf_check_reserve(buf
, len
)) != 0)
316 * If the requested allocation appended would push us past max_size
317 * then pack the buffer, zeroing buf->off.
319 sshbuf_maybe_pack(buf
, buf
->size
+ len
> buf
->max_size
);
320 SSHBUF_TELL("allocate");
321 if (len
+ buf
->size
<= buf
->alloc
)
322 return 0; /* already have it. */
325 * Prefer to alloc in SSHBUF_SIZE_INC units, but
326 * allocate less if doing so would overflow max_size.
328 need
= len
+ buf
->size
- buf
->alloc
;
329 rlen
= ROUNDUP(buf
->alloc
+ need
, SSHBUF_SIZE_INC
);
330 SSHBUF_DBG(("need %zu initial rlen %zu", need
, rlen
));
331 if (rlen
> buf
->max_size
)
332 rlen
= buf
->alloc
+ need
;
333 SSHBUF_DBG(("adjusted rlen %zu", rlen
));
334 if ((dp
= recallocarray(buf
->d
, buf
->alloc
, rlen
, 1)) == NULL
) {
335 SSHBUF_DBG(("realloc fail"));
336 return SSH_ERR_ALLOC_FAIL
;
339 buf
->cd
= buf
->d
= dp
;
340 if ((r
= sshbuf_check_reserve(buf
, len
)) < 0) {
349 sshbuf_reserve(struct sshbuf
*buf
, size_t len
, u_char
**dpp
)
357 SSHBUF_DBG(("reserve buf = %p len = %zu", buf
, len
));
358 if ((r
= sshbuf_allocate(buf
, len
)) != 0)
361 dp
= buf
->d
+ buf
->size
;
369 sshbuf_consume(struct sshbuf
*buf
, size_t len
)
373 SSHBUF_DBG(("len = %zu", len
));
374 if ((r
= sshbuf_check_sanity(buf
)) != 0)
378 if (len
> sshbuf_len(buf
))
379 return SSH_ERR_MESSAGE_INCOMPLETE
;
381 /* deal with empty buffer */
382 if (buf
->off
== buf
->size
)
383 buf
->off
= buf
->size
= 0;
389 sshbuf_consume_end(struct sshbuf
*buf
, size_t len
)
393 SSHBUF_DBG(("len = %zu", len
));
394 if ((r
= sshbuf_check_sanity(buf
)) != 0)
398 if (len
> sshbuf_len(buf
))
399 return SSH_ERR_MESSAGE_INCOMPLETE
;