1296781Sdes/*	$OpenBSD: sshbuf.c,v 1.6 2016/01/12 23:42:54 djm Exp $	*/
2276707Sdes/*
3276707Sdes * Copyright (c) 2011 Damien Miller
4276707Sdes *
5276707Sdes * Permission to use, copy, modify, and distribute this software for any
6276707Sdes * purpose with or without fee is hereby granted, provided that the above
7276707Sdes * copyright notice and this permission notice appear in all copies.
8276707Sdes *
9276707Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10276707Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11276707Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12276707Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13276707Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14276707Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15276707Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16276707Sdes */
17276707Sdes
18276707Sdes#define SSHBUF_INTERNAL
19276707Sdes#include "includes.h"
20276707Sdes
21295367Sdes#include <sys/param.h>	/* roundup */
22276707Sdes#include <sys/types.h>
23276707Sdes#include <signal.h>
24276707Sdes#include <stdlib.h>
25276707Sdes#include <stdio.h>
26276707Sdes#include <string.h>
27276707Sdes
28276707Sdes#include "ssherr.h"
29276707Sdes#include "sshbuf.h"
30276707Sdes
31276707Sdesstatic inline int
32276707Sdessshbuf_check_sanity(const struct sshbuf *buf)
33276707Sdes{
34276707Sdes	SSHBUF_TELL("sanity");
35276707Sdes	if (__predict_false(buf == NULL ||
36276707Sdes	    (!buf->readonly && buf->d != buf->cd) ||
37276707Sdes	    buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
38276707Sdes	    buf->cd == NULL ||
39276707Sdes	    (buf->dont_free && (buf->readonly || buf->parent != NULL)) ||
40276707Sdes	    buf->max_size > SSHBUF_SIZE_MAX ||
41276707Sdes	    buf->alloc > buf->max_size ||
42276707Sdes	    buf->size > buf->alloc ||
43276707Sdes	    buf->off > buf->size)) {
44276707Sdes		/* Do not try to recover from corrupted buffer internals */
45276707Sdes		SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
46276707Sdes		signal(SIGSEGV, SIG_DFL);
47276707Sdes		raise(SIGSEGV);
48276707Sdes		return SSH_ERR_INTERNAL_ERROR;
49276707Sdes	}
50276707Sdes	return 0;
51276707Sdes}
52276707Sdes
53276707Sdesstatic void
54276707Sdessshbuf_maybe_pack(struct sshbuf *buf, int force)
55276707Sdes{
56276707Sdes	SSHBUF_DBG(("force %d", force));
57276707Sdes	SSHBUF_TELL("pre-pack");
58276707Sdes	if (buf->off == 0 || buf->readonly || buf->refcount > 1)
59276707Sdes		return;
60276707Sdes	if (force ||
61276707Sdes	    (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
62276707Sdes		memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
63276707Sdes		buf->size -= buf->off;
64276707Sdes		buf->off = 0;
65276707Sdes		SSHBUF_TELL("packed");
66276707Sdes	}
67276707Sdes}
68276707Sdes
69276707Sdesstruct sshbuf *
70276707Sdessshbuf_new(void)
71276707Sdes{
72276707Sdes	struct sshbuf *ret;
73276707Sdes
74276707Sdes	if ((ret = calloc(sizeof(*ret), 1)) == NULL)
75276707Sdes		return NULL;
76276707Sdes	ret->alloc = SSHBUF_SIZE_INIT;
77276707Sdes	ret->max_size = SSHBUF_SIZE_MAX;
78276707Sdes	ret->readonly = 0;
79276707Sdes	ret->refcount = 1;
80276707Sdes	ret->parent = NULL;
81276707Sdes	if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
82276707Sdes		free(ret);
83276707Sdes		return NULL;
84276707Sdes	}
85276707Sdes	return ret;
86276707Sdes}
87276707Sdes
88276707Sdesstruct sshbuf *
89276707Sdessshbuf_from(const void *blob, size_t len)
90276707Sdes{
91276707Sdes	struct sshbuf *ret;
92276707Sdes
93276707Sdes	if (blob == NULL || len > SSHBUF_SIZE_MAX ||
94276707Sdes	    (ret = calloc(sizeof(*ret), 1)) == NULL)
95276707Sdes		return NULL;
96276707Sdes	ret->alloc = ret->size = ret->max_size = len;
97276707Sdes	ret->readonly = 1;
98276707Sdes	ret->refcount = 1;
99276707Sdes	ret->parent = NULL;
100276707Sdes	ret->cd = blob;
101276707Sdes	ret->d = NULL;
102276707Sdes	return ret;
103276707Sdes}
104276707Sdes
105276707Sdesint
106276707Sdessshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
107276707Sdes{
108276707Sdes	int r;
109276707Sdes
110276707Sdes	if ((r = sshbuf_check_sanity(child)) != 0 ||
111276707Sdes	    (r = sshbuf_check_sanity(parent)) != 0)
112276707Sdes		return r;
113276707Sdes	child->parent = parent;
114276707Sdes	child->parent->refcount++;
115276707Sdes	return 0;
116276707Sdes}
117276707Sdes
118276707Sdesstruct sshbuf *
119276707Sdessshbuf_fromb(struct sshbuf *buf)
120276707Sdes{
121276707Sdes	struct sshbuf *ret;
122276707Sdes
123276707Sdes	if (sshbuf_check_sanity(buf) != 0)
124276707Sdes		return NULL;
125276707Sdes	if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
126276707Sdes		return NULL;
127276707Sdes	if (sshbuf_set_parent(ret, buf) != 0) {
128276707Sdes		sshbuf_free(ret);
129276707Sdes		return NULL;
130276707Sdes	}
131276707Sdes	return ret;
132276707Sdes}
133276707Sdes
134276707Sdesvoid
135276707Sdessshbuf_init(struct sshbuf *ret)
136276707Sdes{
137295367Sdes	explicit_bzero(ret, sizeof(*ret));
138276707Sdes	ret->alloc = SSHBUF_SIZE_INIT;
139276707Sdes	ret->max_size = SSHBUF_SIZE_MAX;
140276707Sdes	ret->readonly = 0;
141276707Sdes	ret->dont_free = 1;
142276707Sdes	ret->refcount = 1;
143276707Sdes	if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL)
144276707Sdes		ret->alloc = 0;
145276707Sdes}
146276707Sdes
147276707Sdesvoid
148276707Sdessshbuf_free(struct sshbuf *buf)
149276707Sdes{
150276707Sdes	int dont_free = 0;
151276707Sdes
152276707Sdes	if (buf == NULL)
153276707Sdes		return;
154276707Sdes	/*
155276707Sdes	 * The following will leak on insane buffers, but this is the safest
156276707Sdes	 * course of action - an invalid pointer or already-freed pointer may
157276707Sdes	 * have been passed to us and continuing to scribble over memory would
158276707Sdes	 * be bad.
159276707Sdes	 */
160276707Sdes	if (sshbuf_check_sanity(buf) != 0)
161276707Sdes		return;
162276707Sdes	/*
163276707Sdes	 * If we are a child, the free our parent to decrement its reference
164276707Sdes	 * count and possibly free it.
165276707Sdes	 */
166296781Sdes	sshbuf_free(buf->parent);
167296781Sdes	buf->parent = NULL;
168276707Sdes	/*
169276707Sdes	 * If we are a parent with still-extant children, then don't free just
170276707Sdes	 * yet. The last child's call to sshbuf_free should decrement our
171276707Sdes	 * refcount to 0 and trigger the actual free.
172276707Sdes	 */
173276707Sdes	buf->refcount--;
174276707Sdes	if (buf->refcount > 0)
175276707Sdes		return;
176276707Sdes	dont_free = buf->dont_free;
177276707Sdes	if (!buf->readonly) {
178295367Sdes		explicit_bzero(buf->d, buf->alloc);
179276707Sdes		free(buf->d);
180276707Sdes	}
181295367Sdes	explicit_bzero(buf, sizeof(*buf));
182276707Sdes	if (!dont_free)
183276707Sdes		free(buf);
184276707Sdes}
185276707Sdes
186276707Sdesvoid
187276707Sdessshbuf_reset(struct sshbuf *buf)
188276707Sdes{
189276707Sdes	u_char *d;
190276707Sdes
191276707Sdes	if (buf->readonly || buf->refcount > 1) {
192276707Sdes		/* Nonsensical. Just make buffer appear empty */
193276707Sdes		buf->off = buf->size;
194276707Sdes		return;
195276707Sdes	}
196276707Sdes	if (sshbuf_check_sanity(buf) == 0)
197295367Sdes		explicit_bzero(buf->d, buf->alloc);
198276707Sdes	buf->off = buf->size = 0;
199276707Sdes	if (buf->alloc != SSHBUF_SIZE_INIT) {
200276707Sdes		if ((d = realloc(buf->d, SSHBUF_SIZE_INIT)) != NULL) {
201276707Sdes			buf->cd = buf->d = d;
202276707Sdes			buf->alloc = SSHBUF_SIZE_INIT;
203276707Sdes		}
204276707Sdes	}
205276707Sdes}
206276707Sdes
207276707Sdessize_t
208276707Sdessshbuf_max_size(const struct sshbuf *buf)
209276707Sdes{
210276707Sdes	return buf->max_size;
211276707Sdes}
212276707Sdes
213276707Sdessize_t
214276707Sdessshbuf_alloc(const struct sshbuf *buf)
215276707Sdes{
216276707Sdes	return buf->alloc;
217276707Sdes}
218276707Sdes
219276707Sdesconst struct sshbuf *
220276707Sdessshbuf_parent(const struct sshbuf *buf)
221276707Sdes{
222276707Sdes	return buf->parent;
223276707Sdes}
224276707Sdes
225276707Sdesu_int
226276707Sdessshbuf_refcount(const struct sshbuf *buf)
227276707Sdes{
228276707Sdes	return buf->refcount;
229276707Sdes}
230276707Sdes
231276707Sdesint
232276707Sdessshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
233276707Sdes{
234276707Sdes	size_t rlen;
235276707Sdes	u_char *dp;
236276707Sdes	int r;
237276707Sdes
238276707Sdes	SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
239276707Sdes	if ((r = sshbuf_check_sanity(buf)) != 0)
240276707Sdes		return r;
241276707Sdes	if (max_size == buf->max_size)
242276707Sdes		return 0;
243276707Sdes	if (buf->readonly || buf->refcount > 1)
244276707Sdes		return SSH_ERR_BUFFER_READ_ONLY;
245276707Sdes	if (max_size > SSHBUF_SIZE_MAX)
246276707Sdes		return SSH_ERR_NO_BUFFER_SPACE;
247276707Sdes	/* pack and realloc if necessary */
248276707Sdes	sshbuf_maybe_pack(buf, max_size < buf->size);
249276707Sdes	if (max_size < buf->alloc && max_size > buf->size) {
250276707Sdes		if (buf->size < SSHBUF_SIZE_INIT)
251276707Sdes			rlen = SSHBUF_SIZE_INIT;
252276707Sdes		else
253276707Sdes			rlen = roundup(buf->size, SSHBUF_SIZE_INC);
254276707Sdes		if (rlen > max_size)
255276707Sdes			rlen = max_size;
256295367Sdes		explicit_bzero(buf->d + buf->size, buf->alloc - buf->size);
257276707Sdes		SSHBUF_DBG(("new alloc = %zu", rlen));
258276707Sdes		if ((dp = realloc(buf->d, rlen)) == NULL)
259276707Sdes			return SSH_ERR_ALLOC_FAIL;
260276707Sdes		buf->cd = buf->d = dp;
261276707Sdes		buf->alloc = rlen;
262276707Sdes	}
263276707Sdes	SSHBUF_TELL("new-max");
264276707Sdes	if (max_size < buf->alloc)
265276707Sdes		return SSH_ERR_NO_BUFFER_SPACE;
266276707Sdes	buf->max_size = max_size;
267276707Sdes	return 0;
268276707Sdes}
269276707Sdes
270276707Sdessize_t
271276707Sdessshbuf_len(const struct sshbuf *buf)
272276707Sdes{
273276707Sdes	if (sshbuf_check_sanity(buf) != 0)
274276707Sdes		return 0;
275276707Sdes	return buf->size - buf->off;
276276707Sdes}
277276707Sdes
278276707Sdessize_t
279276707Sdessshbuf_avail(const struct sshbuf *buf)
280276707Sdes{
281276707Sdes	if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
282276707Sdes		return 0;
283276707Sdes	return buf->max_size - (buf->size - buf->off);
284276707Sdes}
285276707Sdes
286276707Sdesconst u_char *
287276707Sdessshbuf_ptr(const struct sshbuf *buf)
288276707Sdes{
289276707Sdes	if (sshbuf_check_sanity(buf) != 0)
290276707Sdes		return NULL;
291276707Sdes	return buf->cd + buf->off;
292276707Sdes}
293276707Sdes
294276707Sdesu_char *
295276707Sdessshbuf_mutable_ptr(const struct sshbuf *buf)
296276707Sdes{
297276707Sdes	if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
298276707Sdes		return NULL;
299276707Sdes	return buf->d + buf->off;
300276707Sdes}
301276707Sdes
302276707Sdesint
303276707Sdessshbuf_check_reserve(const struct sshbuf *buf, size_t len)
304276707Sdes{
305276707Sdes	int r;
306276707Sdes
307276707Sdes	if ((r = sshbuf_check_sanity(buf)) != 0)
308276707Sdes		return r;
309276707Sdes	if (buf->readonly || buf->refcount > 1)
310276707Sdes		return SSH_ERR_BUFFER_READ_ONLY;
311276707Sdes	SSHBUF_TELL("check");
312276707Sdes	/* Check that len is reasonable and that max_size + available < len */
313276707Sdes	if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
314276707Sdes		return SSH_ERR_NO_BUFFER_SPACE;
315276707Sdes	return 0;
316276707Sdes}
317276707Sdes
318276707Sdesint
319276707Sdessshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
320276707Sdes{
321276707Sdes	size_t rlen, need;
322276707Sdes	u_char *dp;
323276707Sdes	int r;
324276707Sdes
325276707Sdes	if (dpp != NULL)
326276707Sdes		*dpp = NULL;
327276707Sdes
328276707Sdes	SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
329276707Sdes	if ((r = sshbuf_check_reserve(buf, len)) != 0)
330276707Sdes		return r;
331276707Sdes	/*
332276707Sdes	 * If the requested allocation appended would push us past max_size
333276707Sdes	 * then pack the buffer, zeroing buf->off.
334276707Sdes	 */
335276707Sdes	sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
336276707Sdes	SSHBUF_TELL("reserve");
337276707Sdes	if (len + buf->size > buf->alloc) {
338276707Sdes		/*
339276707Sdes		 * Prefer to alloc in SSHBUF_SIZE_INC units, but
340276707Sdes		 * allocate less if doing so would overflow max_size.
341276707Sdes		 */
342276707Sdes		need = len + buf->size - buf->alloc;
343276707Sdes		rlen = roundup(buf->alloc + need, SSHBUF_SIZE_INC);
344276707Sdes		SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
345276707Sdes		if (rlen > buf->max_size)
346276707Sdes			rlen = buf->alloc + need;
347276707Sdes		SSHBUF_DBG(("adjusted rlen %zu", rlen));
348276707Sdes		if ((dp = realloc(buf->d, rlen)) == NULL) {
349276707Sdes			SSHBUF_DBG(("realloc fail"));
350276707Sdes			if (dpp != NULL)
351276707Sdes				*dpp = NULL;
352276707Sdes			return SSH_ERR_ALLOC_FAIL;
353276707Sdes		}
354276707Sdes		buf->alloc = rlen;
355276707Sdes		buf->cd = buf->d = dp;
356276707Sdes		if ((r = sshbuf_check_reserve(buf, len)) < 0) {
357276707Sdes			/* shouldn't fail */
358276707Sdes			if (dpp != NULL)
359276707Sdes				*dpp = NULL;
360276707Sdes			return r;
361276707Sdes		}
362276707Sdes	}
363276707Sdes	dp = buf->d + buf->size;
364276707Sdes	buf->size += len;
365276707Sdes	SSHBUF_TELL("done");
366276707Sdes	if (dpp != NULL)
367276707Sdes		*dpp = dp;
368276707Sdes	return 0;
369276707Sdes}
370276707Sdes
371276707Sdesint
372276707Sdessshbuf_consume(struct sshbuf *buf, size_t len)
373276707Sdes{
374276707Sdes	int r;
375276707Sdes
376276707Sdes	SSHBUF_DBG(("len = %zu", len));
377276707Sdes	if ((r = sshbuf_check_sanity(buf)) != 0)
378276707Sdes		return r;
379276707Sdes	if (len == 0)
380276707Sdes		return 0;
381276707Sdes	if (len > sshbuf_len(buf))
382276707Sdes		return SSH_ERR_MESSAGE_INCOMPLETE;
383276707Sdes	buf->off += len;
384276707Sdes	SSHBUF_TELL("done");
385276707Sdes	return 0;
386276707Sdes}
387276707Sdes
388276707Sdesint
389276707Sdessshbuf_consume_end(struct sshbuf *buf, size_t len)
390276707Sdes{
391276707Sdes	int r;
392276707Sdes
393276707Sdes	SSHBUF_DBG(("len = %zu", len));
394276707Sdes	if ((r = sshbuf_check_sanity(buf)) != 0)
395276707Sdes		return r;
396276707Sdes	if (len == 0)
397276707Sdes		return 0;
398276707Sdes	if (len > sshbuf_len(buf))
399276707Sdes		return SSH_ERR_MESSAGE_INCOMPLETE;
400276707Sdes	buf->size -= len;
401276707Sdes	SSHBUF_TELL("done");
402276707Sdes	return 0;
403276707Sdes}
404276707Sdes
405