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
42struct 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
54static inline int
55sshbuf_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
75static void
76sshbuf_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
91struct sshbuf *
92sshbuf_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
110struct sshbuf *
111sshbuf_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
127int
128sshbuf_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
142struct sshbuf *
143sshbuf_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
158void
159sshbuf_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
195void
196sshbuf_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
218size_t
219sshbuf_max_size(const struct sshbuf *buf)
220{
221	return buf->max_size;
222}
223
224size_t
225sshbuf_alloc(const struct sshbuf *buf)
226{
227	return buf->alloc;
228}
229
230const struct sshbuf *
231sshbuf_parent(const struct sshbuf *buf)
232{
233	return buf->parent;
234}
235
236u_int
237sshbuf_refcount(const struct sshbuf *buf)
238{
239	return buf->refcount;
240}
241
242int
243sshbuf_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
280size_t
281sshbuf_len(const struct sshbuf *buf)
282{
283	if (sshbuf_check_sanity(buf) != 0)
284		return 0;
285	return buf->size - buf->off;
286}
287
288size_t
289sshbuf_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
296const u_char *
297sshbuf_ptr(const struct sshbuf *buf)
298{
299	if (sshbuf_check_sanity(buf) != 0)
300		return NULL;
301	return buf->cd + buf->off;
302}
303
304u_char *
305sshbuf_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
312int
313sshbuf_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
328int
329sshbuf_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
371int
372sshbuf_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
391int
392sshbuf_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
411int
412sshbuf_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