Coverage Report

Created: 2024-06-18 06:23

/src/hpn-ssh/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
/* #include "log.h" */
31
32
834k
#define BUF_WATERSHED 256*1024
33
34
#ifdef SSHBUF_DEBUG
35
# define SSHBUF_TELL(what) do { \
36
    printf("%s:%d %s: %s size %zu alloc %zu off %zu max %zu\n", \
37
        __FILE__, __LINE__, __func__, what, \
38
        buf->size, buf->alloc, buf->off, buf->max_size); \
39
    fflush(stdout); \
40
  } while (0)
41
#else
42
# define SSHBUF_TELL(what)
43
#endif
44
45
struct sshbuf {
46
  u_char *d;    /* Data */
47
  const u_char *cd; /* Const data */
48
  size_t off;   /* First available byte is buf->d + buf->off */
49
  size_t size;    /* Last byte is buf->d + buf->size - 1 */
50
  size_t max_size;  /* Maximum size of buffer */
51
  size_t alloc;   /* Total bytes allocated to buf->d */
52
  int readonly;   /* Refers to external, const data */
53
  u_int refcount;   /* Tracks self and number of child buffers */
54
  struct sshbuf *parent;  /* If child, pointer to parent */
55
  char label[MAX_LABEL_LEN];   /* String for buffer label - debugging use */
56
  int type;               /* type of buffer enum (sshbuf_types)*/
57
};
58
59
/* update the label string for a given sshbuf. Useful
60
 * for debugging */
61
void
62
sshbuf_relabel(struct sshbuf *buf, const char *label)
63
280k
{
64
280k
  if (label != NULL)
65
280k
    strncpy(buf->label, label, MAX_LABEL_LEN-1);
66
280k
}
67
68
/* set the type (from enum sshbuf_type) of the given sshbuf.
69
 * The purpose is to allow different classes of buffers to
70
 * follow different code paths if necessary */
71
void
72
sshbuf_type(struct sshbuf *buf, int type)
73
280k
{
74
280k
  if (type < BUF_MAX_TYPE)
75
280k
    buf->type = type;
76
280k
}
77
78
static inline int
79
sshbuf_check_sanity(const struct sshbuf *buf)
80
135M
{
81
135M
  SSHBUF_TELL("sanity");
82
135M
  if (__predict_false(buf == NULL ||
83
135M
      (!buf->readonly && buf->d != buf->cd) ||
84
135M
      buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
85
135M
      buf->cd == NULL ||
86
135M
      buf->max_size > SSHBUF_SIZE_MAX ||
87
135M
      buf->alloc > buf->max_size ||
88
135M
      buf->size > buf->alloc ||
89
135M
      buf->off > buf->size)) {
90
    /* Do not try to recover from corrupted buffer internals */
91
0
    SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
92
0
    ssh_signal(SIGSEGV, SIG_DFL);
93
0
    raise(SIGSEGV);
94
0
    return SSH_ERR_INTERNAL_ERROR;
95
0
  }
96
135M
  return 0;
97
135M
}
98
99
static void
100
sshbuf_maybe_pack(struct sshbuf *buf, int force)
101
40.8M
{
102
40.8M
  SSHBUF_DBG(("force %d", force));
103
40.8M
  SSHBUF_TELL("pre-pack");
104
40.8M
  if (buf->off == 0 || buf->readonly || buf->refcount > 1)
105
40.8M
    return;
106
0
  if (force ||
107
0
      (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
108
0
    memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
109
0
    buf->size -= buf->off;
110
0
    buf->off = 0;
111
0
    SSHBUF_TELL("packed");
112
0
  }
113
0
}
114
115
struct sshbuf *
116
sshbuf_new_label (const char *label)
117
632k
{
118
632k
  struct sshbuf *ret;
119
120
632k
  if ((ret = calloc(sizeof(*ret), 1)) == NULL)
121
0
    return NULL;
122
632k
  ret->alloc = SSHBUF_SIZE_INIT;
123
632k
  ret->max_size = SSHBUF_SIZE_MAX;
124
632k
  ret->readonly = 0;
125
632k
  ret->refcount = 1;
126
632k
  ret->parent = NULL;
127
632k
  if (label != NULL)
128
632k
    strncpy(ret->label, label, MAX_LABEL_LEN-1);
129
632k
  if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
130
0
    free(ret);
131
0
    return NULL;
132
0
  }
133
632k
  return ret;
134
632k
}
135
136
struct sshbuf *
137
sshbuf_from(const void *blob, size_t len)
138
62.3k
{
139
62.3k
  struct sshbuf *ret;
140
141
62.3k
  if (blob == NULL || len > SSHBUF_SIZE_MAX ||
142
62.3k
      (ret = calloc(sizeof(*ret), 1)) == NULL)
143
0
    return NULL;
144
62.3k
  ret->alloc = ret->size = ret->max_size = len;
145
62.3k
  ret->readonly = 1;
146
62.3k
  ret->refcount = 1;
147
62.3k
  ret->parent = NULL;
148
62.3k
  ret->cd = blob;
149
62.3k
  ret->d = NULL;
150
62.3k
  return ret;
151
62.3k
}
152
153
int
154
sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
155
62.3k
{
156
62.3k
  int r;
157
158
62.3k
  if ((r = sshbuf_check_sanity(child)) != 0 ||
159
62.3k
      (r = sshbuf_check_sanity(parent)) != 0)
160
0
    return r;
161
62.3k
  if (child->parent != NULL && child->parent != parent)
162
0
    return SSH_ERR_INTERNAL_ERROR;
163
62.3k
  child->parent = parent;
164
62.3k
  child->parent->refcount++;
165
62.3k
  return 0;
166
62.3k
}
167
168
struct sshbuf *
169
sshbuf_fromb(struct sshbuf *buf)
170
62.3k
{
171
62.3k
  struct sshbuf *ret;
172
173
62.3k
  if (sshbuf_check_sanity(buf) != 0)
174
0
    return NULL;
175
62.3k
  if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
176
0
    return NULL;
177
62.3k
  if (sshbuf_set_parent(ret, buf) != 0) {
178
0
    sshbuf_free(ret);
179
0
    return NULL;
180
0
  }
181
62.3k
  return ret;
182
62.3k
}
183
184
void
185
sshbuf_free(struct sshbuf *buf)
186
1.59M
{
187
1.59M
  if (buf == NULL)
188
842k
    return;
189
  /*
190
   * The following will leak on insane buffers, but this is the safest
191
   * course of action - an invalid pointer or already-freed pointer may
192
   * have been passed to us and continuing to scribble over memory would
193
   * be bad.
194
   */
195
757k
  if (sshbuf_check_sanity(buf) != 0)
196
0
    return;
197
198
  /*
199
   * If we are a parent with still-extant children, then don't free just
200
   * yet. The last child's call to sshbuf_free should decrement our
201
   * refcount to 0 and trigger the actual free.
202
   */
203
757k
  buf->refcount--;
204
757k
  if (buf->refcount > 0)
205
62.3k
    return;
206
207
  /*
208
   * If we are a child, the free our parent to decrement its reference
209
   * count and possibly free it.
210
   */
211
694k
  sshbuf_free(buf->parent);
212
694k
  buf->parent = NULL;
213
214
694k
  if (!buf->readonly) {
215
632k
    explicit_bzero(buf->d, buf->alloc);
216
632k
    free(buf->d);
217
632k
  }
218
694k
  freezero(buf, sizeof(*buf));
219
694k
}
220
221
void
222
sshbuf_reset(struct sshbuf *buf)
223
2.52M
{
224
2.52M
  u_char *d;
225
226
2.52M
  if (buf->readonly || buf->refcount > 1) {
227
    /* Nonsensical. Just make buffer appear empty */
228
35.0k
    buf->off = buf->size;
229
35.0k
    return;
230
35.0k
  }
231
2.48M
  if (sshbuf_check_sanity(buf) != 0)
232
0
    return;
233
2.48M
  buf->off = buf->size = 0;
234
2.48M
  if (buf->alloc != SSHBUF_SIZE_INIT) {
235
78.3k
    if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
236
78.3k
        1)) != NULL) {
237
78.3k
      buf->cd = buf->d = d;
238
78.3k
      buf->alloc = SSHBUF_SIZE_INIT;
239
78.3k
    }
240
78.3k
  }
241
2.48M
  explicit_bzero(buf->d, buf->alloc);
242
2.48M
}
243
244
size_t
245
sshbuf_max_size(const struct sshbuf *buf)
246
0
{
247
0
  return buf->max_size;
248
0
}
249
250
size_t
251
sshbuf_alloc(const struct sshbuf *buf)
252
0
{
253
0
  return buf->alloc;
254
0
}
255
256
const struct sshbuf *
257
sshbuf_parent(const struct sshbuf *buf)
258
0
{
259
0
  return buf->parent;
260
0
}
261
262
u_int
263
sshbuf_refcount(const struct sshbuf *buf)
264
0
{
265
0
  return buf->refcount;
266
0
}
267
268
int
269
sshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
270
0
{
271
0
  size_t rlen;
272
0
  u_char *dp;
273
0
  int r;
274
275
0
  SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
276
0
  if ((r = sshbuf_check_sanity(buf)) != 0)
277
0
    return r;
278
0
  if (max_size == buf->max_size)
279
0
    return 0;
280
0
  if (buf->readonly || buf->refcount > 1)
281
0
    return SSH_ERR_BUFFER_READ_ONLY;
282
0
  if (max_size > SSHBUF_SIZE_MAX)
283
0
    return SSH_ERR_NO_BUFFER_SPACE;
284
  /* pack and realloc if necessary */
285
0
  sshbuf_maybe_pack(buf, max_size < buf->size);
286
0
  if (max_size < buf->alloc && max_size > buf->size) {
287
0
    if (buf->size < SSHBUF_SIZE_INIT)
288
0
      rlen = SSHBUF_SIZE_INIT;
289
0
    else
290
0
      rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC);
291
0
    if (rlen > max_size)
292
0
      rlen = max_size;
293
0
    SSHBUF_DBG(("new alloc = %zu", rlen));
294
0
    if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL)
295
0
      return SSH_ERR_ALLOC_FAIL;
296
0
    buf->cd = buf->d = dp;
297
0
    buf->alloc = rlen;
298
0
  }
299
0
  SSHBUF_TELL("new-max");
300
0
  if (max_size < buf->alloc)
301
0
    return SSH_ERR_NO_BUFFER_SPACE;
302
0
  buf->max_size = max_size;
303
0
  return 0;
304
0
}
305
306
size_t
307
sshbuf_len(const struct sshbuf *buf)
308
83.8M
{
309
83.8M
  if (sshbuf_check_sanity(buf) != 0)
310
0
    return 0;
311
83.8M
  return buf->size - buf->off;
312
83.8M
}
313
314
size_t
315
sshbuf_avail(const struct sshbuf *buf)
316
0
{
317
0
  if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
318
0
    return 0;
319
  /* we need to reserve a small amount of overhead on the input buffer
320
   * or we can enter into a pathological state during bulk
321
   * data transfers. We use a fraction of the max size as we want it to scale
322
   * with the size of the input buffer. If we do it for all of the buffers
323
   * we fail the regression unit tests. This seems like a reasonable
324
   * solution. Of course, I still need to figure out *why* this is
325
   * happening and come up with an actual fix. TODO
326
   * cjr 4/19/2024 */
327
0
  if (buf->type == BUF_CHANNEL_INPUT)
328
0
    return buf->max_size / 1.05 - (buf->size - buf->off);
329
0
  else
330
0
    return buf->max_size - (buf->size - buf->off);
331
0
}
332
333
const u_char *
334
sshbuf_ptr(const struct sshbuf *buf)
335
5.44M
{
336
5.44M
  if (sshbuf_check_sanity(buf) != 0)
337
0
    return NULL;
338
5.44M
  return buf->cd + buf->off;
339
5.44M
}
340
341
u_char *
342
sshbuf_mutable_ptr(const struct sshbuf *buf)
343
248k
{
344
248k
  if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
345
0
    return NULL;
346
248k
  return buf->d + buf->off;
347
248k
}
348
349
int
350
sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
351
41.2M
{
352
41.2M
  int r;
353
354
41.2M
  if ((r = sshbuf_check_sanity(buf)) != 0)
355
0
    return r;
356
41.2M
  if (buf->readonly || buf->refcount > 1)
357
0
    return SSH_ERR_BUFFER_READ_ONLY;
358
41.2M
  SSHBUF_TELL("check");
359
  /* Check that len is reasonable and that max_size + available < len */
360
41.2M
  if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
361
0
    return SSH_ERR_NO_BUFFER_SPACE;
362
41.2M
  return 0;
363
41.2M
}
364
365
int
366
sshbuf_allocate(struct sshbuf *buf, size_t len)
367
40.8M
{
368
40.8M
  size_t rlen, need;
369
40.8M
  u_char *dp;
370
40.8M
  int r;
371
372
40.8M
  SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
373
40.8M
  if ((r = sshbuf_check_reserve(buf, len)) != 0)
374
0
    return r;
375
  /*
376
   * If the requested allocation appended would push us past max_size
377
   * then pack the buffer, zeroing buf->off.
378
   */
379
40.8M
  sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
380
40.8M
  SSHBUF_TELL("allocate");
381
40.8M
  if (len + buf->size <= buf->alloc)
382
40.4M
    return 0; /* already have it. */
383
384
  /*
385
   * Prefer to alloc in SSHBUF_SIZE_INC units, but
386
   * allocate less if doing so would overflow max_size.
387
   */
388
417k
  need = len + buf->size - buf->alloc;
389
417k
  rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
390
  /* With the changes in 8.9 the output buffer end up growing pretty
391
   * slowly. It's knows that it needs to grow but it only does so 32K
392
   * at a time. This means a lot of calls to realloc and memcpy which
393
   * kills performance until the buffer reaches some maximum size.
394
   * So we explicitly test for a buffer that's trying to grow and
395
   * if it is then we push the growth by 4MB at a time. This can result in
396
   * the buffer being over allocated (in terms of actual needs) but the
397
   * process is fast. This significantly reduces overhead
398
   * and improves performance. In this case we look for a buffer that is trying
399
   * to grow larger than BUF_WATERSHED (256*1024 taken from PACKET_MAX_SIZE)
400
   * and explcitly check that the buffer is being used for inbound outbound
401
   * channel buffering.
402
   * Updated for 18.4.1 -cjr 04/20/24
403
   */
404
417k
  if (rlen > BUF_WATERSHED && (buf->type == BUF_CHANNEL_OUTPUT || buf->type == BUF_CHANNEL_INPUT)) {
405
    /* debug_f ("Prior: label: %s, %p, rlen is %zu need is %zu max_size is %zu",
406
       buf->label, buf, rlen, need, buf->max_size); */
407
    /* easiest thing to do is grow the nuffer by 4MB each time. It might end
408
     * up being somewhat overallocated but works quickly */
409
94
    need = (4*1024*1024);
410
94
    rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
411
    /* debug_f ("Post: label: %s, %p, rlen is %zu need is %zu max_size is %zu", */
412
    /*   buf->label, buf, rlen, need, buf->max_size); */
413
94
  }
414
417k
  SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
415
416
  /* rlen might be above the max allocation */
417
417k
  if (rlen > buf->max_size)
418
0
    rlen = buf->max_size;
419
420
417k
  SSHBUF_DBG(("adjusted rlen %zu", rlen));
421
417k
  if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) {
422
0
    SSHBUF_DBG(("realloc fail"));
423
0
    return SSH_ERR_ALLOC_FAIL;
424
0
  }
425
417k
  buf->alloc = rlen;
426
417k
  buf->cd = buf->d = dp;
427
417k
  if ((r = sshbuf_check_reserve(buf, len)) < 0) {
428
    /* shouldn't fail */
429
0
    return r;
430
0
  }
431
417k
  SSHBUF_TELL("done");
432
417k
  return 0;
433
417k
}
434
435
int
436
sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
437
40.8M
{
438
40.8M
  u_char *dp;
439
40.8M
  int r;
440
441
40.8M
  if (dpp != NULL)
442
40.8M
    *dpp = NULL;
443
444
40.8M
  SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
445
40.8M
  if ((r = sshbuf_allocate(buf, len)) != 0)
446
0
    return r;
447
448
40.8M
  dp = buf->d + buf->size;
449
40.8M
  buf->size += len;
450
40.8M
  if (dpp != NULL)
451
40.8M
    *dpp = dp;
452
40.8M
  return 0;
453
40.8M
}
454
455
int
456
sshbuf_consume(struct sshbuf *buf, size_t len)
457
1.44M
{
458
1.44M
  int r;
459
460
1.44M
  SSHBUF_DBG(("len = %zu", len));
461
1.44M
  if ((r = sshbuf_check_sanity(buf)) != 0)
462
0
    return r;
463
1.44M
  if (len == 0)
464
0
    return 0;
465
1.44M
  if (len > sshbuf_len(buf))
466
1.12k
    return SSH_ERR_MESSAGE_INCOMPLETE;
467
1.44M
  buf->off += len;
468
  /* deal with empty buffer */
469
1.44M
  if (buf->off == buf->size)
470
264k
    buf->off = buf->size = 0;
471
1.44M
  SSHBUF_TELL("done");
472
1.44M
  return 0;
473
1.44M
}
474
475
int
476
sshbuf_consume_end(struct sshbuf *buf, size_t len)
477
302k
{
478
302k
  int r;
479
480
302k
  SSHBUF_DBG(("len = %zu", len));
481
302k
  if ((r = sshbuf_check_sanity(buf)) != 0)
482
0
    return r;
483
302k
  if (len == 0)
484
0
    return 0;
485
302k
  if (len > sshbuf_len(buf))
486
260
    return SSH_ERR_MESSAGE_INCOMPLETE;
487
301k
  buf->size -= len;
488
301k
  SSHBUF_TELL("done");
489
301k
  return 0;
490
302k
}