Coverage Report

Created: 2025-06-22 06:46

/src/hpn-ssh/sshbuf.c
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
/* #include "log.h" */
31
32
0
#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
0
{
64
0
  if (label != NULL)
65
0
    strncpy(buf->label, label, MAX_LABEL_LEN-1);
66
0
}
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
0
{
74
0
  if (type < BUF_MAX_TYPE)
75
0
    buf->type = type;
76
0
}
77
78
static inline int
79
sshbuf_check_sanity(const struct sshbuf *buf)
80
0
{
81
0
  SSHBUF_TELL("sanity");
82
0
  if (__predict_false(buf == NULL ||
83
0
      (!buf->readonly && buf->d != buf->cd) ||
84
0
      buf->parent == buf ||
85
0
      buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
86
0
      buf->cd == NULL ||
87
0
      buf->max_size > SSHBUF_SIZE_MAX ||
88
0
      buf->alloc > buf->max_size ||
89
0
      buf->size > buf->alloc ||
90
0
      buf->off > buf->size)) {
91
    /* Do not try to recover from corrupted buffer internals */
92
0
    SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
93
0
    ssh_signal(SIGSEGV, SIG_DFL);
94
0
    raise(SIGSEGV);
95
0
    return SSH_ERR_INTERNAL_ERROR;
96
0
  }
97
0
  return 0;
98
0
}
99
100
static void
101
sshbuf_maybe_pack(struct sshbuf *buf, int force)
102
0
{
103
0
  SSHBUF_DBG(("force %d", force));
104
0
  SSHBUF_TELL("pre-pack");
105
0
  if (buf->off == 0 || buf->readonly || buf->refcount > 1)
106
0
    return;
107
0
  if (force ||
108
0
      (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
109
0
    memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
110
0
    buf->size -= buf->off;
111
0
    buf->off = 0;
112
0
    SSHBUF_TELL("packed");
113
0
  }
114
0
}
115
116
struct sshbuf *
117
sshbuf_new_label (const char *label)
118
0
{
119
0
  struct sshbuf *ret;
120
121
0
  if ((ret = calloc(1, sizeof(*ret))) == NULL)
122
0
    return NULL;
123
0
  ret->alloc = SSHBUF_SIZE_INIT;
124
0
  ret->max_size = SSHBUF_SIZE_MAX;
125
0
  ret->readonly = 0;
126
0
  ret->refcount = 1;
127
0
  ret->parent = NULL;
128
0
  if (label != NULL)
129
0
    strncpy(ret->label, label, MAX_LABEL_LEN-1);
130
0
  if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
131
0
    free(ret);
132
0
    return NULL;
133
0
  }
134
0
  return ret;
135
0
}
136
137
struct sshbuf *
138
sshbuf_from(const void *blob, size_t len)
139
0
{
140
0
  struct sshbuf *ret;
141
142
0
  if (blob == NULL || len > SSHBUF_SIZE_MAX ||
143
0
      (ret = calloc(1, sizeof(*ret))) == NULL)
144
0
    return NULL;
145
0
  ret->alloc = ret->size = ret->max_size = len;
146
0
  ret->readonly = 1;
147
0
  ret->refcount = 1;
148
0
  ret->parent = NULL;
149
0
  ret->cd = blob;
150
0
  ret->d = NULL;
151
0
  return ret;
152
0
}
153
154
int
155
sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
156
0
{
157
0
  int r;
158
159
0
  if ((r = sshbuf_check_sanity(child)) != 0 ||
160
0
      (r = sshbuf_check_sanity(parent)) != 0)
161
0
    return r;
162
0
  if ((child->parent != NULL && child->parent != parent) ||
163
0
      child == parent)
164
0
    return SSH_ERR_INTERNAL_ERROR;
165
0
  child->parent = parent;
166
0
  child->parent->refcount++;
167
0
  return 0;
168
0
}
169
170
struct sshbuf *
171
sshbuf_fromb(struct sshbuf *buf)
172
0
{
173
0
  struct sshbuf *ret;
174
175
0
  if (sshbuf_check_sanity(buf) != 0)
176
0
    return NULL;
177
0
  if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
178
0
    return NULL;
179
0
  if (sshbuf_set_parent(ret, buf) != 0) {
180
0
    sshbuf_free(ret);
181
0
    return NULL;
182
0
  }
183
0
  return ret;
184
0
}
185
186
void
187
sshbuf_free(struct sshbuf *buf)
188
0
{
189
0
  if (buf == NULL)
190
0
    return;
191
  /*
192
   * The following will leak on insane buffers, but this is the safest
193
   * course of action - an invalid pointer or already-freed pointer may
194
   * have been passed to us and continuing to scribble over memory would
195
   * be bad.
196
   */
197
0
  if (sshbuf_check_sanity(buf) != 0)
198
0
    return;
199
200
  /*
201
   * If we are a parent with still-extant children, then don't free just
202
   * yet. The last child's call to sshbuf_free should decrement our
203
   * refcount to 0 and trigger the actual free.
204
   */
205
0
  buf->refcount--;
206
0
  if (buf->refcount > 0)
207
0
    return;
208
209
  /*
210
   * If we are a child, then free our parent to decrement its reference
211
   * count and possibly free it.
212
   */
213
0
  sshbuf_free(buf->parent);
214
0
  buf->parent = NULL;
215
216
0
  if (!buf->readonly)
217
0
    freezero(buf->d, buf->alloc);
218
0
  freezero(buf, sizeof(*buf));
219
0
}
220
221
void
222
sshbuf_reset(struct sshbuf *buf)
223
0
{
224
0
  u_char *d;
225
226
0
  if (buf->readonly || buf->refcount > 1) {
227
    /* Nonsensical. Just make buffer appear empty */
228
0
    buf->off = buf->size;
229
0
    return;
230
0
  }
231
0
  if (sshbuf_check_sanity(buf) != 0)
232
0
    return;
233
0
  buf->off = buf->size = 0;
234
0
  if (buf->alloc != SSHBUF_SIZE_INIT) {
235
0
    if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
236
0
        1)) != NULL) {
237
0
      buf->cd = buf->d = d;
238
0
      buf->alloc = SSHBUF_SIZE_INIT;
239
0
    }
240
0
  }
241
0
  explicit_bzero(buf->d, buf->alloc);
242
0
}
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
0
{
309
0
  if (sshbuf_check_sanity(buf) != 0)
310
0
    return 0;
311
0
  return buf->size - buf->off;
312
0
}
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
0
{
336
0
  if (sshbuf_check_sanity(buf) != 0)
337
0
    return NULL;
338
0
  return buf->cd + buf->off;
339
0
}
340
341
u_char *
342
sshbuf_mutable_ptr(const struct sshbuf *buf)
343
0
{
344
0
  if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
345
0
    return NULL;
346
0
  return buf->d + buf->off;
347
0
}
348
349
int
350
sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
351
0
{
352
0
  int r;
353
354
0
  if ((r = sshbuf_check_sanity(buf)) != 0)
355
0
    return r;
356
0
  if (buf->readonly || buf->refcount > 1)
357
0
    return SSH_ERR_BUFFER_READ_ONLY;
358
0
  SSHBUF_TELL("check");
359
  /* Check that len is reasonable and that max_size + available < len */
360
0
  if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
361
0
    return SSH_ERR_NO_BUFFER_SPACE;
362
0
  return 0;
363
0
}
364
365
int
366
sshbuf_allocate(struct sshbuf *buf, size_t len)
367
0
{
368
0
  size_t rlen, need;
369
0
  u_char *dp;
370
0
  int r;
371
372
0
  SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
373
0
  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
0
  sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
380
0
  SSHBUF_TELL("allocate");
381
0
  if (len + buf->size <= buf->alloc)
382
0
    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
0
  need = len + buf->size - buf->alloc;
389
0
  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
0
  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
0
    need = (4*1024*1024);
410
0
    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
0
  }
414
0
  SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
415
416
  /* rlen might be above the max allocation */
417
0
  if (rlen > buf->max_size)
418
0
    rlen = buf->max_size;
419
420
0
  SSHBUF_DBG(("adjusted rlen %zu", rlen));
421
0
  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
0
  buf->alloc = rlen;
426
0
  buf->cd = buf->d = dp;
427
0
  if ((r = sshbuf_check_reserve(buf, len)) < 0) {
428
    /* shouldn't fail */
429
0
    return r;
430
0
  }
431
0
  SSHBUF_TELL("done");
432
0
  return 0;
433
0
}
434
435
int
436
sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
437
0
{
438
0
  u_char *dp;
439
0
  int r;
440
441
0
  if (dpp != NULL)
442
0
    *dpp = NULL;
443
444
0
  SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
445
0
  if ((r = sshbuf_allocate(buf, len)) != 0)
446
0
    return r;
447
448
0
  dp = buf->d + buf->size;
449
0
  buf->size += len;
450
0
  if (dpp != NULL)
451
0
    *dpp = dp;
452
0
  return 0;
453
0
}
454
455
int
456
sshbuf_consume(struct sshbuf *buf, size_t len)
457
0
{
458
0
  int r;
459
460
0
  SSHBUF_DBG(("len = %zu", len));
461
0
  if ((r = sshbuf_check_sanity(buf)) != 0)
462
0
    return r;
463
0
  if (len == 0)
464
0
    return 0;
465
0
  if (len > sshbuf_len(buf))
466
0
    return SSH_ERR_MESSAGE_INCOMPLETE;
467
0
  buf->off += len;
468
  /* deal with empty buffer */
469
0
  if (buf->off == buf->size)
470
0
    buf->off = buf->size = 0;
471
0
  SSHBUF_TELL("done");
472
0
  return 0;
473
0
}
474
475
int
476
sshbuf_consume_end(struct sshbuf *buf, size_t len)
477
0
{
478
0
  int r;
479
480
0
  SSHBUF_DBG(("len = %zu", len));
481
0
  if ((r = sshbuf_check_sanity(buf)) != 0)
482
0
    return r;
483
0
  if (len == 0)
484
0
    return 0;
485
0
  if (len > sshbuf_len(buf))
486
0
    return SSH_ERR_MESSAGE_INCOMPLETE;
487
0
  buf->size -= len;
488
0
  SSHBUF_TELL("done");
489
0
  return 0;
490
0
}