Coverage Report

Created: 2026-01-10 07:11

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