xref: /openbsd/usr.bin/ssh/sshbuf.c (revision b691e04f)
1 /*	$OpenBSD: sshbuf.c,v 1.23 2024/08/14 15:42:18 tobias 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 #ifdef SSHBUF_DEBUG
30 # define SSHBUF_TELL(what) do { \
31 		printf("%s:%d %s: %s size %zu alloc %zu off %zu max %zu\n", \
32 		    __FILE__, __LINE__, __func__, what, \
33 		    buf->size, buf->alloc, buf->off, buf->max_size); \
34 		fflush(stdout); \
35 	} while (0)
36 #else
37 # define SSHBUF_TELL(what)
38 #endif
39 
40 struct sshbuf {
41 	u_char *d;		/* Data */
42 	const u_char *cd;	/* Const data */
43 	size_t off;		/* First available byte is buf->d + buf->off */
44 	size_t size;		/* Last byte is buf->d + buf->size - 1 */
45 	size_t max_size;	/* Maximum size of buffer */
46 	size_t alloc;		/* Total bytes allocated to buf->d */
47 	int readonly;		/* Refers to external, const data */
48 	u_int refcount;		/* Tracks self and number of child buffers */
49 	struct sshbuf *parent;	/* If child, pointer to parent */
50 };
51 
52 static inline int
sshbuf_check_sanity(const struct sshbuf * buf)53 sshbuf_check_sanity(const struct sshbuf *buf)
54 {
55 	SSHBUF_TELL("sanity");
56 	if (__predict_false(buf == NULL ||
57 	    (!buf->readonly && buf->d != buf->cd) ||
58 	    buf->parent == buf ||
59 	    buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
60 	    buf->cd == NULL ||
61 	    buf->max_size > SSHBUF_SIZE_MAX ||
62 	    buf->alloc > buf->max_size ||
63 	    buf->size > buf->alloc ||
64 	    buf->off > buf->size)) {
65 		/* Do not try to recover from corrupted buffer internals */
66 		SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
67 		ssh_signal(SIGSEGV, SIG_DFL);
68 		raise(SIGSEGV);
69 		return SSH_ERR_INTERNAL_ERROR;
70 	}
71 	return 0;
72 }
73 
74 static void
sshbuf_maybe_pack(struct sshbuf * buf,int force)75 sshbuf_maybe_pack(struct sshbuf *buf, int force)
76 {
77 	SSHBUF_DBG(("force %d", force));
78 	SSHBUF_TELL("pre-pack");
79 	if (buf->off == 0 || buf->readonly || buf->refcount > 1)
80 		return;
81 	if (force ||
82 	    (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
83 		memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
84 		buf->size -= buf->off;
85 		buf->off = 0;
86 		SSHBUF_TELL("packed");
87 	}
88 }
89 
90 struct sshbuf *
sshbuf_new(void)91 sshbuf_new(void)
92 {
93 	struct sshbuf *ret;
94 
95 	if ((ret = calloc(1, sizeof(*ret))) == NULL)
96 		return NULL;
97 	ret->alloc = SSHBUF_SIZE_INIT;
98 	ret->max_size = SSHBUF_SIZE_MAX;
99 	ret->readonly = 0;
100 	ret->refcount = 1;
101 	ret->parent = NULL;
102 	if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
103 		free(ret);
104 		return NULL;
105 	}
106 	return ret;
107 }
108 
109 struct sshbuf *
sshbuf_from(const void * blob,size_t len)110 sshbuf_from(const void *blob, size_t len)
111 {
112 	struct sshbuf *ret;
113 
114 	if (blob == NULL || len > SSHBUF_SIZE_MAX ||
115 	    (ret = calloc(1, sizeof(*ret))) == NULL)
116 		return NULL;
117 	ret->alloc = ret->size = ret->max_size = len;
118 	ret->readonly = 1;
119 	ret->refcount = 1;
120 	ret->parent = NULL;
121 	ret->cd = blob;
122 	ret->d = NULL;
123 	return ret;
124 }
125 
126 int
sshbuf_set_parent(struct sshbuf * child,struct sshbuf * parent)127 sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
128 {
129 	int r;
130 
131 	if ((r = sshbuf_check_sanity(child)) != 0 ||
132 	    (r = sshbuf_check_sanity(parent)) != 0)
133 		return r;
134 	if ((child->parent != NULL && child->parent != parent) ||
135 	    child == 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, then 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 		freezero(buf->d, buf->alloc);
190 	freezero(buf, sizeof(*buf));
191 }
192 
193 void
sshbuf_reset(struct sshbuf * buf)194 sshbuf_reset(struct sshbuf *buf)
195 {
196 	u_char *d;
197 
198 	if (buf->readonly || buf->refcount > 1) {
199 		/* Nonsensical. Just make buffer appear empty */
200 		buf->off = buf->size;
201 		return;
202 	}
203 	if (sshbuf_check_sanity(buf) != 0)
204 		return;
205 	buf->off = buf->size = 0;
206 	if (buf->alloc != SSHBUF_SIZE_INIT) {
207 		if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
208 		    1)) != NULL) {
209 			buf->cd = buf->d = d;
210 			buf->alloc = SSHBUF_SIZE_INIT;
211 		}
212 	}
213 	explicit_bzero(buf->d, buf->alloc);
214 }
215 
216 size_t
sshbuf_max_size(const struct sshbuf * buf)217 sshbuf_max_size(const struct sshbuf *buf)
218 {
219 	return buf->max_size;
220 }
221 
222 size_t
sshbuf_alloc(const struct sshbuf * buf)223 sshbuf_alloc(const struct sshbuf *buf)
224 {
225 	return buf->alloc;
226 }
227 
228 const struct sshbuf *
sshbuf_parent(const struct sshbuf * buf)229 sshbuf_parent(const struct sshbuf *buf)
230 {
231 	return buf->parent;
232 }
233 
234 u_int
sshbuf_refcount(const struct sshbuf * buf)235 sshbuf_refcount(const struct sshbuf *buf)
236 {
237 	return buf->refcount;
238 }
239 
240 int
sshbuf_set_max_size(struct sshbuf * buf,size_t max_size)241 sshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
242 {
243 	size_t rlen;
244 	u_char *dp;
245 	int r;
246 
247 	SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
248 	if ((r = sshbuf_check_sanity(buf)) != 0)
249 		return r;
250 	if (max_size == buf->max_size)
251 		return 0;
252 	if (buf->readonly || buf->refcount > 1)
253 		return SSH_ERR_BUFFER_READ_ONLY;
254 	if (max_size > SSHBUF_SIZE_MAX)
255 		return SSH_ERR_NO_BUFFER_SPACE;
256 	/* pack and realloc if necessary */
257 	sshbuf_maybe_pack(buf, max_size < buf->size);
258 	if (max_size < buf->alloc && max_size > buf->size) {
259 		if (buf->size < SSHBUF_SIZE_INIT)
260 			rlen = SSHBUF_SIZE_INIT;
261 		else
262 			rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC);
263 		if (rlen > max_size)
264 			rlen = max_size;
265 		SSHBUF_DBG(("new alloc = %zu", rlen));
266 		if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL)
267 			return SSH_ERR_ALLOC_FAIL;
268 		buf->cd = buf->d = dp;
269 		buf->alloc = rlen;
270 	}
271 	SSHBUF_TELL("new-max");
272 	if (max_size < buf->alloc)
273 		return SSH_ERR_NO_BUFFER_SPACE;
274 	buf->max_size = max_size;
275 	return 0;
276 }
277 
278 size_t
sshbuf_len(const struct sshbuf * buf)279 sshbuf_len(const struct sshbuf *buf)
280 {
281 	if (sshbuf_check_sanity(buf) != 0)
282 		return 0;
283 	return buf->size - buf->off;
284 }
285 
286 size_t
sshbuf_avail(const struct sshbuf * buf)287 sshbuf_avail(const struct sshbuf *buf)
288 {
289 	if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
290 		return 0;
291 	return buf->max_size - (buf->size - buf->off);
292 }
293 
294 const u_char *
sshbuf_ptr(const struct sshbuf * buf)295 sshbuf_ptr(const struct sshbuf *buf)
296 {
297 	if (sshbuf_check_sanity(buf) != 0)
298 		return NULL;
299 	return buf->cd + buf->off;
300 }
301 
302 u_char *
sshbuf_mutable_ptr(const struct sshbuf * buf)303 sshbuf_mutable_ptr(const struct sshbuf *buf)
304 {
305 	if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
306 		return NULL;
307 	return buf->d + buf->off;
308 }
309 
310 int
sshbuf_check_reserve(const struct sshbuf * buf,size_t len)311 sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
312 {
313 	int r;
314 
315 	if ((r = sshbuf_check_sanity(buf)) != 0)
316 		return r;
317 	if (buf->readonly || buf->refcount > 1)
318 		return SSH_ERR_BUFFER_READ_ONLY;
319 	SSHBUF_TELL("check");
320 	/* Check that len is reasonable and that max_size + available < len */
321 	if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
322 		return SSH_ERR_NO_BUFFER_SPACE;
323 	return 0;
324 }
325 
326 int
sshbuf_allocate(struct sshbuf * buf,size_t len)327 sshbuf_allocate(struct sshbuf *buf, size_t len)
328 {
329 	size_t rlen, need;
330 	u_char *dp;
331 	int r;
332 
333 	SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
334 	if ((r = sshbuf_check_reserve(buf, len)) != 0)
335 		return r;
336 	/*
337 	 * If the requested allocation appended would push us past max_size
338 	 * then pack the buffer, zeroing buf->off.
339 	 */
340 	sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
341 	SSHBUF_TELL("allocate");
342 	if (len + buf->size <= buf->alloc)
343 		return 0; /* already have it. */
344 
345 	/*
346 	 * Prefer to alloc in SSHBUF_SIZE_INC units, but
347 	 * allocate less if doing so would overflow max_size.
348 	 */
349 	need = len + buf->size - buf->alloc;
350 	rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
351 	SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
352 	if (rlen > buf->max_size)
353 		rlen = buf->alloc + need;
354 	SSHBUF_DBG(("adjusted rlen %zu", rlen));
355 	if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) {
356 		SSHBUF_DBG(("realloc fail"));
357 		return SSH_ERR_ALLOC_FAIL;
358 	}
359 	buf->alloc = rlen;
360 	buf->cd = buf->d = dp;
361 	if ((r = sshbuf_check_reserve(buf, len)) < 0) {
362 		/* shouldn't fail */
363 		return r;
364 	}
365 	SSHBUF_TELL("done");
366 	return 0;
367 }
368 
369 int
sshbuf_reserve(struct sshbuf * buf,size_t len,u_char ** dpp)370 sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
371 {
372 	u_char *dp;
373 	int r;
374 
375 	if (dpp != NULL)
376 		*dpp = NULL;
377 
378 	SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
379 	if ((r = sshbuf_allocate(buf, len)) != 0)
380 		return r;
381 
382 	dp = buf->d + buf->size;
383 	buf->size += len;
384 	if (dpp != NULL)
385 		*dpp = dp;
386 	return 0;
387 }
388 
389 int
sshbuf_consume(struct sshbuf * buf,size_t len)390 sshbuf_consume(struct sshbuf *buf, size_t len)
391 {
392 	int r;
393 
394 	SSHBUF_DBG(("len = %zu", len));
395 	if ((r = sshbuf_check_sanity(buf)) != 0)
396 		return r;
397 	if (len == 0)
398 		return 0;
399 	if (len > sshbuf_len(buf))
400 		return SSH_ERR_MESSAGE_INCOMPLETE;
401 	buf->off += len;
402 	/* deal with empty buffer */
403 	if (buf->off == buf->size)
404 		buf->off = buf->size = 0;
405 	SSHBUF_TELL("done");
406 	return 0;
407 }
408 
409 int
sshbuf_consume_end(struct sshbuf * buf,size_t len)410 sshbuf_consume_end(struct sshbuf *buf, size_t len)
411 {
412 	int r;
413 
414 	SSHBUF_DBG(("len = %zu", len));
415 	if ((r = sshbuf_check_sanity(buf)) != 0)
416 		return r;
417 	if (len == 0)
418 		return 0;
419 	if (len > sshbuf_len(buf))
420 		return SSH_ERR_MESSAGE_INCOMPLETE;
421 	buf->size -= len;
422 	SSHBUF_TELL("done");
423 	return 0;
424 }
425 
426