xref: /freebsd/crypto/openssh/sshbuf.c (revision e0c4386e)
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 
18 #include "includes.h"
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
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
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 *
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 *
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
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 *
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
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
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
219 sshbuf_max_size(const struct sshbuf *buf)
220 {
221 	return buf->max_size;
222 }
223 
224 size_t
225 sshbuf_alloc(const struct sshbuf *buf)
226 {
227 	return buf->alloc;
228 }
229 
230 const struct sshbuf *
231 sshbuf_parent(const struct sshbuf *buf)
232 {
233 	return buf->parent;
234 }
235 
236 u_int
237 sshbuf_refcount(const struct sshbuf *buf)
238 {
239 	return buf->refcount;
240 }
241 
242 int
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
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
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 *
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 *
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
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
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
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
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
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