Coverage Report

Created: 2023-06-07 07:08

/src/openssh/sshbuf.c
Line
Count
Source (jump to first uncovered line)
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
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
92.4k
{
57
92.4k
  SSHBUF_TELL("sanity");
58
92.4k
  if (__predict_false(buf == NULL ||
59
92.4k
      (!buf->readonly && buf->d != buf->cd) ||
60
92.4k
      buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
61
92.4k
      buf->cd == NULL ||
62
92.4k
      buf->max_size > SSHBUF_SIZE_MAX ||
63
92.4k
      buf->alloc > buf->max_size ||
64
92.4k
      buf->size > buf->alloc ||
65
92.4k
      buf->off > buf->size)) {
66
    /* Do not try to recover from corrupted buffer internals */
67
0
    SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
68
0
    ssh_signal(SIGSEGV, SIG_DFL);
69
0
    raise(SIGSEGV);
70
0
    return SSH_ERR_INTERNAL_ERROR;
71
0
  }
72
92.4k
  return 0;
73
92.4k
}
74
75
static void
76
sshbuf_maybe_pack(struct sshbuf *buf, int force)
77
0
{
78
0
  SSHBUF_DBG(("force %d", force));
79
0
  SSHBUF_TELL("pre-pack");
80
0
  if (buf->off == 0 || buf->readonly || buf->refcount > 1)
81
0
    return;
82
0
  if (force ||
83
0
      (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
84
0
    memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
85
0
    buf->size -= buf->off;
86
0
    buf->off = 0;
87
0
    SSHBUF_TELL("packed");
88
0
  }
89
0
}
90
91
struct sshbuf *
92
sshbuf_new(void)
93
0
{
94
0
  struct sshbuf *ret;
95
96
0
  if ((ret = calloc(sizeof(*ret), 1)) == NULL)
97
0
    return NULL;
98
0
  ret->alloc = SSHBUF_SIZE_INIT;
99
0
  ret->max_size = SSHBUF_SIZE_MAX;
100
0
  ret->readonly = 0;
101
0
  ret->refcount = 1;
102
0
  ret->parent = NULL;
103
0
  if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
104
0
    free(ret);
105
0
    return NULL;
106
0
  }
107
0
  return ret;
108
0
}
109
110
struct sshbuf *
111
sshbuf_from(const void *blob, size_t len)
112
9.09k
{
113
9.09k
  struct sshbuf *ret;
114
115
9.09k
  if (blob == NULL || len > SSHBUF_SIZE_MAX ||
116
9.09k
      (ret = calloc(sizeof(*ret), 1)) == NULL)
117
0
    return NULL;
118
9.09k
  ret->alloc = ret->size = ret->max_size = len;
119
9.09k
  ret->readonly = 1;
120
9.09k
  ret->refcount = 1;
121
9.09k
  ret->parent = NULL;
122
9.09k
  ret->cd = blob;
123
9.09k
  ret->d = NULL;
124
9.09k
  return ret;
125
9.09k
}
126
127
int
128
sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
129
2.27k
{
130
2.27k
  int r;
131
132
2.27k
  if ((r = sshbuf_check_sanity(child)) != 0 ||
133
2.27k
      (r = sshbuf_check_sanity(parent)) != 0)
134
0
    return r;
135
2.27k
  if (child->parent != NULL && child->parent != parent)
136
0
    return SSH_ERR_INTERNAL_ERROR;
137
2.27k
  child->parent = parent;
138
2.27k
  child->parent->refcount++;
139
2.27k
  return 0;
140
2.27k
}
141
142
struct sshbuf *
143
sshbuf_fromb(struct sshbuf *buf)
144
0
{
145
0
  struct sshbuf *ret;
146
147
0
  if (sshbuf_check_sanity(buf) != 0)
148
0
    return NULL;
149
0
  if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
150
0
    return NULL;
151
0
  if (sshbuf_set_parent(ret, buf) != 0) {
152
0
    sshbuf_free(ret);
153
0
    return NULL;
154
0
  }
155
0
  return ret;
156
0
}
157
158
void
159
sshbuf_free(struct sshbuf *buf)
160
19.3k
{
161
19.3k
  if (buf == NULL)
162
7.96k
    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
11.3k
  if (sshbuf_check_sanity(buf) != 0)
170
0
    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
11.3k
  buf->refcount--;
178
11.3k
  if (buf->refcount > 0)
179
2.27k
    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
9.09k
  sshbuf_free(buf->parent);
186
9.09k
  buf->parent = NULL;
187
188
9.09k
  if (!buf->readonly) {
189
0
    explicit_bzero(buf->d, buf->alloc);
190
0
    free(buf->d);
191
0
  }
192
9.09k
  freezero(buf, sizeof(*buf));
193
9.09k
}
194
195
void
196
sshbuf_reset(struct sshbuf *buf)
197
0
{
198
0
  u_char *d;
199
200
0
  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
0
  if (sshbuf_check_sanity(buf) != 0)
206
0
    return;
207
0
  buf->off = buf->size = 0;
208
0
  if (buf->alloc != SSHBUF_SIZE_INIT) {
209
0
    if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
210
0
        1)) != NULL) {
211
0
      buf->cd = buf->d = d;
212
0
      buf->alloc = SSHBUF_SIZE_INIT;
213
0
    }
214
0
  }
215
0
  explicit_bzero(buf->d, buf->alloc);
216
0
}
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
47.2k
{
283
47.2k
  if (sshbuf_check_sanity(buf) != 0)
284
0
    return 0;
285
47.2k
  return buf->size - buf->off;
286
47.2k
}
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
18.9k
{
299
18.9k
  if (sshbuf_check_sanity(buf) != 0)
300
0
    return NULL;
301
18.9k
  return buf->cd + buf->off;
302
18.9k
}
303
304
u_char *
305
sshbuf_mutable_ptr(const struct sshbuf *buf)
306
0
{
307
0
  if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
308
0
    return NULL;
309
0
  return buf->d + buf->off;
310
0
}
311
312
int
313
sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
314
0
{
315
0
  int r;
316
317
0
  if ((r = sshbuf_check_sanity(buf)) != 0)
318
0
    return r;
319
0
  if (buf->readonly || buf->refcount > 1)
320
0
    return SSH_ERR_BUFFER_READ_ONLY;
321
0
  SSHBUF_TELL("check");
322
  /* Check that len is reasonable and that max_size + available < len */
323
0
  if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
324
0
    return SSH_ERR_NO_BUFFER_SPACE;
325
0
  return 0;
326
0
}
327
328
int
329
sshbuf_allocate(struct sshbuf *buf, size_t len)
330
0
{
331
0
  size_t rlen, need;
332
0
  u_char *dp;
333
0
  int r;
334
335
0
  SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
336
0
  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
0
  sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
343
0
  SSHBUF_TELL("allocate");
344
0
  if (len + buf->size <= buf->alloc)
345
0
    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
0
  need = len + buf->size - buf->alloc;
352
0
  rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
353
0
  SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
354
0
  if (rlen > buf->max_size)
355
0
    rlen = buf->alloc + need;
356
0
  SSHBUF_DBG(("adjusted rlen %zu", rlen));
357
0
  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
0
  buf->alloc = rlen;
362
0
  buf->cd = buf->d = dp;
363
0
  if ((r = sshbuf_check_reserve(buf, len)) < 0) {
364
    /* shouldn't fail */
365
0
    return r;
366
0
  }
367
0
  SSHBUF_TELL("done");
368
0
  return 0;
369
0
}
370
371
int
372
sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
373
0
{
374
0
  u_char *dp;
375
0
  int r;
376
377
0
  if (dpp != NULL)
378
0
    *dpp = NULL;
379
380
0
  SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
381
0
  if ((r = sshbuf_allocate(buf, len)) != 0)
382
0
    return r;
383
384
0
  dp = buf->d + buf->size;
385
0
  buf->size += len;
386
0
  if (dpp != NULL)
387
0
    *dpp = dp;
388
0
  return 0;
389
0
}
390
391
int
392
sshbuf_consume(struct sshbuf *buf, size_t len)
393
10.3k
{
394
10.3k
  int r;
395
396
10.3k
  SSHBUF_DBG(("len = %zu", len));
397
10.3k
  if ((r = sshbuf_check_sanity(buf)) != 0)
398
0
    return r;
399
10.3k
  if (len == 0)
400
0
    return 0;
401
10.3k
  if (len > sshbuf_len(buf))
402
0
    return SSH_ERR_MESSAGE_INCOMPLETE;
403
10.3k
  buf->off += len;
404
  /* deal with empty buffer */
405
10.3k
  if (buf->off == buf->size)
406
4.38k
    buf->off = buf->size = 0;
407
10.3k
  SSHBUF_TELL("done");
408
10.3k
  return 0;
409
10.3k
}
410
411
int
412
sshbuf_consume_end(struct sshbuf *buf, size_t len)
413
0
{
414
0
  int r;
415
416
0
  SSHBUF_DBG(("len = %zu", len));
417
0
  if ((r = sshbuf_check_sanity(buf)) != 0)
418
0
    return r;
419
0
  if (len == 0)
420
0
    return 0;
421
0
  if (len > sshbuf_len(buf))
422
0
    return SSH_ERR_MESSAGE_INCOMPLETE;
423
0
  buf->size -= len;
424
0
  SSHBUF_TELL("done");
425
0
  return 0;
426
0
}
427