1 /* $OpenBSD: sshbuf.c,v 1.19 2022/12/02 04:40:27 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 #include "includes.h"
18 __RCSID("$NetBSD: sshbuf.c,v 1.14 2023/07/26 17:58:16 christos Exp $");
19
20 #include <sys/types.h>
21 #include <signal.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25
26 #include "ssherr.h"
27 #define SSHBUF_INTERNAL
28 #include "sshbuf.h"
29 #include "misc.h"
30
31 #ifdef SSHBUF_DEBUG
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); \
36 fflush(stdout); \
37 } while (0)
38 #else
39 # define SSHBUF_TELL(what)
40 #endif
41
42 struct sshbuf {
43 u_char *d; /* Data */
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 */
52 };
53
54 static inline int
sshbuf_check_sanity(const struct sshbuf * buf)55 sshbuf_check_sanity(const struct sshbuf *buf)
56 {
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 ||
61 buf->cd == NULL ||
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);
69 raise(SIGSEGV);
70 return SSH_ERR_INTERNAL_ERROR;
71 }
72 return 0;
73 }
74
75 static void
sshbuf_maybe_pack(struct sshbuf * buf,int force)76 sshbuf_maybe_pack(struct sshbuf *buf, int force)
77 {
78 SSHBUF_DBG(("force %d", force));
79 SSHBUF_TELL("pre-pack");
80 if (buf->off == 0 || buf->readonly || buf->refcount > 1)
81 return;
82 if (force ||
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;
86 buf->off = 0;
87 SSHBUF_TELL("packed");
88 }
89 }
90
91 struct sshbuf *
sshbuf_new(void)92 sshbuf_new(void)
93 {
94 struct sshbuf *ret;
95
96 if ((ret = calloc(sizeof(*ret), 1)) == NULL)
97 return NULL;
98 ret->alloc = SSHBUF_SIZE_INIT;
99 ret->max_size = SSHBUF_SIZE_MAX;
100 ret->readonly = 0;
101 ret->refcount = 1;
102 ret->parent = NULL;
103 if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
104 free(ret);
105 return NULL;
106 }
107 return ret;
108 }
109
110 struct sshbuf *
sshbuf_from(const void * blob,size_t len)111 sshbuf_from(const void *blob, size_t len)
112 {
113 struct sshbuf *ret;
114
115 if (blob == NULL || len > SSHBUF_SIZE_MAX ||
116 (ret = calloc(sizeof(*ret), 1)) == NULL)
117 return NULL;
118 ret->alloc = ret->size = ret->max_size = len;
119 ret->readonly = 1;
120 ret->refcount = 1;
121 ret->parent = NULL;
122 ret->cd = blob;
123 ret->d = NULL;
124 return ret;
125 }
126
127 int
sshbuf_set_parent(struct sshbuf * child,struct sshbuf * parent)128 sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
129 {
130 int r;
131
132 if ((r = sshbuf_check_sanity(child)) != 0 ||
133 (r = sshbuf_check_sanity(parent)) != 0)
134 return r;
135 if (child->parent != NULL && child->parent != parent)
136 return SSH_ERR_INTERNAL_ERROR;
137 child->parent = parent;
138 child->parent->refcount++;
139 return 0;
140 }
141
142 struct sshbuf *
sshbuf_fromb(struct sshbuf * buf)143 sshbuf_fromb(struct sshbuf *buf)
144 {
145 struct sshbuf *ret;
146
147 if (sshbuf_check_sanity(buf) != 0)
148 return NULL;
149 if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
150 return NULL;
151 if (sshbuf_set_parent(ret, buf) != 0) {
152 sshbuf_free(ret);
153 return NULL;
154 }
155 return ret;
156 }
157
158 void
sshbuf_free(struct sshbuf * buf)159 sshbuf_free(struct sshbuf *buf)
160 {
161 if (buf == NULL)
162 return;
163 /*
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
167 * be bad.
168 */
169 if (sshbuf_check_sanity(buf) != 0)
170 return;
171
172 /*
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.
176 */
177 buf->refcount--;
178 if (buf->refcount > 0)
179 return;
180
181 /*
182 * If we are a child, the free our parent to decrement its reference
183 * count and possibly free it.
184 */
185 sshbuf_free(buf->parent);
186 buf->parent = NULL;
187
188 if (!buf->readonly) {
189 explicit_bzero(buf->d, buf->alloc);
190 free(buf->d);
191 }
192 freezero(buf, sizeof(*buf));
193 }
194
195 void
sshbuf_reset(struct sshbuf * buf)196 sshbuf_reset(struct sshbuf *buf)
197 {
198 u_char *d;
199
200 if (buf->readonly || buf->refcount > 1) {
201 /* Nonsensical. Just make buffer appear empty */
202 buf->off = buf->size;
203 return;
204 }
205 if (sshbuf_check_sanity(buf) != 0)
206 return;
207 buf->off = buf->size = 0;
208 if (buf->alloc != SSHBUF_SIZE_INIT) {
209 if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
210 1)) != NULL) {
211 buf->cd = buf->d = d;
212 buf->alloc = SSHBUF_SIZE_INIT;
213 }
214 }
215 explicit_bzero(buf->d, buf->alloc);
216 }
217
218 size_t
sshbuf_max_size(const struct sshbuf * buf)219 sshbuf_max_size(const struct sshbuf *buf)
220 {
221 return buf->max_size;
222 }
223
224 size_t
sshbuf_alloc(const struct sshbuf * buf)225 sshbuf_alloc(const struct sshbuf *buf)
226 {
227 return buf->alloc;
228 }
229
230 const struct sshbuf *
sshbuf_parent(const struct sshbuf * buf)231 sshbuf_parent(const struct sshbuf *buf)
232 {
233 return buf->parent;
234 }
235
236 u_int
sshbuf_refcount(const struct sshbuf * buf)237 sshbuf_refcount(const struct sshbuf *buf)
238 {
239 return buf->refcount;
240 }
241
242 int
sshbuf_set_max_size(struct sshbuf * buf,size_t max_size)243 sshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
244 {
245 size_t rlen;
246 u_char *dp;
247 int r;
248
249 SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
250 if ((r = sshbuf_check_sanity(buf)) != 0)
251 return r;
252 if (max_size == buf->max_size)
253 return 0;
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;
263 else
264 rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC);
265 if (rlen > max_size)
266 rlen = max_size;
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;
271 buf->alloc = rlen;
272 }
273 SSHBUF_TELL("new-max");
274 if (max_size < buf->alloc)
275 return SSH_ERR_NO_BUFFER_SPACE;
276 buf->max_size = max_size;
277 return 0;
278 }
279
280 size_t
sshbuf_len(const struct sshbuf * buf)281 sshbuf_len(const struct sshbuf *buf)
282 {
283 if (sshbuf_check_sanity(buf) != 0)
284 return 0;
285 return buf->size - buf->off;
286 }
287
288 size_t
sshbuf_avail(const struct sshbuf * buf)289 sshbuf_avail(const struct sshbuf *buf)
290 {
291 if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
292 return 0;
293 return buf->max_size - (buf->size - buf->off);
294 }
295
296 const u_char *
sshbuf_ptr(const struct sshbuf * buf)297 sshbuf_ptr(const struct sshbuf *buf)
298 {
299 if (sshbuf_check_sanity(buf) != 0)
300 return NULL;
301 return buf->cd + buf->off;
302 }
303
304 u_char *
sshbuf_mutable_ptr(const struct sshbuf * buf)305 sshbuf_mutable_ptr(const struct sshbuf *buf)
306 {
307 if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
308 return NULL;
309 return buf->d + buf->off;
310 }
311
312 int
sshbuf_check_reserve(const struct sshbuf * buf,size_t len)313 sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
314 {
315 int r;
316
317 if ((r = sshbuf_check_sanity(buf)) != 0)
318 return r;
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;
325 return 0;
326 }
327
328 int
sshbuf_allocate(struct sshbuf * buf,size_t len)329 sshbuf_allocate(struct sshbuf *buf, size_t len)
330 {
331 size_t rlen, need;
332 u_char *dp;
333 int r;
334
335 SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
336 if ((r = sshbuf_check_reserve(buf, len)) != 0)
337 return r;
338 /*
339 * If the requested allocation appended would push us past max_size
340 * then pack the buffer, zeroing buf->off.
341 */
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. */
346
347 /*
348 * Prefer to alloc in SSHBUF_SIZE_INC units, but
349 * allocate less if doing so would overflow max_size.
350 */
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;
360 }
361 buf->alloc = rlen;
362 buf->cd = buf->d = dp;
363 if ((r = sshbuf_check_reserve(buf, len)) < 0) {
364 /* shouldn't fail */
365 return r;
366 }
367 SSHBUF_TELL("done");
368 return 0;
369 }
370
371 int
sshbuf_reserve(struct sshbuf * buf,size_t len,u_char ** dpp)372 sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
373 {
374 u_char *dp;
375 int r;
376
377 if (dpp != NULL)
378 *dpp = NULL;
379
380 SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
381 if ((r = sshbuf_allocate(buf, len)) != 0)
382 return r;
383
384 dp = buf->d + buf->size;
385 buf->size += len;
386 if (dpp != NULL)
387 *dpp = dp;
388 return 0;
389 }
390
391 int
sshbuf_consume(struct sshbuf * buf,size_t len)392 sshbuf_consume(struct sshbuf *buf, size_t len)
393 {
394 int r;
395
396 SSHBUF_DBG(("len = %zu", len));
397 if ((r = sshbuf_check_sanity(buf)) != 0)
398 return r;
399 if (len == 0)
400 return 0;
401 if (len > sshbuf_len(buf))
402 return SSH_ERR_MESSAGE_INCOMPLETE;
403 buf->off += len;
404 /* deal with empty buffer */
405 if (buf->off == buf->size)
406 buf->off = buf->size = 0;
407 SSHBUF_TELL("done");
408 return 0;
409 }
410
411 int
sshbuf_consume_end(struct sshbuf * buf,size_t len)412 sshbuf_consume_end(struct sshbuf *buf, size_t len)
413 {
414 int r;
415
416 SSHBUF_DBG(("len = %zu", len));
417 if ((r = sshbuf_check_sanity(buf)) != 0)
418 return r;
419 if (len == 0)
420 return 0;
421 if (len > sshbuf_len(buf))
422 return SSH_ERR_MESSAGE_INCOMPLETE;
423 buf->size -= len;
424 SSHBUF_TELL("done");
425 return 0;
426 }
427
428