Coverage Report

Created: 2026-06-10 06:24

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
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
  /* 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
0
{
339
0
  if (sshbuf_check_sanity(buf) != 0)
340
0
    return NULL;
341
0
  return buf->cd + buf->off;
342
0
}
343
344
u_char *
345
sshbuf_mutable_ptr(const struct sshbuf *buf)
346
0
{
347
0
  if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
348
0
    return NULL;
349
0
  return buf->d + buf->off;
350
0
}
351
352
int
353
sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
354
0
{
355
0
  int r;
356
357
0
  if ((r = sshbuf_check_sanity(buf)) != 0)
358
0
    return r;
359
0
  if (buf->readonly || buf->refcount > 1)
360
0
    return SSH_ERR_BUFFER_READ_ONLY;
361
0
  SSHBUF_TELL("check");
362
  /* Check that len is reasonable and that max_size + available < len */
363
0
  if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
364
0
    return SSH_ERR_NO_BUFFER_SPACE;
365
0
  return 0;
366
0
}
367
368
int
369
sshbuf_allocate(struct sshbuf *buf, size_t len)
370
0
{
371
0
  size_t rlen, need;
372
0
  u_char *dp;
373
0
  int r;
374
375
0
  SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
376
0
  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
0
  sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
383
0
  SSHBUF_TELL("allocate");
384
0
  if (len + buf->size <= buf->alloc)
385
0
    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
0
  need = len + buf->size - buf->alloc;
392
0
  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
0
  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
0
    need = (4*1024*1024);
413
0
    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
0
  }
417
0
  SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
418
419
  /* rlen might be above the max allocation */
420
0
  if (rlen > buf->max_size)
421
0
    rlen = buf->max_size;
422
423
0
  SSHBUF_DBG(("adjusted rlen %zu", rlen));
424
0
  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
0
  buf->alloc = rlen;
429
0
  buf->cd = buf->d = dp;
430
0
  if ((r = sshbuf_check_reserve(buf, len)) < 0) {
431
    /* shouldn't fail */
432
0
    return r;
433
0
  }
434
0
  SSHBUF_TELL("done");
435
0
  return 0;
436
0
}
437
438
int
439
sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
440
0
{
441
0
  u_char *dp;
442
0
  int r;
443
444
0
  if (dpp != NULL)
445
0
    *dpp = NULL;
446
447
0
  SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
448
0
  if ((r = sshbuf_allocate(buf, len)) != 0)
449
0
    return r;
450
451
0
  dp = buf->d + buf->size;
452
0
  buf->size += len;
453
0
  if (dpp != NULL)
454
0
    *dpp = dp;
455
0
  return 0;
456
0
}
457
458
int
459
sshbuf_consume(struct sshbuf *buf, size_t len)
460
0
{
461
0
  int r;
462
463
0
  SSHBUF_DBG(("len = %zu", len));
464
0
  if ((r = sshbuf_check_sanity(buf)) != 0)
465
0
    return r;
466
0
  if (len == 0)
467
0
    return 0;
468
0
  if (len > sshbuf_len(buf))
469
0
    return SSH_ERR_MESSAGE_INCOMPLETE;
470
0
  buf->off += len;
471
  /* deal with empty buffer */
472
0
  if (buf->off == buf->size)
473
0
    buf->off = buf->size = 0;
474
0
  SSHBUF_TELL("done");
475
0
  return 0;
476
0
}
477
478
int
479
sshbuf_consume_end(struct sshbuf *buf, size_t len)
480
0
{
481
0
  int r;
482
483
0
  SSHBUF_DBG(("len = %zu", len));
484
0
  if ((r = sshbuf_check_sanity(buf)) != 0)
485
0
    return r;
486
0
  if (len == 0)
487
0
    return 0;
488
0
  if (len > sshbuf_len(buf))
489
0
    return SSH_ERR_MESSAGE_INCOMPLETE;
490
0
  buf->size -= len;
491
0
  SSHBUF_TELL("done");
492
0
  return 0;
493
0
}
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
}