Coverage Report

Created: 2026-06-09 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/buffer.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3
/* @UNSAFE: whole file */
4
5
#include "lib.h"
6
#include "safe-memset.h"
7
#include "buffer.h"
8
9
#define BUFFER_TRAILING_SENTRY_CHR 0xAB
10
11
/* Disable our memcpy() safety wrapper. This file is very performance sensitive
12
   and it's been checked to work correctly with memcpy(). */
13
#undef memcpy
14
15
struct real_buffer {
16
  union {
17
    struct buffer buf;
18
    struct {
19
      /* public: */
20
      const void *r_buffer;
21
      size_t used;
22
      /* private: */
23
      unsigned char *w_buffer;
24
      size_t dirty, alloc, writable_size, max_size;
25
26
      pool_t pool;
27
28
      bool alloced:1;
29
      bool dynamic:1;
30
    };
31
  };
32
};
33
typedef int buffer_check_sizes[COMPILE_ERROR_IF_TRUE(sizeof(struct real_buffer) > sizeof(buffer_t)) ?1:1];
34
35
static void buffer_alloc(struct real_buffer *buf, size_t size)
36
0
{
37
0
  i_assert(buf->w_buffer == NULL || buf->alloced);
38
39
0
  if (size == buf->alloc)
40
0
    return;
41
42
0
  i_assert(size > buf->alloc);
43
44
0
  if (buf->w_buffer == NULL)
45
0
    buf->w_buffer = p_malloc(buf->pool, size);
46
0
  else
47
0
    buf->w_buffer = p_realloc(buf->pool, buf->w_buffer, buf->alloc, size);
48
0
  buf->alloc = size;
49
0
  buf->writable_size = size-1; /* -1 for str_c() NUL */
50
51
0
  buf->r_buffer = buf->w_buffer;
52
0
  buf->alloced = TRUE;
53
0
}
54
55
static inline void
56
buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size)
57
0
{
58
0
  size_t new_size;
59
60
0
  if (unlikely(pos > buf->max_size || buf->max_size - pos < data_size))
61
0
    i_panic("Buffer write out of range (%zu + %zu)", pos, data_size);
62
63
0
  new_size = pos + data_size;
64
65
0
  if (new_size > buf->used && buf->used < buf->dirty) {
66
    /* clear used..dirty area */
67
0
    size_t max = I_MIN(I_MIN(buf->alloc, buf->dirty), new_size);
68
69
0
    memset(buf->w_buffer + buf->used, 0, max - buf->used);
70
0
  }
71
72
  /* Use buf->writable_size instead of buf->alloc to always keep +1 byte
73
     available in case str_c() is called for this buffer. This is mainly
74
     for cases where the buffer is allocated from data stack, and str_c()
75
     is called in a separate stack frame. */
76
0
  if (new_size > buf->writable_size) {
77
0
    if (unlikely(!buf->dynamic)) {
78
0
      i_panic("Buffer full (%zu > %zu, pool %s)",
79
0
        pos + data_size, buf->alloc,
80
0
        buf->pool == NULL ? "<none>" :
81
0
        pool_get_name(buf->pool));
82
0
    }
83
84
0
    size_t new_alloc_size =
85
0
      pool_get_exp_grown_size(buf->pool, buf->alloc,
86
0
            new_size + 1);
87
0
    if (new_alloc_size > buf->max_size) {
88
      /* limit to max_size, but do include +1 for
89
         str_c() NUL */
90
0
      new_alloc_size = buf->max_size + 1;
91
0
    }
92
0
    buffer_alloc(buf, new_alloc_size);
93
0
  }
94
#if 0
95
  else if (new_size > buf->used && buf->alloced &&
96
     !buf->pool->alloconly_pool && !buf->pool->datastack_pool) {
97
    void *new_buf;
98
99
    /* buffer's size increased: move the buffer's memory elsewhere.
100
       this should help catch bugs where old pointers are tried to
101
       be used to access the buffer's memory */
102
    new_buf = p_malloc(buf->pool, buf->alloc);
103
    memcpy(new_buf, buf->w_buffer, buf->alloc);
104
    p_free(buf->pool, buf->w_buffer);
105
106
    buf->w_buffer = new_buf;
107
    buf->r_buffer = new_buf;
108
  }
109
#endif
110
111
0
  if (new_size > buf->used)
112
0
    buf->used = new_size;
113
0
  i_assert(buf->used <= buf->alloc);
114
0
  i_assert(buf->w_buffer != NULL);
115
116
#ifdef DEBUG_FAST
117
  if (new_size == buf->used && new_size < buf->alloc &&
118
      buf->dirty <= new_size && buf->w_buffer != NULL) {
119
    buf->w_buffer[new_size] = BUFFER_TRAILING_SENTRY_CHR;
120
    buf->dirty = new_size + 1;
121
  }
122
#endif
123
0
}
124
125
static inline void
126
buffer_check_append_limits(struct real_buffer *buf, size_t data_size)
127
0
{
128
  /* Fast path: See if data to be appended fits into allocated buffer.
129
     If it does, we don't even need to memset() the dirty buffer since
130
     it's going to be filled with the newly appended data. */
131
0
#ifndef DEBUG_FAST
132
0
  i_assert(buf->used <= buf->writable_size);
133
0
  if (buf->writable_size - buf->used < data_size)
134
0
    buffer_check_limits(buf, buf->used, data_size);
135
0
  else
136
0
    buf->used += data_size;
137
#else
138
  /* Always add trailing sentry with debug checks */
139
  buffer_check_limits(buf, buf->used, data_size);
140
#endif
141
0
}
142
143
#undef buffer_create_from_data
144
void buffer_create_from_data(buffer_t *buffer, void *data, size_t size)
145
0
{
146
0
  struct real_buffer *buf;
147
148
0
  i_assert(sizeof(*buffer) >= sizeof(struct real_buffer));
149
150
0
  buf = container_of(buffer, struct real_buffer, buf);
151
0
  i_zero(buf);
152
0
  buf->alloc = buf->writable_size = buf->max_size = size;
153
0
  buf->r_buffer = buf->w_buffer = data;
154
  /* clear the whole memory area. unnecessary usually, but if the
155
     buffer is used by e.g. str_c() it tries to access uninitialized
156
     memory */
157
0
  memset(data, 0, size);
158
0
}
159
160
#undef buffer_create_from_const_data
161
void buffer_create_from_const_data(buffer_t *buffer,
162
           const void *data, size_t size)
163
0
{
164
0
  struct real_buffer *buf;
165
166
0
  i_assert(sizeof(*buffer) >= sizeof(struct real_buffer));
167
168
0
  buf = container_of(buffer, struct real_buffer, buf);
169
0
  i_zero(buf);
170
171
0
  buf->used = buf->alloc = buf->writable_size = buf->max_size = size;
172
0
  buf->r_buffer = data;
173
0
  i_assert(buf->w_buffer == NULL);
174
0
}
175
176
buffer_t *buffer_create_dynamic(pool_t pool, size_t init_size)
177
0
{
178
0
  return buffer_create_dynamic_max(pool, init_size, SIZE_MAX);
179
0
}
180
181
buffer_t *buffer_create_dynamic_max(pool_t pool, size_t init_size,
182
            size_t max_size)
183
0
{
184
0
  struct real_buffer *buf;
185
186
#ifdef DEBUG_FAST
187
  /* we increment this by 1 later on, so if it's SIZE_MAX
188
     it turns into 0 and hides a potential bug.
189
190
     Too scary to use in production for now, though. This
191
     can change in future. */
192
  i_assert(init_size < SIZE_MAX);
193
#endif
194
195
0
  buf = p_new(pool, struct real_buffer, 1);
196
0
  buf->pool = pool;
197
0
  buf->dynamic = TRUE;
198
0
  buf->max_size = max_size;
199
  /* buffer_alloc() reserves +1 for str_c() NIL, so add +1 here to
200
     init_size so we can actually write that much to the buffer without
201
     realloc */
202
0
  buffer_alloc(buf, init_size+1);
203
0
  return &buf->buf;
204
0
}
205
206
void buffer_free(buffer_t **_buf)
207
0
{
208
0
  if (*_buf == NULL)
209
0
    return;
210
0
  struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf);
211
212
0
  *_buf = NULL;
213
0
  if (buf->alloced)
214
0
    p_free(buf->pool, buf->w_buffer);
215
0
  if (buf->pool != NULL)
216
0
    p_free(buf->pool, buf);
217
0
}
218
219
void *buffer_free_without_data(buffer_t **_buf)
220
0
{
221
0
  struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf);
222
0
  void *data;
223
224
0
  *_buf = NULL;
225
226
0
  data = buf->w_buffer;
227
0
  p_free(buf->pool, buf);
228
0
  return data;
229
0
}
230
231
pool_t buffer_get_pool(const buffer_t *_buf)
232
0
{
233
0
  const struct real_buffer *buf =
234
0
    container_of(_buf, const struct real_buffer, buf);
235
236
0
  return buf->pool;
237
0
}
238
239
void buffer_write(buffer_t *_buf, size_t pos,
240
      const void *data, size_t data_size)
241
0
{
242
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
243
244
0
  buffer_check_limits(buf, pos, data_size);
245
0
  if (data_size > 0)
246
0
    memcpy(buf->w_buffer + pos, data, data_size);
247
0
}
248
249
void buffer_write_array(buffer_t *buf, size_t pos,
250
      const void *data, size_t count, size_t size)
251
0
{
252
0
  buffer_write(buf, pos, data, MALLOC_MULTIPLY(count, size));
253
0
}
254
255
void buffer_append(buffer_t *_buf, const void *data, size_t data_size)
256
0
{
257
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
258
259
0
  if (data_size > 0) {
260
0
    size_t pos = buf->used;
261
0
    buffer_check_append_limits(buf, data_size);
262
0
    memcpy(buf->w_buffer + pos, data, data_size);
263
0
  }
264
0
}
265
266
void buffer_append_array(buffer_t *buf, const void *data,
267
       size_t count, size_t size)
268
0
{
269
0
  buffer_append(buf, data, MALLOC_MULTIPLY(count, size));
270
0
}
271
272
void buffer_append_c(buffer_t *_buf, unsigned char chr)
273
0
{
274
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
275
0
  size_t pos = buf->used;
276
277
0
  buffer_check_append_limits(buf, 1);
278
0
  buf->w_buffer[pos] = chr;
279
0
}
280
281
void buffer_insert(buffer_t *_buf, size_t pos,
282
       const void *data, size_t data_size)
283
0
{
284
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
285
286
0
  if (pos >= buf->used)
287
0
    buffer_write(_buf, pos, data, data_size);
288
0
  else if (data_size > 0) {
289
0
    buffer_copy(_buf, pos + data_size, _buf, pos, SIZE_MAX);
290
0
    memcpy(buf->w_buffer + pos, data, data_size);
291
0
  }
292
0
}
293
294
void buffer_insert_array(buffer_t *buf, size_t pos,
295
       const void *data, size_t count, size_t size)
296
0
{
297
0
  buffer_insert(buf, pos, data, MALLOC_MULTIPLY(count, size));
298
0
}
299
300
void buffer_delete(buffer_t *_buf, size_t pos, size_t size)
301
0
{
302
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
303
0
  size_t end_size;
304
305
0
  if (pos >= buf->used)
306
0
    return;
307
0
  end_size = buf->used - pos;
308
309
0
  if (size < end_size) {
310
    /* delete from between */
311
0
    end_size -= size;
312
0
    memmove(buf->w_buffer + pos,
313
0
      buf->w_buffer + pos + size, end_size);
314
0
  } else {
315
    /* delete the rest of the buffer */
316
0
    end_size = 0;
317
0
  }
318
319
0
  buffer_set_used_size(_buf, pos + end_size);
320
0
}
321
322
void buffer_replace(buffer_t *_buf, size_t pos, size_t size,
323
        const void *data, size_t data_size)
324
0
{
325
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
326
0
  size_t end_size;
327
328
0
  if (pos >= buf->used) {
329
0
    buffer_write(_buf, pos, data, data_size);
330
0
    return;
331
0
  }
332
0
  end_size = buf->used - pos;
333
334
0
  if (size < end_size) {
335
0
    end_size -= size;
336
0
    if (data_size == 0) {
337
      /* delete from between */
338
0
      memmove(buf->w_buffer + pos,
339
0
        buf->w_buffer + pos + size, end_size);
340
0
    } else {
341
      /* insert */
342
0
      buffer_copy(_buf, pos + data_size, _buf, pos + size,
343
0
            SIZE_MAX);
344
0
      memcpy(buf->w_buffer + pos, data, data_size);
345
0
    }
346
0
  } else {
347
    /* overwrite the end */
348
0
    end_size = 0;
349
0
    buffer_write(_buf, pos, data, data_size);
350
0
  }
351
352
0
  buffer_set_used_size(_buf, pos + data_size + end_size);
353
0
}
354
355
356
void buffer_write_zero(buffer_t *_buf, size_t pos, size_t data_size)
357
0
{
358
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
359
360
0
  buffer_check_limits(buf, pos, data_size);
361
0
  memset(buf->w_buffer + pos, 0, data_size);
362
0
}
363
364
void buffer_append_zero(buffer_t *_buf, size_t data_size)
365
0
{
366
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
367
368
  /* NOTE: When appending it's enough to check that the limits are
369
     valid, because the data is already guaranteed to be zero-filled. */
370
0
  buffer_check_limits(buf, buf->used, data_size);
371
0
}
372
373
void buffer_insert_zero(buffer_t *_buf, size_t pos, size_t data_size)
374
0
{
375
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
376
377
0
  if (pos >= buf->used)
378
0
    buffer_write_zero(_buf, pos, data_size);
379
0
  else {
380
0
    buffer_copy(_buf, pos + data_size, _buf, pos, SIZE_MAX);
381
0
    memset(buf->w_buffer + pos, 0, data_size);
382
0
  }
383
0
}
384
385
void buffer_nul_terminate(buffer_t *_buf)
386
0
{
387
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
388
389
  /* +1 extra byte is always kept for NUL termination */
390
0
  i_assert(buf->used < buf->alloc);
391
  /* Buffer might not be writable, so check first if the NUL is
392
     already there. */
393
0
  if (((const char *)buf->r_buffer)[buf->used] != '\0')
394
0
    buf->w_buffer[buf->used] = '\0';
395
0
}
396
397
void buffer_copy(buffer_t *_dest, size_t dest_pos,
398
     const buffer_t *_src, size_t src_pos, size_t copy_size)
399
0
{
400
0
  struct real_buffer *dest = container_of(_dest, struct real_buffer, buf);
401
0
  const struct real_buffer *src =
402
0
    container_of(_src, const struct real_buffer, buf);
403
0
  size_t max_size;
404
405
0
  i_assert(src_pos <= src->used);
406
407
0
  max_size = src->used - src_pos;
408
0
  if (copy_size > max_size)
409
0
    copy_size = max_size;
410
411
0
  buffer_check_limits(dest, dest_pos, copy_size);
412
0
  i_assert(src->r_buffer != NULL);
413
414
0
  if (src == dest) {
415
0
    memmove(dest->w_buffer + dest_pos,
416
0
      CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size);
417
0
  } else {
418
0
    memcpy(dest->w_buffer + dest_pos,
419
0
           CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size);
420
0
  }
421
0
}
422
423
void buffer_append_buf(buffer_t *dest, const buffer_t *src,
424
           size_t src_pos, size_t copy_size)
425
0
{
426
0
  buffer_copy(dest, dest->used, src, src_pos, copy_size);
427
0
}
428
429
void *buffer_get_space_unsafe(buffer_t *_buf, size_t pos, size_t size)
430
0
{
431
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
432
433
0
  buffer_check_limits(buf, pos, size);
434
0
  return buf->w_buffer + pos;
435
0
}
436
437
void *buffer_append_space_unsafe(buffer_t *buf, size_t size)
438
0
{
439
  /* NOTE: can't use buffer_check_append_limits() here because it doesn't
440
     guarantee that the buffer is zero-filled. */
441
0
  return buffer_get_space_unsafe(buf, buf->used, size);
442
0
}
443
444
void *buffer_get_modifiable_data(const buffer_t *_buf, size_t *used_size_r)
445
0
{
446
0
  const struct real_buffer *buf =
447
0
    container_of(_buf, const struct real_buffer, buf);
448
449
0
  if (used_size_r != NULL)
450
0
    *used_size_r = buf->used;
451
0
  i_assert(buf->used == 0 || buf->w_buffer != NULL);
452
0
  return buf->w_buffer;
453
0
}
454
455
void buffer_set_used_size(buffer_t *_buf, size_t used_size)
456
0
{
457
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
458
459
0
  i_assert(used_size <= buf->alloc);
460
461
0
  if (buf->used > buf->dirty)
462
0
    buf->dirty = buf->used;
463
464
0
  buf->used = used_size;
465
0
}
466
467
void buffer_clear_safe(buffer_t *_buf)
468
0
{
469
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
470
471
  /* Can be NULL with const data based buffers */
472
0
  if (buf->w_buffer != NULL)
473
0
    safe_memset(buf->w_buffer, 0, I_MAX(buf->used, buf->dirty));
474
0
  buffer_clear(_buf);
475
0
}
476
477
size_t buffer_get_size(const buffer_t *_buf)
478
0
{
479
0
  const struct real_buffer *buf =
480
0
    container_of(_buf, const struct real_buffer, buf);
481
482
0
  return buf->alloc;
483
0
}
484
485
size_t buffer_get_writable_size(const buffer_t *_buf)
486
0
{
487
0
  const struct real_buffer *buf =
488
0
    container_of(_buf, const struct real_buffer, buf);
489
490
  /* Use buf->writable_size instead of buf->alloc to reserve +1 for
491
     str_c() NUL in buffer_check_limits(). Otherwise the caller might
492
     increase the buffer's alloc size unnecessarily when it just wants
493
     to access the entire buffer. */
494
0
  return buf->writable_size;
495
0
}
496
497
size_t buffer_get_avail_size(const buffer_t *_buf)
498
0
{
499
0
  const struct real_buffer *buf =
500
0
    container_of(_buf, const struct real_buffer, buf);
501
502
0
  i_assert(buf->alloc >= buf->used);
503
0
  return ((buf->dynamic ? SIZE_MAX : buf->alloc) - buf->used);
504
0
}
505
506
bool buffer_cmp(const buffer_t *buf1, const buffer_t *buf2)
507
0
{
508
0
  if (buf1->used != buf2->used)
509
0
    return FALSE;
510
0
  if (buf1->used == 0)
511
0
    return TRUE;
512
513
0
  return memcmp(buf1->data, buf2->data, buf1->used) == 0;
514
0
}
515
516
void buffer_verify_pool(buffer_t *_buf)
517
0
{
518
0
  const struct real_buffer *buf =
519
0
    container_of(_buf, struct real_buffer, buf);
520
0
  void *ret;
521
522
0
  if (buf->pool != NULL && buf->pool->datastack_pool && buf->alloc > 0) {
523
    /* this doesn't really do anything except verify the
524
       stack frame */
525
0
    ret = p_realloc(buf->pool, buf->w_buffer,
526
0
        buf->alloc, buf->alloc);
527
0
    i_assert(ret == buf->w_buffer);
528
0
  }
529
0
}
530
531
void ATTR_NO_SANITIZE_IMPLICIT_CONVERSION
532
  ATTR_NO_SANITIZE_INTEGER
533
buffer_truncate_rshift_bits(buffer_t *buf, size_t bits)
534
0
{
535
  /* no-op if it's shorten than bits in any case.. */
536
0
  if (buf->used * 8 < bits) return;
537
538
0
  if (bits > 0) {
539
    /* truncate it to closest byte boundary */
540
0
    size_t bytes = ((bits + 7) & ~(size_t)7) / 8;
541
    /* remaining bits */
542
0
    bits = bits % 8;
543
0
    buffer_set_used_size(buf, I_MIN(bytes, buf->used));
544
0
    unsigned char *ptr = buffer_get_modifiable_data(buf, &bytes);
545
    /* right shift over byte array */
546
0
    if (bits > 0) {
547
0
      for(size_t i=bytes-1;i>0;i--)
548
0
        ptr[i] = (ptr[i]>>(8-bits)) +
549
0
           ((ptr[i-1]&(0xff>>(bits)))<<bits);
550
0
      ptr[0] = ptr[0]>>(8-bits);
551
0
    }
552
0
  } else {
553
0
    buffer_set_used_size(buf, 0);
554
0
  }
555
0
}