Coverage Report

Created: 2025-10-28 06:17

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
15.1M
{
35
15.1M
  i_assert(buf->w_buffer == NULL || buf->alloced);
36
37
15.1M
  if (size == buf->alloc)
38
0
    return;
39
40
15.1M
  i_assert(size > buf->alloc);
41
42
15.1M
  if (buf->w_buffer == NULL)
43
15.0M
    buf->w_buffer = p_malloc(buf->pool, size);
44
78.4k
  else
45
78.4k
    buf->w_buffer = p_realloc(buf->pool, buf->w_buffer, buf->alloc, size);
46
15.1M
  buf->alloc = size;
47
15.1M
  buf->writable_size = size-1; /* -1 for str_c() NUL */
48
49
15.1M
  buf->r_buffer = buf->w_buffer;
50
15.1M
  buf->alloced = TRUE;
51
15.1M
}
52
53
static inline void
54
buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size)
55
94.0M
{
56
94.0M
  size_t new_size;
57
58
94.0M
  if (unlikely(buf->max_size - pos < data_size))
59
0
    i_panic("Buffer write out of range (%zu + %zu)", pos, data_size);
60
61
94.0M
  new_size = pos + data_size;
62
63
94.0M
  if (new_size > buf->used && buf->used < buf->dirty) {
64
    /* clear used..dirty area */
65
22.7M
    size_t max = I_MIN(I_MIN(buf->alloc, buf->dirty), new_size);
66
67
22.7M
    memset(buf->w_buffer + buf->used, 0, max - buf->used);
68
22.7M
  }
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
94.0M
  if (new_size > buf->writable_size) {
75
78.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
78.4k
    size_t new_alloc_size =
83
78.4k
      pool_get_exp_grown_size(buf->pool, buf->alloc,
84
78.4k
            new_size + 1);
85
78.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
78.4k
    buffer_alloc(buf, new_alloc_size);
91
78.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
94.0M
  if (new_size > buf->used)
110
94.0M
    buf->used = new_size;
111
94.0M
  i_assert(buf->used <= buf->alloc);
112
94.0M
  i_assert(buf->w_buffer != NULL);
113
94.0M
}
114
115
static inline void
116
buffer_check_append_limits(struct real_buffer *buf, size_t data_size)
117
72.8M
{
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
72.8M
  if (buf->writable_size - buf->used < data_size)
122
4.71k
    buffer_check_limits(buf, buf->used, data_size);
123
72.8M
  else
124
72.8M
    buf->used += data_size;
125
72.8M
}
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
6.43k
{
162
6.43k
  return buffer_create_dynamic_max(pool, init_size, SIZE_MAX);
163
6.43k
}
164
165
buffer_t *buffer_create_dynamic_max(pool_t pool, size_t init_size,
166
            size_t max_size)
167
15.0M
{
168
15.0M
  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
15.0M
  buf = p_new(pool, struct real_buffer, 1);
180
15.0M
  buf->pool = pool;
181
15.0M
  buf->dynamic = TRUE;
182
15.0M
  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
15.0M
  buffer_alloc(buf, init_size+1);
187
15.0M
  return &buf->buf;
188
15.0M
}
189
190
void buffer_free(buffer_t **_buf)
191
0
{
192
0
  if (*_buf == NULL)
193
0
    return;
194
0
  struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf);
195
196
0
  *_buf = NULL;
197
0
  if (buf->alloced)
198
0
    p_free(buf->pool, buf->w_buffer);
199
0
  if (buf->pool != NULL)
200
0
    p_free(buf->pool, buf);
201
0
}
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
2.14k
{
217
2.14k
  const struct real_buffer *buf =
218
2.14k
    container_of(_buf, const struct real_buffer, buf);
219
220
2.14k
  return buf->pool;
221
2.14k
}
222
223
void buffer_write(buffer_t *_buf, size_t pos,
224
      const void *data, size_t data_size)
225
0
{
226
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
227
228
0
  buffer_check_limits(buf, pos, data_size);
229
0
  if (data_size > 0)
230
0
    memcpy(buf->w_buffer + pos, data, data_size);
231
0
}
232
233
void buffer_append(buffer_t *_buf, const void *data, size_t data_size)
234
17.1M
{
235
17.1M
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
236
237
17.1M
  if (data_size > 0) {
238
17.1M
    size_t pos = buf->used;
239
17.1M
    buffer_check_append_limits(buf, data_size);
240
17.1M
    memcpy(buf->w_buffer + pos, data, data_size);
241
17.1M
  }
242
17.1M
}
243
244
void buffer_append_c(buffer_t *_buf, unsigned char chr)
245
55.7M
{
246
55.7M
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
247
55.7M
  size_t pos = buf->used;
248
249
55.7M
  buffer_check_append_limits(buf, 1);
250
55.7M
  buf->w_buffer[pos] = chr;
251
55.7M
}
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
0
{
268
0
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
269
0
  size_t end_size;
270
271
0
  if (pos >= buf->used)
272
0
    return;
273
0
  end_size = buf->used - pos;
274
275
0
  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
0
  } else {
281
    /* delete the rest of the buffer */
282
0
    end_size = 0;
283
0
  }
284
285
0
  buffer_set_used_size(_buf, pos + end_size);
286
0
}
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
0
{
354
0
  struct real_buffer *dest = container_of(_dest, struct real_buffer, buf);
355
0
  const struct real_buffer *src =
356
0
    container_of(_src, const struct real_buffer, buf);
357
0
  size_t max_size;
358
359
0
  i_assert(src_pos <= src->used);
360
361
0
  max_size = src->used - src_pos;
362
0
  if (copy_size > max_size)
363
0
    copy_size = max_size;
364
365
0
  buffer_check_limits(dest, dest_pos, copy_size);
366
0
  i_assert(src->r_buffer != NULL);
367
368
0
  if (src == dest) {
369
0
    memmove(dest->w_buffer + dest_pos,
370
0
      CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size);
371
0
  } else {
372
0
    memcpy(dest->w_buffer + dest_pos,
373
0
           CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size);
374
0
  }
375
0
}
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
94.0M
{
385
94.0M
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
386
387
94.0M
  buffer_check_limits(buf, pos, size);
388
94.0M
  return buf->w_buffer + pos;
389
94.0M
}
390
391
void *buffer_append_space_unsafe(buffer_t *buf, size_t size)
392
71.2M
{
393
  /* NOTE: can't use buffer_check_append_limits() here because it doesn't
394
     guarantee that the buffer is zero-filled. */
395
71.2M
  return buffer_get_space_unsafe(buf, buf->used, size);
396
71.2M
}
397
398
void *buffer_get_modifiable_data(const buffer_t *_buf, size_t *used_size_r)
399
39
{
400
39
  const struct real_buffer *buf =
401
39
    container_of(_buf, const struct real_buffer, buf);
402
403
39
  if (used_size_r != NULL)
404
0
    *used_size_r = buf->used;
405
39
  i_assert(buf->used == 0 || buf->w_buffer != NULL);
406
39
  return buf->w_buffer;
407
39
}
408
409
void buffer_set_used_size(buffer_t *_buf, size_t used_size)
410
22.7M
{
411
22.7M
  struct real_buffer *buf = container_of(_buf, struct real_buffer, buf);
412
413
22.7M
  i_assert(used_size <= buf->alloc);
414
415
22.7M
  if (buf->used > buf->dirty)
416
22.7M
    buf->dirty = buf->used;
417
418
22.7M
  buf->used = used_size;
419
22.7M
}
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
4.40k
{
433
4.40k
  const struct real_buffer *buf =
434
4.40k
    container_of(_buf, const struct real_buffer, buf);
435
436
4.40k
  return buf->alloc;
437
4.40k
}
438
439
size_t buffer_get_writable_size(const buffer_t *_buf)
440
22.8M
{
441
22.8M
  const struct real_buffer *buf =
442
22.8M
    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
22.8M
  return buf->writable_size;
449
22.8M
}
450
451
size_t buffer_get_avail_size(const buffer_t *_buf)
452
0
{
453
0
  const struct real_buffer *buf =
454
0
    container_of(_buf, const struct real_buffer, buf);
455
456
0
  i_assert(buf->alloc >= buf->used);
457
0
  return ((buf->dynamic ? SIZE_MAX : buf->alloc) - buf->used);
458
0
}
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
}