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 <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
40struct 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
52static inline int
53sshbuf_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->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
59	    buf->cd == NULL ||
60	    buf->max_size > SSHBUF_SIZE_MAX ||
61	    buf->alloc > buf->max_size ||
62	    buf->size > buf->alloc ||
63	    buf->off > buf->size)) {
64		/* Do not try to recover from corrupted buffer internals */
65		SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
66		ssh_signal(SIGSEGV, SIG_DFL);
67		raise(SIGSEGV);
68		return SSH_ERR_INTERNAL_ERROR;
69	}
70	return 0;
71}
72
73static void
74sshbuf_maybe_pack(struct sshbuf *buf, int force)
75{
76	SSHBUF_DBG(("force %d", force));
77	SSHBUF_TELL("pre-pack");
78	if (buf->off == 0 || buf->readonly || buf->refcount > 1)
79		return;
80	if (force ||
81	    (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
82		memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
83		buf->size -= buf->off;
84		buf->off = 0;
85		SSHBUF_TELL("packed");
86	}
87}
88
89struct sshbuf *
90sshbuf_new(void)
91{
92	struct sshbuf *ret;
93
94	if ((ret = calloc(sizeof(*ret), 1)) == NULL)
95		return NULL;
96	ret->alloc = SSHBUF_SIZE_INIT;
97	ret->max_size = SSHBUF_SIZE_MAX;
98	ret->readonly = 0;
99	ret->refcount = 1;
100	ret->parent = NULL;
101	if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
102		free(ret);
103		return NULL;
104	}
105	return ret;
106}
107
108struct sshbuf *
109sshbuf_from(const void *blob, size_t len)
110{
111	struct sshbuf *ret;
112
113	if (blob == NULL || len > SSHBUF_SIZE_MAX ||
114	    (ret = calloc(sizeof(*ret), 1)) == NULL)
115		return NULL;
116	ret->alloc = ret->size = ret->max_size = len;
117	ret->readonly = 1;
118	ret->refcount = 1;
119	ret->parent = NULL;
120	ret->cd = blob;
121	ret->d = NULL;
122	return ret;
123}
124
125int
126sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
127{
128	int r;
129
130	if ((r = sshbuf_check_sanity(child)) != 0 ||
131	    (r = sshbuf_check_sanity(parent)) != 0)
132		return r;
133	if (child->parent != NULL && child->parent != parent)
134		return SSH_ERR_INTERNAL_ERROR;
135	child->parent = parent;
136	child->parent->refcount++;
137	return 0;
138}
139
140struct sshbuf *
141sshbuf_fromb(struct sshbuf *buf)
142{
143	struct sshbuf *ret;
144
145	if (sshbuf_check_sanity(buf) != 0)
146		return NULL;
147	if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
148		return NULL;
149	if (sshbuf_set_parent(ret, buf) != 0) {
150		sshbuf_free(ret);
151		return NULL;
152	}
153	return ret;
154}
155
156void
157sshbuf_free(struct sshbuf *buf)
158{
159	if (buf == NULL)
160		return;
161	/*
162	 * The following will leak on insane buffers, but this is the safest
163	 * course of action - an invalid pointer or already-freed pointer may
164	 * have been passed to us and continuing to scribble over memory would
165	 * be bad.
166	 */
167	if (sshbuf_check_sanity(buf) != 0)
168		return;
169
170	/*
171	 * If we are a parent with still-extant children, then don't free just
172	 * yet. The last child's call to sshbuf_free should decrement our
173	 * refcount to 0 and trigger the actual free.
174	 */
175	buf->refcount--;
176	if (buf->refcount > 0)
177		return;
178
179	/*
180	 * If we are a child, the free our parent to decrement its reference
181	 * count and possibly free it.
182	 */
183	sshbuf_free(buf->parent);
184	buf->parent = NULL;
185
186	if (!buf->readonly) {
187		explicit_bzero(buf->d, buf->alloc);
188		free(buf->d);
189	}
190	freezero(buf, sizeof(*buf));
191}
192
193void
194sshbuf_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
216size_t
217sshbuf_max_size(const struct sshbuf *buf)
218{
219	return buf->max_size;
220}
221
222size_t
223sshbuf_alloc(const struct sshbuf *buf)
224{
225	return buf->alloc;
226}
227
228const struct sshbuf *
229sshbuf_parent(const struct sshbuf *buf)
230{
231	return buf->parent;
232}
233
234u_int
235sshbuf_refcount(const struct sshbuf *buf)
236{
237	return buf->refcount;
238}
239
240int
241sshbuf_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
278size_t
279sshbuf_len(const struct sshbuf *buf)
280{
281	if (sshbuf_check_sanity(buf) != 0)
282		return 0;
283	return buf->size - buf->off;
284}
285
286size_t
287sshbuf_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
294const u_char *
295sshbuf_ptr(const struct sshbuf *buf)
296{
297	if (sshbuf_check_sanity(buf) != 0)
298		return NULL;
299	return buf->cd + buf->off;
300}
301
302u_char *
303sshbuf_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
310int
311sshbuf_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
326int
327sshbuf_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
369int
370sshbuf_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
389int
390sshbuf_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
409int
410sshbuf_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