Coverage Report

Created: 2026-09-14 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/buffer.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level 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
35.9k
{
37
35.9k
  i_assert(buf->w_buffer == NULL || buf->alloced);
38
39
35.9k
  if (size == buf->alloc)
40
0
    return;
41
42
35.9k
  i_assert(size > buf->alloc);
43
44
35.9k
  if (buf->w_buffer == NULL)
45
35.3k
    buf->w_buffer = p_malloc(buf->pool, size);
46
640
  else
47
640
    buf->w_buffer = p_realloc(buf->pool, buf->w_buffer, buf->alloc, size);
48
35.9k
  buf->alloc = size;
49
35.9k
  buf->writable_size = size-1; /* -1 for str_c() NUL */
50
51
35.9k
  buf->r_buffer = buf->w_buffer;
52
35.9k
  buf->alloced = TRUE;
53
35.9k
}
54
55
static inline void
56
buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size)
57
554k
{
58
554k
  size_t new_size;
59
60
554k
  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
554k
  new_size = pos + data_size;
64
65
554k
  if (new_size > buf->used && buf->used < buf->dirty) {
66
    /* clear used..dirty area */
67
64
    size_t max = I_MIN(I_MIN(buf->alloc, buf->dirty), new_size);
68
69
64
    memset(buf->w_buffer + buf->used, 0, max - buf->used);
70
64
  }
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
554k
  if (new_size > buf->writable_size) {
77
640
    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
640
    size_t new_alloc_size =
85
640
      pool_get_exp_grown_size(buf->pool, buf->alloc,
86
640
            new_size + 1);
87
640
    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
640
    buffer_alloc(buf, new_alloc_size);
93
640
  }
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
554k
  if (new_size > buf->used)
112
18.3k
    buf->used = new_size;
113
554k
  i_assert(buf->used <= buf->alloc);
114
554k
  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
554k
}
124
125
static inline void
126
buffer_check_append_limits(struct real_buffer *buf, size_t data_size)
127
291k
{
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
291k
#ifndef DEBUG_FAST
132
291k
  i_assert(buf->used <= buf->writable_size);
133
291k
  if (buf->writable_size - buf->used < data_size)
134
335
    buffer_check_limits(buf, buf->used, data_size);
135
291k
  else
136
291k
    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
291k
}
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
2.98k
{
178
2.98k
  return buffer_create_dynamic_max(pool, init_size, SIZE_MAX);
179
2.98k
}
180
181
buffer_t *buffer_create_dynamic_max(pool_t pool, size_t init_size,
182
            size_t max_size)
183
35.3k
{
184
35.3k
  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
35.3k
  buf = p_new(pool, struct real_buffer, 1);
196
35.3k
  buf->pool = pool;
197
35.3k
  buf->dynamic = TRUE;
198
35.3k
  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
35.3k
  buffer_alloc(buf, init_size+1);
203
35.3k
  return &buf->buf;
204
35.3k
}
205
206
void buffer_free(buffer_t **_buf)
207
38.5k
{
208
38.5k
  if (*_buf == NULL)
209
3.23k
    return;
210
35.3k
  struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf);
211
212
35.3k
  *_buf = NULL;
213
35.3k
  if (buf->alloced)
214
35.3k
    p_free(buf->pool, buf->w_buffer);
215
35.3k
  if (buf->pool != NULL)
216
35.3k
    p_free(buf->pool, buf);
217
35.3k
}
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
183k
{
257
183k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
258
259
183k
  if (data_size > 0) {
260
183k
    size_t pos = buf->used;
261
183k
    buffer_check_append_limits(buf, data_size);
262
183k
    memcpy(buf->w_buffer + pos, data, data_size);
263
183k
  }
264
183k
}
265
266
void buffer_append_array(buffer_t *buf, const void *data,
267
       size_t count, size_t size)
268
9.70k
{
269
9.70k
  buffer_append(buf, data, MALLOC_MULTIPLY(count, size));
270
9.70k
}
271
272
void buffer_append_c(buffer_t *_buf, unsigned char chr)
273
108k
{
274
108k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
275
108k
  size_t pos = buf->used;
276
277
108k
  buffer_check_append_limits(buf, 1);
278
108k
  buf->w_buffer[pos] = chr;
279
108k
}
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
3.23k
{
302
3.23k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
303
3.23k
  size_t end_size;
304
305
3.23k
  if (pos >= buf->used)
306
0
    return;
307
3.23k
  end_size = buf->used - pos;
308
309
3.23k
  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
3.23k
  } else {
315
    /* delete the rest of the buffer */
316
3.23k
    end_size = 0;
317
3.23k
  }
318
319
3.23k
  buffer_set_used_size(_buf, pos + end_size);
320
3.23k
}
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
207k
{
387
207k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
388
389
  /* +1 extra byte is always kept for NUL termination */
390
207k
  i_assert(buf->used < buf->alloc);
391
  /* Buffer might not be writable, so check first if the NUL is
392
     already there. */
393
207k
  if (((const char *)buf->r_buffer)[buf->used] != '\0')
394
39.4k
    buf->w_buffer[buf->used] = '\0';
395
207k
}
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
553k
{
431
553k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
432
433
553k
  buffer_check_limits(buf, pos, size);
434
553k
  return buf->w_buffer + pos;
435
553k
}
436
437
void *buffer_append_space_unsafe(buffer_t *buf, size_t size)
438
6.47k
{
439
  /* NOTE: can't use buffer_check_append_limits() here because it doesn't
440
     guarantee that the buffer is zero-filled. */
441
6.47k
  return buffer_get_space_unsafe(buf, buf->used, size);
442
6.47k
}
443
444
void *buffer_get_modifiable_data(const buffer_t *_buf, size_t *used_size_r)
445
3.23k
{
446
3.23k
  const struct real_buffer *buf =
447
3.23k
    container_of(_buf, const struct real_buffer, buf);
448
449
3.23k
  if (used_size_r != NULL)
450
0
    *used_size_r = buf->used;
451
3.23k
  i_assert(buf->used == 0 || buf->w_buffer != NULL);
452
3.23k
  return buf->w_buffer;
453
3.23k
}
454
455
void buffer_set_used_size(buffer_t *_buf, size_t used_size)
456
142k
{
457
142k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
458
459
142k
  i_assert(used_size <= buf->alloc);
460
461
142k
  if (buf->used > buf->dirty)
462
4.15k
    buf->dirty = buf->used;
463
464
142k
  buf->used = used_size;
465
142k
}
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
}