Coverage Report

Created: 2025-11-11 07:12

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
893k
{
35
893k
  i_assert(buf->w_buffer == NULL || buf->alloced);
36
37
893k
  if (size == buf->alloc)
38
0
    return;
39
40
893k
  i_assert(size > buf->alloc);
41
42
893k
  if (buf->w_buffer == NULL)
43
886k
    buf->w_buffer = p_malloc(buf->pool, size);
44
7.42k
  else
45
7.42k
    buf->w_buffer = p_realloc(buf->pool, buf->w_buffer, buf->alloc, size);
46
893k
  buf->alloc = size;
47
893k
  buf->writable_size = size-1; /* -1 for str_c() NUL */
48
49
893k
  buf->r_buffer = buf->w_buffer;
50
893k
  buf->alloced = TRUE;
51
893k
}
52
53
static inline void
54
buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size)
55
1.44M
{
56
1.44M
  size_t new_size;
57
58
1.44M
  if (unlikely(buf->max_size - pos < data_size))
59
0
    i_panic("Buffer write out of range (%zu + %zu)", pos, data_size);
60
61
1.44M
  new_size = pos + data_size;
62
63
1.44M
  if (new_size > buf->used && buf->used < buf->dirty) {
64
    /* clear used..dirty area */
65
7.96k
    size_t max = I_MIN(I_MIN(buf->alloc, buf->dirty), new_size);
66
67
7.96k
    memset(buf->w_buffer + buf->used, 0, max - buf->used);
68
7.96k
  }
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
1.44M
  if (new_size > buf->writable_size) {
75
7.42k
    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
7.42k
    size_t new_alloc_size =
83
7.42k
      pool_get_exp_grown_size(buf->pool, buf->alloc,
84
7.42k
            new_size + 1);
85
7.42k
    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
7.42k
    buffer_alloc(buf, new_alloc_size);
91
7.42k
  }
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
1.44M
  if (new_size > buf->used)
110
1.35M
    buf->used = new_size;
111
1.44M
  i_assert(buf->used <= buf->alloc);
112
1.44M
  i_assert(buf->w_buffer != NULL);
113
1.44M
}
114
115
static inline void
116
buffer_check_append_limits(struct real_buffer *buf, size_t data_size)
117
1.46M
{
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
1.46M
  if (buf->writable_size - buf->used < data_size)
122
7.41k
    buffer_check_limits(buf, buf->used, data_size);
123
1.46M
  else
124
1.46M
    buf->used += data_size;
125
1.46M
}
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
313k
{
162
313k
  return buffer_create_dynamic_max(pool, init_size, SIZE_MAX);
163
313k
}
164
165
buffer_t *buffer_create_dynamic_max(pool_t pool, size_t init_size,
166
            size_t max_size)
167
886k
{
168
886k
  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
886k
  buf = p_new(pool, struct real_buffer, 1);
180
886k
  buf->pool = pool;
181
886k
  buf->dynamic = TRUE;
182
886k
  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
886k
  buffer_alloc(buf, init_size+1);
187
886k
  return &buf->buf;
188
886k
}
189
190
void buffer_free(buffer_t **_buf)
191
271k
{
192
271k
  if (*_buf == NULL)
193
126k
    return;
194
145k
  struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf);
195
196
145k
  *_buf = NULL;
197
145k
  if (buf->alloced)
198
145k
    p_free(buf->pool, buf->w_buffer);
199
145k
  if (buf->pool != NULL)
200
145k
    p_free(buf->pool, buf);
201
145k
}
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
17.5k
{
226
17.5k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
227
228
17.5k
  buffer_check_limits(buf, pos, data_size);
229
17.5k
  if (data_size > 0)
230
17.5k
    memcpy(buf->w_buffer + pos, data, data_size);
231
17.5k
}
232
233
void buffer_append(buffer_t *_buf, const void *data, size_t data_size)
234
1.21M
{
235
1.21M
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
236
237
1.21M
  if (data_size > 0) {
238
1.17M
    size_t pos = buf->used;
239
1.17M
    buffer_check_append_limits(buf, data_size);
240
1.17M
    memcpy(buf->w_buffer + pos, data, data_size);
241
1.17M
  }
242
1.21M
}
243
244
void buffer_append_c(buffer_t *_buf, unsigned char chr)
245
294k
{
246
294k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
247
294k
  size_t pos = buf->used;
248
249
294k
  buffer_check_append_limits(buf, 1);
250
294k
  buf->w_buffer[pos] = chr;
251
294k
}
252
253
void buffer_insert(buffer_t *_buf, size_t pos,
254
       const void *data, size_t data_size)
255
21.2k
{
256
21.2k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
257
258
21.2k
  if (pos >= buf->used)
259
11.2k
    buffer_write(_buf, pos, data, data_size);
260
10.0k
  else if (data_size > 0) {
261
10.0k
    buffer_copy(_buf, pos + data_size, _buf, pos, SIZE_MAX);
262
10.0k
    memcpy(buf->w_buffer + pos, data, data_size);
263
10.0k
  }
264
21.2k
}
265
266
void buffer_delete(buffer_t *_buf, size_t pos, size_t size)
267
67.0k
{
268
67.0k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
269
67.0k
  size_t end_size;
270
271
67.0k
  if (pos >= buf->used)
272
0
    return;
273
67.0k
  end_size = buf->used - pos;
274
275
67.0k
  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
67.0k
  } else {
281
    /* delete the rest of the buffer */
282
67.0k
    end_size = 0;
283
67.0k
  }
284
285
67.0k
  buffer_set_used_size(_buf, pos + end_size);
286
67.0k
}
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
118k
{
324
118k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
325
326
118k
  buffer_check_limits(buf, pos, data_size);
327
118k
  memset(buf->w_buffer + pos, 0, data_size);
328
118k
}
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
10.0k
{
354
10.0k
  struct real_buffer *dest = container_of(_dest, struct real_buffer, buf);
355
10.0k
  const struct real_buffer *src =
356
10.0k
    container_of(_src, const struct real_buffer, buf);
357
10.0k
  size_t max_size;
358
359
10.0k
  i_assert(src_pos <= src->used);
360
361
10.0k
  max_size = src->used - src_pos;
362
10.0k
  if (copy_size > max_size)
363
10.0k
    copy_size = max_size;
364
365
10.0k
  buffer_check_limits(dest, dest_pos, copy_size);
366
10.0k
  i_assert(src->r_buffer != NULL);
367
368
10.0k
  if (src == dest) {
369
10.0k
    memmove(dest->w_buffer + dest_pos,
370
10.0k
      CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size);
371
10.0k
  } else {
372
0
    memcpy(dest->w_buffer + dest_pos,
373
0
           CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size);
374
0
  }
375
10.0k
}
376
377
void buffer_append_buf(buffer_t *dest, const buffer_t *src,
378
           size_t src_pos, size_t copy_size)
379
0
{
380
0
  buffer_copy(dest, dest->used, src, src_pos, copy_size);
381
0
}
382
383
void *buffer_get_space_unsafe(buffer_t *_buf, size_t pos, size_t size)
384
1.29M
{
385
1.29M
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
386
387
1.29M
  buffer_check_limits(buf, pos, size);
388
1.29M
  return buf->w_buffer + pos;
389
1.29M
}
390
391
void *buffer_append_space_unsafe(buffer_t *buf, size_t size)
392
1.16M
{
393
  /* NOTE: can't use buffer_check_append_limits() here because it doesn't
394
     guarantee that the buffer is zero-filled. */
395
1.16M
  return buffer_get_space_unsafe(buf, buf->used, size);
396
1.16M
}
397
398
void *buffer_get_modifiable_data(const buffer_t *_buf, size_t *used_size_r)
399
1.03M
{
400
1.03M
  const struct real_buffer *buf =
401
1.03M
    container_of(_buf, const struct real_buffer, buf);
402
403
1.03M
  if (used_size_r != NULL)
404
0
    *used_size_r = buf->used;
405
1.03M
  i_assert(buf->used == 0 || buf->w_buffer != NULL);
406
1.03M
  return buf->w_buffer;
407
1.03M
}
408
409
void buffer_set_used_size(buffer_t *_buf, size_t used_size)
410
124k
{
411
124k
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
412
413
124k
  i_assert(used_size <= buf->alloc);
414
415
124k
  if (buf->used > buf->dirty)
416
49.6k
    buf->dirty = buf->used;
417
418
124k
  buf->used = used_size;
419
124k
}
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
373k
{
433
373k
  const struct real_buffer *buf =
434
373k
    container_of(_buf, const struct real_buffer, buf);
435
436
373k
  return buf->alloc;
437
373k
}
438
439
size_t buffer_get_writable_size(const buffer_t *_buf)
440
66.2k
{
441
66.2k
  const struct real_buffer *buf =
442
66.2k
    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
66.2k
  return buf->writable_size;
449
66.2k
}
450
451
size_t buffer_get_avail_size(const buffer_t *_buf)
452
12.5k
{
453
12.5k
  const struct real_buffer *buf =
454
12.5k
    container_of(_buf, const struct real_buffer, buf);
455
456
12.5k
  i_assert(buf->alloc >= buf->used);
457
12.5k
  return ((buf->dynamic ? SIZE_MAX : buf->alloc) - buf->used);
458
12.5k
}
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
}