Coverage Report

Created: 2025-07-02 06:55

/src/openssh/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
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
391k
{
57
391k
  SSHBUF_TELL("sanity");
58
391k
  if (__predict_false(buf == NULL ||
59
391k
      (!buf->readonly && buf->d != buf->cd) ||
60
391k
      buf->parent == buf ||
61
391k
      buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
62
391k
      buf->cd == NULL ||
63
391k
      buf->max_size > SSHBUF_SIZE_MAX ||
64
391k
      buf->alloc > buf->max_size ||
65
391k
      buf->size > buf->alloc ||
66
391k
      buf->off > buf->size)) {
67
    /* Do not try to recover from corrupted buffer internals */
68
0
    SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
69
0
    ssh_signal(SIGSEGV, SIG_DFL);
70
0
    raise(SIGSEGV);
71
0
    return SSH_ERR_INTERNAL_ERROR;
72
0
  }
73
391k
  return 0;
74
391k
}
75
76
static void
77
sshbuf_maybe_pack(struct sshbuf *buf, int force)
78
5.79k
{
79
5.79k
  SSHBUF_DBG(("force %d", force));
80
5.79k
  SSHBUF_TELL("pre-pack");
81
5.79k
  if (buf->off == 0 || buf->readonly || buf->refcount > 1)
82
5.79k
    return;
83
0
  if (force ||
84
0
      (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
85
0
    memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
86
0
    buf->size -= buf->off;
87
0
    buf->off = 0;
88
0
    SSHBUF_TELL("packed");
89
0
  }
90
0
}
91
92
struct sshbuf *
93
sshbuf_new(void)
94
5.77k
{
95
5.77k
  struct sshbuf *ret;
96
97
5.77k
  if ((ret = calloc(1, sizeof(*ret))) == NULL)
98
0
    return NULL;
99
5.77k
  ret->alloc = SSHBUF_SIZE_INIT;
100
5.77k
  ret->max_size = SSHBUF_SIZE_MAX;
101
5.77k
  ret->readonly = 0;
102
5.77k
  ret->refcount = 1;
103
5.77k
  ret->parent = NULL;
104
5.77k
  if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
105
0
    free(ret);
106
0
    return NULL;
107
0
  }
108
5.77k
  return ret;
109
5.77k
}
110
111
struct sshbuf *
112
sshbuf_from(const void *blob, size_t len)
113
17.4k
{
114
17.4k
  struct sshbuf *ret;
115
116
17.4k
  if (blob == NULL || len > SSHBUF_SIZE_MAX ||
117
17.4k
      (ret = calloc(1, sizeof(*ret))) == NULL)
118
0
    return NULL;
119
17.4k
  ret->alloc = ret->size = ret->max_size = len;
120
17.4k
  ret->readonly = 1;
121
17.4k
  ret->refcount = 1;
122
17.4k
  ret->parent = NULL;
123
17.4k
  ret->cd = blob;
124
17.4k
  ret->d = NULL;
125
17.4k
  return ret;
126
17.4k
}
127
128
int
129
sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
130
12.0k
{
131
12.0k
  int r;
132
133
12.0k
  if ((r = sshbuf_check_sanity(child)) != 0 ||
134
12.0k
      (r = sshbuf_check_sanity(parent)) != 0)
135
0
    return r;
136
12.0k
  if ((child->parent != NULL && child->parent != parent) ||
137
12.0k
      child == parent)
138
0
    return SSH_ERR_INTERNAL_ERROR;
139
12.0k
  child->parent = parent;
140
12.0k
  child->parent->refcount++;
141
12.0k
  return 0;
142
12.0k
}
143
144
struct sshbuf *
145
sshbuf_fromb(struct sshbuf *buf)
146
3.37k
{
147
3.37k
  struct sshbuf *ret;
148
149
3.37k
  if (sshbuf_check_sanity(buf) != 0)
150
0
    return NULL;
151
3.37k
  if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
152
0
    return NULL;
153
3.37k
  if (sshbuf_set_parent(ret, buf) != 0) {
154
0
    sshbuf_free(ret);
155
0
    return NULL;
156
0
  }
157
3.37k
  return ret;
158
3.37k
}
159
160
void
161
sshbuf_free(struct sshbuf *buf)
162
49.3k
{
163
49.3k
  if (buf == NULL)
164
14.0k
    return;
165
  /*
166
   * The following will leak on insane buffers, but this is the safest
167
   * course of action - an invalid pointer or already-freed pointer may
168
   * have been passed to us and continuing to scribble over memory would
169
   * be bad.
170
   */
171
35.3k
  if (sshbuf_check_sanity(buf) != 0)
172
0
    return;
173
174
  /*
175
   * If we are a parent with still-extant children, then don't free just
176
   * yet. The last child's call to sshbuf_free should decrement our
177
   * refcount to 0 and trigger the actual free.
178
   */
179
35.3k
  buf->refcount--;
180
35.3k
  if (buf->refcount > 0)
181
12.0k
    return;
182
183
  /*
184
   * If we are a child, then free our parent to decrement its reference
185
   * count and possibly free it.
186
   */
187
23.2k
  sshbuf_free(buf->parent);
188
23.2k
  buf->parent = NULL;
189
190
23.2k
  if (!buf->readonly)
191
5.77k
    freezero(buf->d, buf->alloc);
192
23.2k
  freezero(buf, sizeof(*buf));
193
23.2k
}
194
195
void
196
sshbuf_reset(struct sshbuf *buf)
197
82
{
198
82
  u_char *d;
199
200
82
  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
82
  if (sshbuf_check_sanity(buf) != 0)
206
0
    return;
207
82
  buf->off = buf->size = 0;
208
82
  if (buf->alloc != SSHBUF_SIZE_INIT) {
209
60
    if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
210
60
        1)) != NULL) {
211
60
      buf->cd = buf->d = d;
212
60
      buf->alloc = SSHBUF_SIZE_INIT;
213
60
    }
214
60
  }
215
82
  explicit_bzero(buf->d, buf->alloc);
216
82
}
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
190k
{
283
190k
  if (sshbuf_check_sanity(buf) != 0)
284
0
    return 0;
285
190k
  return buf->size - buf->off;
286
190k
}
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
78.6k
{
299
78.6k
  if (sshbuf_check_sanity(buf) != 0)
300
0
    return NULL;
301
78.6k
  return buf->cd + buf->off;
302
78.6k
}
303
304
u_char *
305
sshbuf_mutable_ptr(const struct sshbuf *buf)
306
63
{
307
63
  if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
308
0
    return NULL;
309
63
  return buf->d + buf->off;
310
63
}
311
312
int
313
sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
314
7.19k
{
315
7.19k
  int r;
316
317
7.19k
  if ((r = sshbuf_check_sanity(buf)) != 0)
318
0
    return r;
319
7.19k
  if (buf->readonly || buf->refcount > 1)
320
0
    return SSH_ERR_BUFFER_READ_ONLY;
321
7.19k
  SSHBUF_TELL("check");
322
  /* Check that len is reasonable and that max_size + available < len */
323
7.19k
  if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
324
0
    return SSH_ERR_NO_BUFFER_SPACE;
325
7.19k
  return 0;
326
7.19k
}
327
328
int
329
sshbuf_allocate(struct sshbuf *buf, size_t len)
330
5.79k
{
331
5.79k
  size_t rlen, need;
332
5.79k
  u_char *dp;
333
5.79k
  int r;
334
335
5.79k
  SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
336
5.79k
  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
5.79k
  sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
343
5.79k
  SSHBUF_TELL("allocate");
344
5.79k
  if (len + buf->size <= buf->alloc)
345
4.38k
    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
1.40k
  need = len + buf->size - buf->alloc;
352
1.40k
  rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
353
1.40k
  SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
354
1.40k
  if (rlen > buf->max_size)
355
0
    rlen = buf->alloc + need;
356
1.40k
  SSHBUF_DBG(("adjusted rlen %zu", rlen));
357
1.40k
  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
1.40k
  buf->alloc = rlen;
362
1.40k
  buf->cd = buf->d = dp;
363
1.40k
  if ((r = sshbuf_check_reserve(buf, len)) < 0) {
364
    /* shouldn't fail */
365
0
    return r;
366
0
  }
367
1.40k
  SSHBUF_TELL("done");
368
1.40k
  return 0;
369
1.40k
}
370
371
int
372
sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
373
5.79k
{
374
5.79k
  u_char *dp;
375
5.79k
  int r;
376
377
5.79k
  if (dpp != NULL)
378
5.79k
    *dpp = NULL;
379
380
5.79k
  SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
381
5.79k
  if ((r = sshbuf_allocate(buf, len)) != 0)
382
0
    return r;
383
384
5.79k
  dp = buf->d + buf->size;
385
5.79k
  buf->size += len;
386
5.79k
  if (dpp != NULL)
387
5.79k
    *dpp = dp;
388
5.79k
  return 0;
389
5.79k
}
390
391
int
392
sshbuf_consume(struct sshbuf *buf, size_t len)
393
51.6k
{
394
51.6k
  int r;
395
396
51.6k
  SSHBUF_DBG(("len = %zu", len));
397
51.6k
  if ((r = sshbuf_check_sanity(buf)) != 0)
398
0
    return r;
399
51.6k
  if (len == 0)
400
54
    return 0;
401
51.6k
  if (len > sshbuf_len(buf))
402
113
    return SSH_ERR_MESSAGE_INCOMPLETE;
403
51.5k
  buf->off += len;
404
  /* deal with empty buffer */
405
51.5k
  if (buf->off == buf->size)
406
6.39k
    buf->off = buf->size = 0;
407
51.5k
  SSHBUF_TELL("done");
408
51.5k
  return 0;
409
51.6k
}
410
411
int
412
sshbuf_consume_end(struct sshbuf *buf, size_t len)
413
81
{
414
81
  int r;
415
416
81
  SSHBUF_DBG(("len = %zu", len));
417
81
  if ((r = sshbuf_check_sanity(buf)) != 0)
418
0
    return r;
419
81
  if (len == 0)
420
0
    return 0;
421
81
  if (len > sshbuf_len(buf))
422
0
    return SSH_ERR_MESSAGE_INCOMPLETE;
423
81
  buf->size -= len;
424
81
  SSHBUF_TELL("done");
425
81
  return 0;
426
81
}
427