Coverage Report

Created: 2026-07-16 07:01

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
1.07M
{
37
1.07M
  i_assert(buf->w_buffer == NULL || buf->alloced);
38
39
1.07M
  if (size == buf->alloc)
40
0
    return;
41
42
1.07M
  i_assert(size > buf->alloc);
43
44
1.07M
  if (buf->w_buffer == NULL)
45
1.03M
    buf->w_buffer = p_malloc(buf->pool, size);
46
35.7k
  else
47
35.7k
    buf->w_buffer = p_realloc(buf->pool, buf->w_buffer, buf->alloc, size);
48
1.07M
  buf->alloc = size;
49
1.07M
  buf->writable_size = size-1; /* -1 for str_c() NUL */
50
51
1.07M
  buf->r_buffer = buf->w_buffer;
52
1.07M
  buf->alloced = TRUE;
53
1.07M
}
54
55
static inline void
56
buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size)
57
1.03M
{
58
1.03M
  size_t new_size;
59
60
1.03M
  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
1.03M
  new_size = pos + data_size;
64
65
1.03M
  if (new_size > buf->used && buf->used < buf->dirty) {
66
    /* clear used..dirty area */
67
4.91k
    size_t max = I_MIN(I_MIN(buf->alloc, buf->dirty), new_size);
68
69
4.91k
    memset(buf->w_buffer + buf->used, 0, max - buf->used);
70
4.91k
  }
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
1.03M
  if (new_size > buf->writable_size) {
77
35.7k
    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
35.7k
    size_t new_alloc_size =
85
35.7k
      pool_get_exp_grown_size(buf->pool, buf->alloc,
86
35.7k
            new_size + 1);
87
35.7k
    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
35.7k
    buffer_alloc(buf, new_alloc_size);
93
35.7k
  }
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
1.03M
  if (new_size > buf->used)
112
167k
    buf->used = new_size;
113
1.03M
  i_assert(buf->used <= buf->alloc);
114
1.03M
  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
1.03M
}
124
125
static inline void
126
buffer_check_append_limits(struct real_buffer *buf, size_t data_size)
127
185M
{
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
185M
#ifndef DEBUG_FAST
132
185M
  i_assert(buf->used <= buf->writable_size);
133
185M
  if (buf->writable_size - buf->used < data_size)
134
35.3k
    buffer_check_limits(buf, buf->used, data_size);
135
185M
  else
136
185M
    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
185M
}
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
851k
{
178
851k
  return buffer_create_dynamic_max(pool, init_size, SIZE_MAX);
179
851k
}
180
181
buffer_t *buffer_create_dynamic_max(pool_t pool, size_t init_size,
182
            size_t max_size)
183
1.03M
{
184
1.03M
  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
1.03M
  buf = p_new(pool, struct real_buffer, 1);
196
1.03M
  buf->pool = pool;
197
1.03M
  buf->dynamic = TRUE;
198
1.03M
  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
1.03M
  buffer_alloc(buf, init_size+1);
203
1.03M
  return &buf->buf;
204
1.03M
}
205
206
void buffer_free(buffer_t **_buf)
207
939k
{
208
939k
  if (*_buf == NULL)
209
256k
    return;
210
683k
  struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf);
211
212
683k
  *_buf = NULL;
213
683k
  if (buf->alloced)
214
683k
    p_free(buf->pool, buf->w_buffer);
215
683k
  if (buf->pool != NULL)
216
683k
    p_free(buf->pool, buf);
217
683k
}
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
167M
{
257
167M
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
258
259
167M
  if (data_size > 0) {
260
88.1M
    size_t pos = buf->used;
261
88.1M
    buffer_check_append_limits(buf, data_size);
262
88.1M
    memcpy(buf->w_buffer + pos, data, data_size);
263
88.1M
  }
264
167M
}
265
266
void buffer_append_array(buffer_t *buf, const void *data,
267
       size_t count, size_t size)
268
437k
{
269
437k
  buffer_append(buf, data, MALLOC_MULTIPLY(count, size));
270
437k
}
271
272
void buffer_append_c(buffer_t *_buf, unsigned char chr)
273
97.5M
{
274
97.5M
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
275
97.5M
  size_t pos = buf->used;
276
277
97.5M
  buffer_check_append_limits(buf, 1);
278
97.5M
  buf->w_buffer[pos] = chr;
279
97.5M
}
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
290k
{
302
290k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
303
290k
  size_t end_size;
304
305
290k
  if (pos >= buf->used)
306
0
    return;
307
290k
  end_size = buf->used - pos;
308
309
290k
  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
290k
  } else {
315
    /* delete the rest of the buffer */
316
290k
    end_size = 0;
317
290k
  }
318
319
290k
  buffer_set_used_size(_buf, pos + end_size);
320
290k
}
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
402k
{
387
402k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
388
389
  /* +1 extra byte is always kept for NUL termination */
390
402k
  i_assert(buf->used < buf->alloc);
391
  /* Buffer might not be writable, so check first if the NUL is
392
     already there. */
393
402k
  if (((const char *)buf->r_buffer)[buf->used] != '\0')
394
15.9k
    buf->w_buffer[buf->used] = '\0';
395
402k
}
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
932k
{
400
932k
  struct real_buffer *dest = container_of(_dest, struct real_buffer, buf);
401
932k
  const struct real_buffer *src =
402
932k
    container_of(_src, const struct real_buffer, buf);
403
932k
  size_t max_size;
404
405
932k
  i_assert(src_pos <= src->used);
406
407
932k
  max_size = src->used - src_pos;
408
932k
  if (copy_size > max_size)
409
932k
    copy_size = max_size;
410
411
932k
  buffer_check_limits(dest, dest_pos, copy_size);
412
932k
  i_assert(src->r_buffer != NULL);
413
414
932k
  if (src == dest) {
415
0
    memmove(dest->w_buffer + dest_pos,
416
0
      CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size);
417
932k
  } else {
418
932k
    memcpy(dest->w_buffer + dest_pos,
419
932k
           CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size);
420
932k
  }
421
932k
}
422
423
void buffer_append_buf(buffer_t *dest, const buffer_t *src,
424
           size_t src_pos, size_t copy_size)
425
932k
{
426
932k
  buffer_copy(dest, dest->used, src, src_pos, copy_size);
427
932k
}
428
429
void *buffer_get_space_unsafe(buffer_t *_buf, size_t pos, size_t size)
430
70.3k
{
431
70.3k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
432
433
70.3k
  buffer_check_limits(buf, pos, size);
434
70.3k
  return buf->w_buffer + pos;
435
70.3k
}
436
437
void *buffer_append_space_unsafe(buffer_t *buf, size_t size)
438
70.3k
{
439
  /* NOTE: can't use buffer_check_append_limits() here because it doesn't
440
     guarantee that the buffer is zero-filled. */
441
70.3k
  return buffer_get_space_unsafe(buf, buf->used, size);
442
70.3k
}
443
444
void *buffer_get_modifiable_data(const buffer_t *_buf, size_t *used_size_r)
445
22.3k
{
446
22.3k
  const struct real_buffer *buf =
447
22.3k
    container_of(_buf, const struct real_buffer, buf);
448
449
22.3k
  if (used_size_r != NULL)
450
0
    *used_size_r = buf->used;
451
22.3k
  i_assert(buf->used == 0 || buf->w_buffer != NULL);
452
22.3k
  return buf->w_buffer;
453
22.3k
}
454
455
void buffer_set_used_size(buffer_t *_buf, size_t used_size)
456
1.96M
{
457
1.96M
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
458
459
1.96M
  i_assert(used_size <= buf->alloc);
460
461
1.96M
  if (buf->used > buf->dirty)
462
86.9k
    buf->dirty = buf->used;
463
464
1.96M
  buf->used = used_size;
465
1.96M
}
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
85.5k
{
499
85.5k
  const struct real_buffer *buf =
500
85.5k
    container_of(_buf, const struct real_buffer, buf);
501
502
85.5k
  i_assert(buf->alloc >= buf->used);
503
85.5k
  return ((buf->dynamic ? SIZE_MAX : buf->alloc) - buf->used);
504
85.5k
}
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
}