Coverage Report

Created: 2026-07-15 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hpn-ssh/sshbuf.c
Line
Count
Source
1
/*  $OpenBSD: sshbuf.c,v 1.24 2025/12/29 23:52:09 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
2.63k
#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
338k
{
81
338k
  SSHBUF_TELL("sanity");
82
338k
  if (__predict_false(buf == NULL ||
83
338k
      (!buf->readonly && buf->d != buf->cd) ||
84
338k
      buf->parent == buf ||
85
338k
      buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
86
338k
      buf->cd == NULL ||
87
338k
      buf->max_size > SSHBUF_SIZE_MAX ||
88
338k
      buf->alloc > buf->max_size ||
89
338k
      buf->size > buf->alloc ||
90
338k
      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
338k
  return 0;
98
338k
}
99
100
static void
101
sshbuf_maybe_pack(struct sshbuf *buf, int force)
102
4.80k
{
103
4.80k
  SSHBUF_DBG(("force %d", force));
104
4.80k
  SSHBUF_TELL("pre-pack");
105
4.80k
  if (buf->off == 0 || buf->readonly || buf->refcount > 1)
106
4.80k
    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
5.32k
{
119
5.32k
  struct sshbuf *ret;
120
121
5.32k
  if ((ret = calloc(1, sizeof(*ret))) == NULL)
122
0
    return NULL;
123
5.32k
  ret->alloc = SSHBUF_SIZE_INIT;
124
5.32k
  ret->max_size = SSHBUF_SIZE_MAX;
125
5.32k
  ret->readonly = 0;
126
5.32k
  ret->refcount = 1;
127
5.32k
  ret->parent = NULL;
128
5.32k
  if (label != NULL)
129
5.32k
    strncpy(ret->label, label, MAX_LABEL_LEN-1);
130
5.32k
  if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
131
0
    free(ret);
132
0
    return NULL;
133
0
  }
134
5.32k
  return ret;
135
5.32k
}
136
137
struct sshbuf *
138
sshbuf_from(const void *blob, size_t len)
139
15.3k
{
140
15.3k
  struct sshbuf *ret;
141
142
15.3k
  if (blob == NULL || len > SSHBUF_SIZE_MAX ||
143
15.3k
      (ret = calloc(1, sizeof(*ret))) == NULL)
144
0
    return NULL;
145
15.3k
  ret->alloc = ret->size = ret->max_size = len;
146
15.3k
  ret->readonly = 1;
147
15.3k
  ret->refcount = 1;
148
15.3k
  ret->parent = NULL;
149
15.3k
  ret->cd = blob;
150
15.3k
  ret->d = NULL;
151
15.3k
  return ret;
152
15.3k
}
153
154
int
155
sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
156
10.9k
{
157
10.9k
  int r;
158
159
10.9k
  if ((r = sshbuf_check_sanity(child)) != 0 ||
160
10.9k
      (r = sshbuf_check_sanity(parent)) != 0)
161
0
    return r;
162
10.9k
  if ((child->parent != NULL && child->parent != parent) ||
163
10.9k
      child == parent)
164
0
    return SSH_ERR_INTERNAL_ERROR;
165
10.9k
  child->parent = parent;
166
10.9k
  child->parent->refcount++;
167
10.9k
  return 0;
168
10.9k
}
169
170
struct sshbuf *
171
sshbuf_fromb(struct sshbuf *buf)
172
3.01k
{
173
3.01k
  struct sshbuf *ret;
174
175
3.01k
  if (sshbuf_check_sanity(buf) != 0)
176
0
    return NULL;
177
3.01k
  if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
178
0
    return NULL;
179
3.01k
  if (sshbuf_set_parent(ret, buf) != 0) {
180
0
    sshbuf_free(ret);
181
0
    return NULL;
182
0
  }
183
3.01k
  return ret;
184
3.01k
}
185
186
void
187
sshbuf_free(struct sshbuf *buf)
188
43.8k
{
189
43.8k
  if (buf == NULL)
190
12.2k
    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
31.6k
  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
31.6k
  buf->refcount--;
206
31.6k
  if (buf->refcount > 0)
207
10.9k
    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
20.6k
  sshbuf_free(buf->parent);
214
20.6k
  buf->parent = NULL;
215
216
20.6k
  if (!buf->readonly)
217
5.32k
    freezero(buf->d, buf->alloc);
218
20.6k
  freezero(buf, sizeof(*buf));
219
20.6k
}
220
221
void
222
sshbuf_reset(struct sshbuf *buf)
223
97
{
224
97
  u_char *d;
225
226
97
  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
97
  if (sshbuf_check_sanity(buf) != 0)
232
0
    return;
233
97
  buf->off = buf->size = 0;
234
97
  if (buf->alloc != SSHBUF_SIZE_INIT) {
235
63
    if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
236
63
        1)) != NULL) {
237
63
      buf->cd = buf->d = d;
238
63
      buf->alloc = SSHBUF_SIZE_INIT;
239
63
    }
240
63
  }
241
97
  explicit_bzero(buf->d, buf->alloc);
242
97
}
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
166k
{
309
166k
  if (sshbuf_check_sanity(buf) != 0)
310
0
    return 0;
311
166k
  return buf->size - buf->off;
312
166k
}
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
  /* Reserve ~4.76% (1/21) headroom on channel input buffers to prevent
320
   * a pathological state during bulk data transfers. The headroom scales
321
   * with buffer size and gives the channel time to drain before accepting
322
   * more data, smoothing flow control and reducing buffer reallocation.
323
   * When the buffer is fuller than the headroom allows we must return 0,
324
   * not underflow: size_t is unsigned so a naive subtraction wraps to
325
   * SIZE_MAX, causing the caller to read into a nearly-full buffer and
326
   * thrash window updates. Only applied to channel input buffers as
327
   * other buffer types fail the regression unit tests with headroom. */
328
0
  if (buf->type == BUF_CHANNEL_INPUT) {
329
0
    size_t headroom = buf->max_size - buf->max_size / 21;
330
0
    size_t used = buf->size - buf->off;
331
0
    return (used >= headroom) ? 0 : headroom - used;
332
0
  } else
333
0
    return buf->max_size - (buf->size - buf->off);
334
0
}
335
336
const u_char *
337
sshbuf_ptr(const struct sshbuf *buf)
338
66.6k
{
339
66.6k
  if (sshbuf_check_sanity(buf) != 0)
340
0
    return NULL;
341
66.6k
  return buf->cd + buf->off;
342
66.6k
}
343
344
u_char *
345
sshbuf_mutable_ptr(const struct sshbuf *buf)
346
78
{
347
78
  if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
348
0
    return NULL;
349
78
  return buf->d + buf->off;
350
78
}
351
352
int
353
sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
354
6.11k
{
355
6.11k
  int r;
356
357
6.11k
  if ((r = sshbuf_check_sanity(buf)) != 0)
358
0
    return r;
359
6.11k
  if (buf->readonly || buf->refcount > 1)
360
0
    return SSH_ERR_BUFFER_READ_ONLY;
361
6.11k
  SSHBUF_TELL("check");
362
  /* Check that len is reasonable and that max_size + available < len */
363
6.11k
  if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
364
0
    return SSH_ERR_NO_BUFFER_SPACE;
365
6.11k
  return 0;
366
6.11k
}
367
368
int
369
sshbuf_allocate(struct sshbuf *buf, size_t len)
370
4.80k
{
371
4.80k
  size_t rlen, need;
372
4.80k
  u_char *dp;
373
4.80k
  int r;
374
375
4.80k
  SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
376
4.80k
  if ((r = sshbuf_check_reserve(buf, len)) != 0)
377
0
    return r;
378
  /*
379
   * If the requested allocation appended would push us past max_size
380
   * then pack the buffer, zeroing buf->off.
381
   */
382
4.80k
  sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
383
4.80k
  SSHBUF_TELL("allocate");
384
4.80k
  if (len + buf->size <= buf->alloc)
385
3.48k
    return 0; /* already have it. */
386
387
  /*
388
   * Prefer to alloc in SSHBUF_SIZE_INC units, but
389
   * allocate less if doing so would overflow max_size.
390
   */
391
1.31k
  need = len + buf->size - buf->alloc;
392
1.31k
  rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
393
  /* With the changes in 8.9 the output buffer end up growing pretty
394
   * slowly. It's knows that it needs to grow but it only does so 32K
395
   * at a time. This means a lot of calls to realloc and memcpy which
396
   * kills performance until the buffer reaches some maximum size.
397
   * So we explicitly test for a buffer that's trying to grow and
398
   * if it is then we push the growth by 4MB at a time. This can result in
399
   * the buffer being over allocated (in terms of actual needs) but the
400
   * process is fast. This significantly reduces overhead
401
   * and improves performance. In this case we look for a buffer that is trying
402
   * to grow larger than BUF_WATERSHED (256*1024 taken from PACKET_MAX_SIZE)
403
   * and explcitly check that the buffer is being used for inbound outbound
404
   * channel buffering.
405
   * Updated for 18.4.1 -cjr 04/20/24
406
   */
407
1.31k
  if (rlen > BUF_WATERSHED && (buf->type == BUF_CHANNEL_OUTPUT || buf->type == BUF_CHANNEL_INPUT)) {
408
    /* debug_f ("Prior: label: %s, %p, rlen is %zu need is %zu max_size is %zu",
409
       buf->label, buf, rlen, need, buf->max_size); */
410
    /* easiest thing to do is grow the nuffer by 4MB each time. It might end
411
     * up being somewhat overallocated but works quickly */
412
31
    need = (4*1024*1024);
413
31
    rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
414
    /* debug_f ("Post: label: %s, %p, rlen is %zu need is %zu max_size is %zu", */
415
    /*   buf->label, buf, rlen, need, buf->max_size); */
416
31
  }
417
1.31k
  SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
418
419
  /* rlen might be above the max allocation */
420
1.31k
  if (rlen > buf->max_size)
421
0
    rlen = buf->max_size;
422
423
1.31k
  SSHBUF_DBG(("adjusted rlen %zu", rlen));
424
1.31k
  if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) {
425
0
    SSHBUF_DBG(("realloc fail"));
426
0
    return SSH_ERR_ALLOC_FAIL;
427
0
  }
428
1.31k
  buf->alloc = rlen;
429
1.31k
  buf->cd = buf->d = dp;
430
1.31k
  if ((r = sshbuf_check_reserve(buf, len)) < 0) {
431
    /* shouldn't fail */
432
0
    return r;
433
0
  }
434
1.31k
  SSHBUF_TELL("done");
435
1.31k
  return 0;
436
1.31k
}
437
438
int
439
sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
440
4.80k
{
441
4.80k
  u_char *dp;
442
4.80k
  int r;
443
444
4.80k
  if (dpp != NULL)
445
4.80k
    *dpp = NULL;
446
447
4.80k
  SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
448
4.80k
  if ((r = sshbuf_allocate(buf, len)) != 0)
449
0
    return r;
450
451
4.80k
  dp = buf->d + buf->size;
452
4.80k
  buf->size += len;
453
4.80k
  if (dpp != NULL)
454
4.80k
    *dpp = dp;
455
4.80k
  return 0;
456
4.80k
}
457
458
int
459
sshbuf_consume(struct sshbuf *buf, size_t len)
460
42.8k
{
461
42.8k
  int r;
462
463
42.8k
  SSHBUF_DBG(("len = %zu", len));
464
42.8k
  if ((r = sshbuf_check_sanity(buf)) != 0)
465
0
    return r;
466
42.8k
  if (len == 0)
467
55
    return 0;
468
42.8k
  if (len > sshbuf_len(buf))
469
30
    return SSH_ERR_MESSAGE_INCOMPLETE;
470
42.8k
  buf->off += len;
471
  /* deal with empty buffer */
472
42.8k
  if (buf->off == buf->size)
473
5.03k
    buf->off = buf->size = 0;
474
42.8k
  SSHBUF_TELL("done");
475
42.8k
  return 0;
476
42.8k
}
477
478
int
479
sshbuf_consume_end(struct sshbuf *buf, size_t len)
480
96
{
481
96
  int r;
482
483
96
  SSHBUF_DBG(("len = %zu", len));
484
96
  if ((r = sshbuf_check_sanity(buf)) != 0)
485
0
    return r;
486
96
  if (len == 0)
487
0
    return 0;
488
96
  if (len > sshbuf_len(buf))
489
0
    return SSH_ERR_MESSAGE_INCOMPLETE;
490
96
  buf->size -= len;
491
96
  SSHBUF_TELL("done");
492
96
  return 0;
493
96
}
494
495
int
496
sshbuf_consume_upto_child(struct sshbuf *buf, const struct sshbuf *child)
497
0
{
498
0
  int r;
499
500
0
  if ((r = sshbuf_check_sanity(buf)) != 0 ||
501
0
      (r = sshbuf_check_sanity(child)) != 0)
502
0
    return r;
503
  /* This function is only used for parent/child buffers */
504
0
  if (child->parent != buf)
505
0
    return SSH_ERR_INVALID_ARGUMENT;
506
  /* Nonsensical if the parent has advanced past the child */
507
0
  if (sshbuf_len(child) > sshbuf_len(buf))
508
0
    return SSH_ERR_INVALID_ARGUMENT;
509
  /* More paranoia, shouldn't happen */
510
0
  if (child->cd < buf->cd)
511
0
    return SSH_ERR_INTERNAL_ERROR;
512
  /* Advance */
513
0
  return sshbuf_consume(buf, sshbuf_len(buf) - sshbuf_len(child));
514
0
}