1 /* $OpenBSD: sshbuf.c,v 1.19 2022/12/02 04:40:27 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.
20 #include <sys/types.h>
27 #define SSHBUF_INTERNAL
32 # define SSHBUF_TELL(what) do { \
33 printf("%s:%d %s: %s size %zu alloc %zu off %zu max %zu\n", \
34 __FILE__, __LINE__, __func__, what, \
35 buf->size, buf->alloc, buf->off, buf->max_size); \
39 # define SSHBUF_TELL(what)
44 const u_char
*cd
; /* Const data */
45 size_t off
; /* First available byte is buf->d + buf->off */
46 size_t size
; /* Last byte is buf->d + buf->size - 1 */
47 size_t max_size
; /* Maximum size of buffer */
48 size_t alloc
; /* Total bytes allocated to buf->d */
49 int readonly
; /* Refers to external, const data */
50 u_int refcount
; /* Tracks self and number of child buffers */
51 struct sshbuf
*parent
; /* If child, pointer to parent */
55 sshbuf_check_sanity(const struct sshbuf
*buf
)
57 SSHBUF_TELL("sanity");
58 if (__predict_false(buf
== NULL
||
59 (!buf
->readonly
&& buf
->d
!= buf
->cd
) ||
60 buf
->refcount
< 1 || buf
->refcount
> SSHBUF_REFS_MAX
||
62 buf
->max_size
> SSHBUF_SIZE_MAX
||
63 buf
->alloc
> buf
->max_size
||
64 buf
->size
> buf
->alloc
||
65 buf
->off
> buf
->size
)) {
66 /* Do not try to recover from corrupted buffer internals */
67 SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
68 ssh_signal(SIGSEGV
, SIG_DFL
);
70 return SSH_ERR_INTERNAL_ERROR
;
76 sshbuf_maybe_pack(struct sshbuf
*buf
, int force
)
78 SSHBUF_DBG(("force %d", force
));
79 SSHBUF_TELL("pre-pack");
80 if (buf
->off
== 0 || buf
->readonly
|| buf
->refcount
> 1)
83 (buf
->off
>= SSHBUF_PACK_MIN
&& buf
->off
>= buf
->size
/ 2)) {
84 memmove(buf
->d
, buf
->d
+ buf
->off
, buf
->size
- buf
->off
);
85 buf
->size
-= buf
->off
;
87 SSHBUF_TELL("packed");
96 if ((ret
= calloc(sizeof(*ret
), 1)) == NULL
)
98 ret
->alloc
= SSHBUF_SIZE_INIT
;
99 ret
->max_size
= SSHBUF_SIZE_MAX
;
103 if ((ret
->cd
= ret
->d
= calloc(1, ret
->alloc
)) == NULL
) {
111 sshbuf_from(const void *blob
, size_t len
)
115 if (blob
== NULL
|| len
> SSHBUF_SIZE_MAX
||
116 (ret
= calloc(sizeof(*ret
), 1)) == NULL
)
118 ret
->alloc
= ret
->size
= ret
->max_size
= len
;
128 sshbuf_set_parent(struct sshbuf
*child
, struct sshbuf
*parent
)
132 if ((r
= sshbuf_check_sanity(child
)) != 0 ||
133 (r
= sshbuf_check_sanity(parent
)) != 0)
135 if (child
->parent
!= NULL
&& child
->parent
!= parent
)
136 return SSH_ERR_INTERNAL_ERROR
;
137 child
->parent
= parent
;
138 child
->parent
->refcount
++;
143 sshbuf_fromb(struct sshbuf
*buf
)
147 if (sshbuf_check_sanity(buf
) != 0)
149 if ((ret
= sshbuf_from(sshbuf_ptr(buf
), sshbuf_len(buf
))) == NULL
)
151 if (sshbuf_set_parent(ret
, buf
) != 0) {
159 sshbuf_free(struct sshbuf
*buf
)
164 * The following will leak on insane buffers, but this is the safest
165 * course of action - an invalid pointer or already-freed pointer may
166 * have been passed to us and continuing to scribble over memory would
169 if (sshbuf_check_sanity(buf
) != 0)
173 * If we are a parent with still-extant children, then don't free just
174 * yet. The last child's call to sshbuf_free should decrement our
175 * refcount to 0 and trigger the actual free.
178 if (buf
->refcount
> 0)
182 * If we are a child, the free our parent to decrement its reference
183 * count and possibly free it.
185 sshbuf_free(buf
->parent
);
188 if (!buf
->readonly
) {
189 explicit_bzero(buf
->d
, buf
->alloc
);
192 freezero(buf
, sizeof(*buf
));
196 sshbuf_reset(struct sshbuf
*buf
)
200 if (buf
->readonly
|| buf
->refcount
> 1) {
201 /* Nonsensical. Just make buffer appear empty */
202 buf
->off
= buf
->size
;
205 if (sshbuf_check_sanity(buf
) != 0)
207 buf
->off
= buf
->size
= 0;
208 if (buf
->alloc
!= SSHBUF_SIZE_INIT
) {
209 if ((d
= recallocarray(buf
->d
, buf
->alloc
, SSHBUF_SIZE_INIT
,
211 buf
->cd
= buf
->d
= d
;
212 buf
->alloc
= SSHBUF_SIZE_INIT
;
215 explicit_bzero(buf
->d
, buf
->alloc
);
219 sshbuf_max_size(const struct sshbuf
*buf
)
221 return buf
->max_size
;
225 sshbuf_alloc(const struct sshbuf
*buf
)
230 const struct sshbuf
*
231 sshbuf_parent(const struct sshbuf
*buf
)
237 sshbuf_refcount(const struct sshbuf
*buf
)
239 return buf
->refcount
;
243 sshbuf_set_max_size(struct sshbuf
*buf
, size_t max_size
)
249 SSHBUF_DBG(("set max buf = %p len = %zu", buf
, max_size
));
250 if ((r
= sshbuf_check_sanity(buf
)) != 0)
252 if (max_size
== buf
->max_size
)
254 if (buf
->readonly
|| buf
->refcount
> 1)
255 return SSH_ERR_BUFFER_READ_ONLY
;
256 if (max_size
> SSHBUF_SIZE_MAX
)
257 return SSH_ERR_NO_BUFFER_SPACE
;
258 /* pack and realloc if necessary */
259 sshbuf_maybe_pack(buf
, max_size
< buf
->size
);
260 if (max_size
< buf
->alloc
&& max_size
> buf
->size
) {
261 if (buf
->size
< SSHBUF_SIZE_INIT
)
262 rlen
= SSHBUF_SIZE_INIT
;
264 rlen
= ROUNDUP(buf
->size
, SSHBUF_SIZE_INC
);
267 SSHBUF_DBG(("new alloc = %zu", rlen
));
268 if ((dp
= recallocarray(buf
->d
, buf
->alloc
, rlen
, 1)) == NULL
)
269 return SSH_ERR_ALLOC_FAIL
;
270 buf
->cd
= buf
->d
= dp
;
273 SSHBUF_TELL("new-max");
274 if (max_size
< buf
->alloc
)
275 return SSH_ERR_NO_BUFFER_SPACE
;
276 buf
->max_size
= max_size
;
281 sshbuf_len(const struct sshbuf
*buf
)
283 if (sshbuf_check_sanity(buf
) != 0)
285 return buf
->size
- buf
->off
;
289 sshbuf_avail(const struct sshbuf
*buf
)
291 if (sshbuf_check_sanity(buf
) != 0 || buf
->readonly
|| buf
->refcount
> 1)
293 return buf
->max_size
- (buf
->size
- buf
->off
);
297 sshbuf_ptr(const struct sshbuf
*buf
)
299 if (sshbuf_check_sanity(buf
) != 0)
301 return buf
->cd
+ buf
->off
;
305 sshbuf_mutable_ptr(const struct sshbuf
*buf
)
307 if (sshbuf_check_sanity(buf
) != 0 || buf
->readonly
|| buf
->refcount
> 1)
309 return buf
->d
+ buf
->off
;
313 sshbuf_check_reserve(const struct sshbuf
*buf
, size_t len
)
317 if ((r
= sshbuf_check_sanity(buf
)) != 0)
319 if (buf
->readonly
|| buf
->refcount
> 1)
320 return SSH_ERR_BUFFER_READ_ONLY
;
321 SSHBUF_TELL("check");
322 /* Check that len is reasonable and that max_size + available < len */
323 if (len
> buf
->max_size
|| buf
->max_size
- len
< buf
->size
- buf
->off
)
324 return SSH_ERR_NO_BUFFER_SPACE
;
329 sshbuf_allocate(struct sshbuf
*buf
, size_t len
)
335 SSHBUF_DBG(("allocate buf = %p len = %zu", buf
, len
));
336 if ((r
= sshbuf_check_reserve(buf
, len
)) != 0)
339 * If the requested allocation appended would push us past max_size
340 * then pack the buffer, zeroing buf->off.
342 sshbuf_maybe_pack(buf
, buf
->size
+ len
> buf
->max_size
);
343 SSHBUF_TELL("allocate");
344 if (len
+ buf
->size
<= buf
->alloc
)
345 return 0; /* already have it. */
348 * Prefer to alloc in SSHBUF_SIZE_INC units, but
349 * allocate less if doing so would overflow max_size.
351 need
= len
+ buf
->size
- buf
->alloc
;
352 rlen
= ROUNDUP(buf
->alloc
+ need
, SSHBUF_SIZE_INC
);
353 SSHBUF_DBG(("need %zu initial rlen %zu", need
, rlen
));
354 if (rlen
> buf
->max_size
)
355 rlen
= buf
->alloc
+ need
;
356 SSHBUF_DBG(("adjusted rlen %zu", rlen
));
357 if ((dp
= recallocarray(buf
->d
, buf
->alloc
, rlen
, 1)) == NULL
) {
358 SSHBUF_DBG(("realloc fail"));
359 return SSH_ERR_ALLOC_FAIL
;
362 buf
->cd
= buf
->d
= dp
;
363 if ((r
= sshbuf_check_reserve(buf
, len
)) < 0) {
372 sshbuf_reserve(struct sshbuf
*buf
, size_t len
, u_char
**dpp
)
380 SSHBUF_DBG(("reserve buf = %p len = %zu", buf
, len
));
381 if ((r
= sshbuf_allocate(buf
, len
)) != 0)
384 dp
= buf
->d
+ buf
->size
;
392 sshbuf_consume(struct sshbuf
*buf
, size_t len
)
396 SSHBUF_DBG(("len = %zu", len
));
397 if ((r
= sshbuf_check_sanity(buf
)) != 0)
401 if (len
> sshbuf_len(buf
))
402 return SSH_ERR_MESSAGE_INCOMPLETE
;
404 /* deal with empty buffer */
405 if (buf
->off
== buf
->size
)
406 buf
->off
= buf
->size
= 0;
412 sshbuf_consume_end(struct sshbuf
*buf
, size_t len
)
416 SSHBUF_DBG(("len = %zu", len
));
417 if ((r
= sshbuf_check_sanity(buf
)) != 0)
421 if (len
> sshbuf_len(buf
))
422 return SSH_ERR_MESSAGE_INCOMPLETE
;