Coverage Report

Created: 2026-08-08 06:29

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
11.4k
{
37
11.4k
  i_assert(buf->w_buffer == NULL || buf->alloced);
38
39
11.4k
  if (size == buf->alloc)
40
0
    return;
41
42
11.4k
  i_assert(size > buf->alloc);
43
44
11.4k
  if (buf->w_buffer == NULL)
45
10.9k
    buf->w_buffer = p_malloc(buf->pool, size);
46
450
  else
47
450
    buf->w_buffer = p_realloc(buf->pool, buf->w_buffer, buf->alloc, size);
48
11.4k
  buf->alloc = size;
49
11.4k
  buf->writable_size = size-1; /* -1 for str_c() NUL */
50
51
11.4k
  buf->r_buffer = buf->w_buffer;
52
11.4k
  buf->alloced = TRUE;
53
11.4k
}
54
55
static inline void
56
buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size)
57
3.42k
{
58
3.42k
  size_t new_size;
59
60
3.42k
  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
3.42k
  new_size = pos + data_size;
64
65
3.42k
  if (new_size > buf->used && buf->used < buf->dirty) {
66
    /* clear used..dirty area */
67
335
    size_t max = I_MIN(I_MIN(buf->alloc, buf->dirty), new_size);
68
69
335
    memset(buf->w_buffer + buf->used, 0, max - buf->used);
70
335
  }
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
3.42k
  if (new_size > buf->writable_size) {
77
450
    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
450
    size_t new_alloc_size =
85
450
      pool_get_exp_grown_size(buf->pool, buf->alloc,
86
450
            new_size + 1);
87
450
    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
450
    buffer_alloc(buf, new_alloc_size);
93
450
  }
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
3.42k
  if (new_size > buf->used)
112
3.42k
    buf->used = new_size;
113
3.42k
  i_assert(buf->used <= buf->alloc);
114
3.42k
  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
3.42k
}
124
125
static inline void
126
buffer_check_append_limits(struct real_buffer *buf, size_t data_size)
127
6.35k
{
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
6.35k
#ifndef DEBUG_FAST
132
6.35k
  i_assert(buf->used <= buf->writable_size);
133
6.35k
  if (buf->writable_size - buf->used < data_size)
134
450
    buffer_check_limits(buf, buf->used, data_size);
135
5.90k
  else
136
5.90k
    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
6.35k
}
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
1.08k
{
178
1.08k
  return buffer_create_dynamic_max(pool, init_size, SIZE_MAX);
179
1.08k
}
180
181
buffer_t *buffer_create_dynamic_max(pool_t pool, size_t init_size,
182
            size_t max_size)
183
10.9k
{
184
10.9k
  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
10.9k
  buf = p_new(pool, struct real_buffer, 1);
196
10.9k
  buf->pool = pool;
197
10.9k
  buf->dynamic = TRUE;
198
10.9k
  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
10.9k
  buffer_alloc(buf, init_size+1);
203
10.9k
  return &buf->buf;
204
10.9k
}
205
206
void buffer_free(buffer_t **_buf)
207
10.8k
{
208
10.8k
  if (*_buf == NULL)
209
1.08k
    return;
210
9.74k
  struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf);
211
212
9.74k
  *_buf = NULL;
213
9.74k
  if (buf->alloced)
214
9.74k
    p_free(buf->pool, buf->w_buffer);
215
9.74k
  if (buf->pool != NULL)
216
9.74k
    p_free(buf->pool, buf);
217
9.74k
}
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
6.35k
{
257
6.35k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
258
259
6.35k
  if (data_size > 0) {
260
6.35k
    size_t pos = buf->used;
261
6.35k
    buffer_check_append_limits(buf, data_size);
262
6.35k
    memcpy(buf->w_buffer + pos, data, data_size);
263
6.35k
  }
264
6.35k
}
265
266
void buffer_append_array(buffer_t *buf, const void *data,
267
       size_t count, size_t size)
268
3.24k
{
269
3.24k
  buffer_append(buf, data, MALLOC_MULTIPLY(count, size));
270
3.24k
}
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
1.08k
{
302
1.08k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
303
1.08k
  size_t end_size;
304
305
1.08k
  if (pos >= buf->used)
306
0
    return;
307
1.08k
  end_size = buf->used - pos;
308
309
1.08k
  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
1.08k
  } else {
315
    /* delete the rest of the buffer */
316
1.08k
    end_size = 0;
317
1.08k
  }
318
319
1.08k
  buffer_set_used_size(_buf, pos + end_size);
320
1.08k
}
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
2.97k
{
431
2.97k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
432
433
2.97k
  buffer_check_limits(buf, pos, size);
434
2.97k
  return buf->w_buffer + pos;
435
2.97k
}
436
437
void *buffer_append_space_unsafe(buffer_t *buf, size_t size)
438
2.97k
{
439
  /* NOTE: can't use buffer_check_append_limits() here because it doesn't
440
     guarantee that the buffer is zero-filled. */
441
2.97k
  return buffer_get_space_unsafe(buf, buf->used, size);
442
2.97k
}
443
444
void *buffer_get_modifiable_data(const buffer_t *_buf, size_t *used_size_r)
445
1.78k
{
446
1.78k
  const struct real_buffer *buf =
447
1.78k
    container_of(_buf, const struct real_buffer, buf);
448
449
1.78k
  if (used_size_r != NULL)
450
0
    *used_size_r = buf->used;
451
1.78k
  i_assert(buf->used == 0 || buf->w_buffer != NULL);
452
1.78k
  return buf->w_buffer;
453
1.78k
}
454
455
void buffer_set_used_size(buffer_t *_buf, size_t used_size)
456
3.55k
{
457
3.55k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
458
459
3.55k
  i_assert(used_size <= buf->alloc);
460
461
3.55k
  if (buf->used > buf->dirty)
462
2.49k
    buf->dirty = buf->used;
463
464
3.55k
  buf->used = used_size;
465
3.55k
}
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
}