Line | Count | Source (jump to first uncovered line) |
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 "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 | 391k | { |
57 | 391k | SSHBUF_TELL("sanity"); |
58 | 391k | if (__predict_false(buf == NULL || |
59 | 391k | (!buf->readonly && buf->d != buf->cd) || |
60 | 391k | buf->parent == buf || |
61 | 391k | buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX || |
62 | 391k | buf->cd == NULL || |
63 | 391k | buf->max_size > SSHBUF_SIZE_MAX || |
64 | 391k | buf->alloc > buf->max_size || |
65 | 391k | buf->size > buf->alloc || |
66 | 391k | buf->off > buf->size)) { |
67 | | /* Do not try to recover from corrupted buffer internals */ |
68 | 0 | SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR")); |
69 | 0 | ssh_signal(SIGSEGV, SIG_DFL); |
70 | 0 | raise(SIGSEGV); |
71 | 0 | return SSH_ERR_INTERNAL_ERROR; |
72 | 0 | } |
73 | 391k | return 0; |
74 | 391k | } |
75 | | |
76 | | static void |
77 | | sshbuf_maybe_pack(struct sshbuf *buf, int force) |
78 | 5.79k | { |
79 | 5.79k | SSHBUF_DBG(("force %d", force)); |
80 | 5.79k | SSHBUF_TELL("pre-pack"); |
81 | 5.79k | if (buf->off == 0 || buf->readonly || buf->refcount > 1) |
82 | 5.79k | return; |
83 | 0 | if (force || |
84 | 0 | (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) { |
85 | 0 | memmove(buf->d, buf->d + buf->off, buf->size - buf->off); |
86 | 0 | buf->size -= buf->off; |
87 | 0 | buf->off = 0; |
88 | 0 | SSHBUF_TELL("packed"); |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | | struct sshbuf * |
93 | | sshbuf_new(void) |
94 | 5.77k | { |
95 | 5.77k | struct sshbuf *ret; |
96 | | |
97 | 5.77k | if ((ret = calloc(1, sizeof(*ret))) == NULL) |
98 | 0 | return NULL; |
99 | 5.77k | ret->alloc = SSHBUF_SIZE_INIT; |
100 | 5.77k | ret->max_size = SSHBUF_SIZE_MAX; |
101 | 5.77k | ret->readonly = 0; |
102 | 5.77k | ret->refcount = 1; |
103 | 5.77k | ret->parent = NULL; |
104 | 5.77k | if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) { |
105 | 0 | free(ret); |
106 | 0 | return NULL; |
107 | 0 | } |
108 | 5.77k | return ret; |
109 | 5.77k | } |
110 | | |
111 | | struct sshbuf * |
112 | | sshbuf_from(const void *blob, size_t len) |
113 | 17.4k | { |
114 | 17.4k | struct sshbuf *ret; |
115 | | |
116 | 17.4k | if (blob == NULL || len > SSHBUF_SIZE_MAX || |
117 | 17.4k | (ret = calloc(1, sizeof(*ret))) == NULL) |
118 | 0 | return NULL; |
119 | 17.4k | ret->alloc = ret->size = ret->max_size = len; |
120 | 17.4k | ret->readonly = 1; |
121 | 17.4k | ret->refcount = 1; |
122 | 17.4k | ret->parent = NULL; |
123 | 17.4k | ret->cd = blob; |
124 | 17.4k | ret->d = NULL; |
125 | 17.4k | return ret; |
126 | 17.4k | } |
127 | | |
128 | | int |
129 | | sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent) |
130 | 12.0k | { |
131 | 12.0k | int r; |
132 | | |
133 | 12.0k | if ((r = sshbuf_check_sanity(child)) != 0 || |
134 | 12.0k | (r = sshbuf_check_sanity(parent)) != 0) |
135 | 0 | return r; |
136 | 12.0k | if ((child->parent != NULL && child->parent != parent) || |
137 | 12.0k | child == parent) |
138 | 0 | return SSH_ERR_INTERNAL_ERROR; |
139 | 12.0k | child->parent = parent; |
140 | 12.0k | child->parent->refcount++; |
141 | 12.0k | return 0; |
142 | 12.0k | } |
143 | | |
144 | | struct sshbuf * |
145 | | sshbuf_fromb(struct sshbuf *buf) |
146 | 3.37k | { |
147 | 3.37k | struct sshbuf *ret; |
148 | | |
149 | 3.37k | if (sshbuf_check_sanity(buf) != 0) |
150 | 0 | return NULL; |
151 | 3.37k | if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL) |
152 | 0 | return NULL; |
153 | 3.37k | if (sshbuf_set_parent(ret, buf) != 0) { |
154 | 0 | sshbuf_free(ret); |
155 | 0 | return NULL; |
156 | 0 | } |
157 | 3.37k | return ret; |
158 | 3.37k | } |
159 | | |
160 | | void |
161 | | sshbuf_free(struct sshbuf *buf) |
162 | 49.3k | { |
163 | 49.3k | if (buf == NULL) |
164 | 14.0k | return; |
165 | | /* |
166 | | * The following will leak on insane buffers, but this is the safest |
167 | | * course of action - an invalid pointer or already-freed pointer may |
168 | | * have been passed to us and continuing to scribble over memory would |
169 | | * be bad. |
170 | | */ |
171 | 35.3k | if (sshbuf_check_sanity(buf) != 0) |
172 | 0 | return; |
173 | | |
174 | | /* |
175 | | * If we are a parent with still-extant children, then don't free just |
176 | | * yet. The last child's call to sshbuf_free should decrement our |
177 | | * refcount to 0 and trigger the actual free. |
178 | | */ |
179 | 35.3k | buf->refcount--; |
180 | 35.3k | if (buf->refcount > 0) |
181 | 12.0k | return; |
182 | | |
183 | | /* |
184 | | * If we are a child, then free our parent to decrement its reference |
185 | | * count and possibly free it. |
186 | | */ |
187 | 23.2k | sshbuf_free(buf->parent); |
188 | 23.2k | buf->parent = NULL; |
189 | | |
190 | 23.2k | if (!buf->readonly) |
191 | 5.77k | freezero(buf->d, buf->alloc); |
192 | 23.2k | freezero(buf, sizeof(*buf)); |
193 | 23.2k | } |
194 | | |
195 | | void |
196 | | sshbuf_reset(struct sshbuf *buf) |
197 | 82 | { |
198 | 82 | u_char *d; |
199 | | |
200 | 82 | if (buf->readonly || buf->refcount > 1) { |
201 | | /* Nonsensical. Just make buffer appear empty */ |
202 | 0 | buf->off = buf->size; |
203 | 0 | return; |
204 | 0 | } |
205 | 82 | if (sshbuf_check_sanity(buf) != 0) |
206 | 0 | return; |
207 | 82 | buf->off = buf->size = 0; |
208 | 82 | if (buf->alloc != SSHBUF_SIZE_INIT) { |
209 | 60 | if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT, |
210 | 60 | 1)) != NULL) { |
211 | 60 | buf->cd = buf->d = d; |
212 | 60 | buf->alloc = SSHBUF_SIZE_INIT; |
213 | 60 | } |
214 | 60 | } |
215 | 82 | explicit_bzero(buf->d, buf->alloc); |
216 | 82 | } |
217 | | |
218 | | size_t |
219 | | sshbuf_max_size(const struct sshbuf *buf) |
220 | 0 | { |
221 | 0 | return buf->max_size; |
222 | 0 | } |
223 | | |
224 | | size_t |
225 | | sshbuf_alloc(const struct sshbuf *buf) |
226 | 0 | { |
227 | 0 | return buf->alloc; |
228 | 0 | } |
229 | | |
230 | | const struct sshbuf * |
231 | | sshbuf_parent(const struct sshbuf *buf) |
232 | 0 | { |
233 | 0 | return buf->parent; |
234 | 0 | } |
235 | | |
236 | | u_int |
237 | | sshbuf_refcount(const struct sshbuf *buf) |
238 | 0 | { |
239 | 0 | return buf->refcount; |
240 | 0 | } |
241 | | |
242 | | int |
243 | | sshbuf_set_max_size(struct sshbuf *buf, size_t max_size) |
244 | 0 | { |
245 | 0 | size_t rlen; |
246 | 0 | u_char *dp; |
247 | 0 | int r; |
248 | |
|
249 | 0 | SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size)); |
250 | 0 | if ((r = sshbuf_check_sanity(buf)) != 0) |
251 | 0 | return r; |
252 | 0 | if (max_size == buf->max_size) |
253 | 0 | return 0; |
254 | 0 | if (buf->readonly || buf->refcount > 1) |
255 | 0 | return SSH_ERR_BUFFER_READ_ONLY; |
256 | 0 | if (max_size > SSHBUF_SIZE_MAX) |
257 | 0 | return SSH_ERR_NO_BUFFER_SPACE; |
258 | | /* pack and realloc if necessary */ |
259 | 0 | sshbuf_maybe_pack(buf, max_size < buf->size); |
260 | 0 | if (max_size < buf->alloc && max_size > buf->size) { |
261 | 0 | if (buf->size < SSHBUF_SIZE_INIT) |
262 | 0 | rlen = SSHBUF_SIZE_INIT; |
263 | 0 | else |
264 | 0 | rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC); |
265 | 0 | if (rlen > max_size) |
266 | 0 | rlen = max_size; |
267 | 0 | SSHBUF_DBG(("new alloc = %zu", rlen)); |
268 | 0 | if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) |
269 | 0 | return SSH_ERR_ALLOC_FAIL; |
270 | 0 | buf->cd = buf->d = dp; |
271 | 0 | buf->alloc = rlen; |
272 | 0 | } |
273 | 0 | SSHBUF_TELL("new-max"); |
274 | 0 | if (max_size < buf->alloc) |
275 | 0 | return SSH_ERR_NO_BUFFER_SPACE; |
276 | 0 | buf->max_size = max_size; |
277 | 0 | return 0; |
278 | 0 | } |
279 | | |
280 | | size_t |
281 | | sshbuf_len(const struct sshbuf *buf) |
282 | 190k | { |
283 | 190k | if (sshbuf_check_sanity(buf) != 0) |
284 | 0 | return 0; |
285 | 190k | return buf->size - buf->off; |
286 | 190k | } |
287 | | |
288 | | size_t |
289 | | sshbuf_avail(const struct sshbuf *buf) |
290 | 0 | { |
291 | 0 | if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1) |
292 | 0 | return 0; |
293 | 0 | return buf->max_size - (buf->size - buf->off); |
294 | 0 | } |
295 | | |
296 | | const u_char * |
297 | | sshbuf_ptr(const struct sshbuf *buf) |
298 | 78.6k | { |
299 | 78.6k | if (sshbuf_check_sanity(buf) != 0) |
300 | 0 | return NULL; |
301 | 78.6k | return buf->cd + buf->off; |
302 | 78.6k | } |
303 | | |
304 | | u_char * |
305 | | sshbuf_mutable_ptr(const struct sshbuf *buf) |
306 | 63 | { |
307 | 63 | if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1) |
308 | 0 | return NULL; |
309 | 63 | return buf->d + buf->off; |
310 | 63 | } |
311 | | |
312 | | int |
313 | | sshbuf_check_reserve(const struct sshbuf *buf, size_t len) |
314 | 7.19k | { |
315 | 7.19k | int r; |
316 | | |
317 | 7.19k | if ((r = sshbuf_check_sanity(buf)) != 0) |
318 | 0 | return r; |
319 | 7.19k | if (buf->readonly || buf->refcount > 1) |
320 | 0 | return SSH_ERR_BUFFER_READ_ONLY; |
321 | 7.19k | SSHBUF_TELL("check"); |
322 | | /* Check that len is reasonable and that max_size + available < len */ |
323 | 7.19k | if (len > buf->max_size || buf->max_size - len < buf->size - buf->off) |
324 | 0 | return SSH_ERR_NO_BUFFER_SPACE; |
325 | 7.19k | return 0; |
326 | 7.19k | } |
327 | | |
328 | | int |
329 | | sshbuf_allocate(struct sshbuf *buf, size_t len) |
330 | 5.79k | { |
331 | 5.79k | size_t rlen, need; |
332 | 5.79k | u_char *dp; |
333 | 5.79k | int r; |
334 | | |
335 | 5.79k | SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len)); |
336 | 5.79k | if ((r = sshbuf_check_reserve(buf, len)) != 0) |
337 | 0 | 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 | 5.79k | sshbuf_maybe_pack(buf, buf->size + len > buf->max_size); |
343 | 5.79k | SSHBUF_TELL("allocate"); |
344 | 5.79k | if (len + buf->size <= buf->alloc) |
345 | 4.38k | 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 | 1.40k | need = len + buf->size - buf->alloc; |
352 | 1.40k | rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC); |
353 | 1.40k | SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen)); |
354 | 1.40k | if (rlen > buf->max_size) |
355 | 0 | rlen = buf->alloc + need; |
356 | 1.40k | SSHBUF_DBG(("adjusted rlen %zu", rlen)); |
357 | 1.40k | if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) { |
358 | 0 | SSHBUF_DBG(("realloc fail")); |
359 | 0 | return SSH_ERR_ALLOC_FAIL; |
360 | 0 | } |
361 | 1.40k | buf->alloc = rlen; |
362 | 1.40k | buf->cd = buf->d = dp; |
363 | 1.40k | if ((r = sshbuf_check_reserve(buf, len)) < 0) { |
364 | | /* shouldn't fail */ |
365 | 0 | return r; |
366 | 0 | } |
367 | 1.40k | SSHBUF_TELL("done"); |
368 | 1.40k | return 0; |
369 | 1.40k | } |
370 | | |
371 | | int |
372 | | sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp) |
373 | 5.79k | { |
374 | 5.79k | u_char *dp; |
375 | 5.79k | int r; |
376 | | |
377 | 5.79k | if (dpp != NULL) |
378 | 5.79k | *dpp = NULL; |
379 | | |
380 | 5.79k | SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len)); |
381 | 5.79k | if ((r = sshbuf_allocate(buf, len)) != 0) |
382 | 0 | return r; |
383 | | |
384 | 5.79k | dp = buf->d + buf->size; |
385 | 5.79k | buf->size += len; |
386 | 5.79k | if (dpp != NULL) |
387 | 5.79k | *dpp = dp; |
388 | 5.79k | return 0; |
389 | 5.79k | } |
390 | | |
391 | | int |
392 | | sshbuf_consume(struct sshbuf *buf, size_t len) |
393 | 51.6k | { |
394 | 51.6k | int r; |
395 | | |
396 | 51.6k | SSHBUF_DBG(("len = %zu", len)); |
397 | 51.6k | if ((r = sshbuf_check_sanity(buf)) != 0) |
398 | 0 | return r; |
399 | 51.6k | if (len == 0) |
400 | 54 | return 0; |
401 | 51.6k | if (len > sshbuf_len(buf)) |
402 | 113 | return SSH_ERR_MESSAGE_INCOMPLETE; |
403 | 51.5k | buf->off += len; |
404 | | /* deal with empty buffer */ |
405 | 51.5k | if (buf->off == buf->size) |
406 | 6.39k | buf->off = buf->size = 0; |
407 | 51.5k | SSHBUF_TELL("done"); |
408 | 51.5k | return 0; |
409 | 51.6k | } |
410 | | |
411 | | int |
412 | | sshbuf_consume_end(struct sshbuf *buf, size_t len) |
413 | 81 | { |
414 | 81 | int r; |
415 | | |
416 | 81 | SSHBUF_DBG(("len = %zu", len)); |
417 | 81 | if ((r = sshbuf_check_sanity(buf)) != 0) |
418 | 0 | return r; |
419 | 81 | if (len == 0) |
420 | 0 | return 0; |
421 | 81 | if (len > sshbuf_len(buf)) |
422 | 0 | return SSH_ERR_MESSAGE_INCOMPLETE; |
423 | 81 | buf->size -= len; |
424 | 81 | SSHBUF_TELL("done"); |
425 | 81 | return 0; |
426 | 81 | } |
427 | | |