Coverage Report

Created: 2025-07-11 06:40

/src/varnish-cache/bin/varnishd/cache/cache_ws_emu.c
Line
Count
Source (jump to first uncovered line)
1
/*-
2
 * Copyright (c) 2021 Varnish Software AS
3
 * All rights reserved.
4
 *
5
 * Author: Dridi Boukelmoune <dridi.boukelmoune@gmail.com>
6
 *
7
 * SPDX-License-Identifier: BSD-2-Clause
8
 *
9
 * Redistribution and use in source and binary forms, with or without
10
 * modification, are permitted provided that the following conditions
11
 * are met:
12
 * 1. Redistributions of source code must retain the above copyright
13
 *    notice, this list of conditions and the following disclaimer.
14
 * 2. Redistributions in binary form must reproduce the above copyright
15
 *    notice, this list of conditions and the following disclaimer in the
16
 *    documentation and/or other materials provided with the distribution.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
22
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28
 * SUCH DAMAGE.
29
 *
30
 */
31
32
#include "config.h"
33
34
#ifdef ENABLE_WORKSPACE_EMULATOR
35
36
#if HAVE_SANITIZER_ASAN_INTERFACE_H
37
#  include <sanitizer/asan_interface.h>
38
#endif
39
40
#include "cache_varnishd.h"
41
42
#include <stdlib.h>
43
44
struct ws_alloc {
45
  unsigned    magic;
46
#define WS_ALLOC_MAGIC    0x22e7fd05
47
  unsigned    off;
48
  unsigned    len;
49
  char      *ptr;
50
  VTAILQ_ENTRY(ws_alloc)  list;
51
};
52
53
VTAILQ_HEAD(ws_alloc_head, ws_alloc);
54
55
struct ws_emu {
56
  unsigned    magic;
57
#define WS_EMU_MAGIC    0x1c89b6ab
58
  unsigned    len;
59
  struct ws   *ws;
60
  struct ws_alloc_head  head;
61
};
62
63
static const uintptr_t snap_overflowed = (uintptr_t)&snap_overflowed;
64
65
static struct ws_emu *
66
ws_emu(const struct ws *ws)
67
6.12k
{
68
6.12k
  struct ws_emu *we;
69
70
6.12k
  CAST_OBJ_NOTNULL(we, (void *)ws->s, WS_EMU_MAGIC);
71
6.12k
  return (we);
72
6.12k
}
73
74
void
75
WS_Assert(const struct ws *ws)
76
4.59k
{
77
4.59k
  struct ws_emu *we;
78
4.59k
  struct ws_alloc *wa, *wa2 = NULL;
79
4.59k
  size_t len;
80
81
4.59k
  CHECK_OBJ_NOTNULL(ws, WS_MAGIC);
82
4.59k
  assert(ws->s != NULL);
83
4.59k
  assert(PAOK(ws->s));
84
4.59k
  assert(ws->e != NULL);
85
4.59k
  assert(PAOK(ws->e));
86
87
4.59k
  we = ws_emu(ws);
88
4.59k
  len = pdiff(ws->s, ws->e);
89
4.59k
  assert(len == we->len);
90
91
4.59k
  len = 0;
92
4.59k
  VTAILQ_FOREACH(wa, &we->head, list) {
93
2.29k
    CHECK_OBJ_NOTNULL(wa, WS_ALLOC_MAGIC);
94
2.29k
    wa2 = wa;
95
2.29k
    assert(len == wa->off);
96
2.29k
    if (wa->ptr == ws->f || wa->ptr == NULL) /* reservation */
97
0
      break;
98
2.29k
    AN(wa->len);
99
2.29k
    len += PRNDUP(wa->len);
100
2.29k
    assert(len <= we->len);
101
2.29k
  }
102
103
4.59k
  if (wa != NULL) {
104
0
    AZ(VTAILQ_NEXT(wa, list));
105
0
    if (wa->ptr == NULL) {
106
0
      AZ(wa->len);
107
0
      assert(ws->f == ws->e);
108
0
      assert(ws->r == ws->e);
109
0
    } else {
110
0
      AN(wa->len);
111
0
      assert(ws->f == wa->ptr);
112
0
      assert(ws->r == ws->f + wa->len);
113
0
    }
114
0
    len += PRNDUP(wa->len);
115
0
    assert(len <= we->len);
116
4.59k
  } else {
117
4.59k
    AZ(ws->f);
118
4.59k
    AZ(ws->r);
119
4.59k
  }
120
121
4.59k
  DSLb(DBG_WORKSPACE, "WS(%p) = (%s, %p %zu %zu %zu)",
122
4.59k
      ws, ws->id, ws->s, wa2 == NULL ? 0 : wa2->off + PRNDUP(wa2->len),
123
4.59k
      ws->r == NULL ? 0 : pdiff(ws->f, ws->r),
124
4.59k
      pdiff(ws->s, ws->e));
125
4.59k
}
126
127
int
128
WS_Allocated(const struct ws *ws, const void *ptr, ssize_t len)
129
0
{
130
0
  struct ws_emu *we;
131
0
  struct ws_alloc *wa;
132
0
  uintptr_t p, pa;
133
134
0
  WS_Assert(ws);
135
0
  AN(ptr);
136
0
  if (len < 0)
137
0
    len = strlen(ptr) + 1;
138
0
  p = (uintptr_t)ptr;
139
0
  we = ws_emu(ws);
140
141
0
  VTAILQ_FOREACH(wa, &we->head, list) {
142
0
    pa = (uintptr_t)wa->ptr;
143
0
    if (p >= (uintptr_t)ws->f && p <= (uintptr_t)ws->r)
144
0
      return (1);
145
    /* XXX: clang 12's ubsan triggers a pointer overflow on
146
     * the if statement below. Since the purpose is to check
147
     * that a pointer+length is within bounds of another
148
     * pointer+length it's unclear whether a pointer overflow
149
     * is relevant. Worked around for now with uintptr_t.
150
     */
151
0
    if (p >= pa && p + len <= pa + wa->len)
152
0
      return (1);
153
0
  }
154
0
  return (0);
155
0
}
156
157
void
158
WS_Init(struct ws *ws, const char *id, void *space, unsigned len)
159
765
{
160
765
  struct ws_emu *we;
161
162
765
  DSLb(DBG_WORKSPACE,
163
765
      "WS_Init(%p, \"%s\", %p, %u)", ws, id, space, len);
164
765
  assert(space != NULL);
165
765
  assert(PAOK(space));
166
765
  assert(len >= sizeof *we);
167
168
765
  len = PRNDDN(len - 1);
169
765
  INIT_OBJ(ws, WS_MAGIC);
170
765
  ws->s = space;
171
765
  ws->e = ws->s + len;
172
173
765
  assert(id[0] & 0x20);   // cheesy islower()
174
765
  bstrcpy(ws->id, id);
175
176
765
  we = space;
177
765
  INIT_OBJ(we, WS_EMU_MAGIC);
178
765
  VTAILQ_INIT(&we->head);
179
765
  we->len = len;
180
181
765
  WS_Assert(ws);
182
765
}
183
184
static void
185
ws_alloc_free(struct ws_emu *we, struct ws_alloc **wap)
186
765
{
187
765
  struct ws_alloc *wa;
188
189
765
  TAKE_OBJ_NOTNULL(wa, wap, WS_ALLOC_MAGIC);
190
765
  AZ(VTAILQ_NEXT(wa, list));
191
765
  VTAILQ_REMOVE(&we->head, wa, list);
192
765
  free(wa->ptr);
193
765
  FREE_OBJ(wa);
194
765
}
195
196
void
197
WS_Reset(struct ws *ws, uintptr_t pp)
198
765
{
199
765
  struct ws_emu *we;
200
765
  struct ws_alloc *wa;
201
765
  char *p;
202
203
765
  WS_Assert(ws);
204
765
  AN(pp);
205
765
  if (pp == snap_overflowed) {
206
0
    DSLb(DBG_WORKSPACE, "WS_Reset(%p, overflowed)", ws);
207
0
    AN(WS_Overflowed(ws));
208
0
    return;
209
0
  }
210
765
  p = (char *)pp;
211
765
  DSLb(DBG_WORKSPACE, "WS_Reset(%p, %p)", ws, p);
212
765
  AZ(ws->r);
213
214
765
  we = ws_emu(ws);
215
1.53k
  while ((wa = VTAILQ_LAST(&we->head, ws_alloc_head)) != NULL &&
216
1.53k
      wa->ptr != p)
217
765
    ws_alloc_free(we, &wa);
218
765
  if (wa == NULL)
219
765
    assert(p == ws->s);
220
221
765
  WS_Assert(ws);
222
765
}
223
224
int
225
WS_Pipeline(struct ws *ws, const void *b, const void *e, unsigned rollback)
226
0
{
227
0
  void *tmp;
228
0
  unsigned r, l;
229
230
0
  WS_Assert(ws);
231
0
  AZ(ws->f);
232
0
  AZ(ws->r);
233
234
  /* NB: the pipeline cannot be moved if it comes from the same
235
   * workspace because a rollback would free the memory. This is
236
   * emulated with two copies instead.
237
   */
238
239
0
  if (b != NULL) {
240
0
    AN(e);
241
0
    l = pdiff(b, e);
242
0
    tmp = malloc(l);
243
0
    AN(tmp);
244
0
    memcpy(tmp, b, l);
245
0
  } else {
246
0
    AZ(e);
247
0
    l = 0;
248
0
    tmp = NULL;
249
0
  }
250
251
0
  if (rollback)
252
0
    WS_Rollback(ws, 0);
253
254
0
  r = WS_ReserveAll(ws);
255
256
0
  if (l > r) {
257
0
    free(tmp);
258
0
    return (-1);
259
0
  }
260
261
0
  if (l > 0)
262
0
    memcpy(ws->f, tmp, l);
263
0
  free(tmp);
264
0
  return (l);
265
0
}
266
267
static struct ws_alloc *
268
ws_emu_alloc(struct ws *ws, unsigned len)
269
765
{
270
765
  struct ws_emu *we;
271
765
  struct ws_alloc *wa;
272
765
  size_t off = 0;
273
274
765
  WS_Assert(ws);
275
765
  AZ(ws->r);
276
277
765
  we = ws_emu(ws);
278
765
  wa = VTAILQ_LAST(&we->head, ws_alloc_head);
279
765
  CHECK_OBJ_ORNULL(wa, WS_ALLOC_MAGIC);
280
281
765
  if (wa != NULL)
282
0
    off = wa->off + PRNDUP(wa->len);
283
765
  if (off + len > we->len) {
284
0
    WS_MarkOverflow(ws);
285
0
    return (NULL);
286
0
  }
287
765
  if (len == 0)
288
0
    len = we->len - off;
289
290
765
  ALLOC_OBJ(wa, WS_ALLOC_MAGIC);
291
765
  AN(wa);
292
765
  wa->off = off;
293
765
  wa->len = len;
294
765
  if (len > 0) {
295
765
    wa->ptr = malloc(len);
296
765
    AN(wa->ptr);
297
765
  }
298
765
  VTAILQ_INSERT_TAIL(&we->head, wa, list);
299
765
  return (wa);
300
765
}
301
302
void *
303
WS_Alloc(struct ws *ws, unsigned bytes)
304
765
{
305
765
  struct ws_alloc *wa;
306
307
765
  assert(bytes > 0);
308
765
  wa = ws_emu_alloc(ws, bytes);
309
765
  WS_Assert(ws);
310
765
  if (wa != NULL) {
311
765
    AN(wa->ptr);
312
765
    DSLb(DBG_WORKSPACE, "WS_Alloc(%p, %u) = %p",
313
765
        ws, bytes, wa->ptr);
314
765
    return (wa->ptr);
315
765
  }
316
0
  return (NULL);
317
765
}
318
319
void *
320
WS_Copy(struct ws *ws, const void *str, int len)
321
0
{
322
0
  struct ws_alloc *wa;
323
324
0
  AN(str);
325
0
  if (len == -1)
326
0
    len = strlen(str) + 1;
327
0
  assert(len > 0);
328
0
  wa = ws_emu_alloc(ws, len);
329
0
  WS_Assert(ws);
330
0
  if (wa != NULL) {
331
0
    AN(wa->ptr);
332
0
    memcpy(wa->ptr, str, len);
333
0
    DSLb(DBG_WORKSPACE, "WS_Copy(%p, %d) = %p",
334
0
        ws, len, wa->ptr);
335
0
    return (wa->ptr);
336
0
  }
337
0
  return (NULL);
338
0
}
339
340
uintptr_t
341
WS_Snapshot(struct ws *ws)
342
0
{
343
0
  struct ws_emu *we;
344
0
  struct ws_alloc *wa;
345
0
  void *p;
346
347
0
  WS_Assert(ws);
348
0
  assert(ws->r == NULL);
349
0
  if (WS_Overflowed(ws)) {
350
0
    DSLb(DBG_WORKSPACE, "WS_Snapshot(%p) = overflowed", ws);
351
0
    return (snap_overflowed);
352
0
  }
353
354
0
  we = ws_emu(ws);
355
0
  wa = VTAILQ_LAST(&we->head, ws_alloc_head);
356
0
  CHECK_OBJ_ORNULL(wa, WS_ALLOC_MAGIC);
357
0
  p = (wa == NULL ? ws->s : wa->ptr);
358
0
  DSLb(DBG_WORKSPACE, "WS_Snapshot(%p) = %p", ws, p);
359
0
  return ((uintptr_t)p);
360
0
}
361
362
unsigned
363
WS_ReserveAll(struct ws *ws)
364
0
{
365
0
  struct ws_alloc *wa;
366
0
  unsigned b;
367
368
0
  wa = ws_emu_alloc(ws, 0);
369
0
  AN(wa);
370
371
0
  if (wa->ptr != NULL) {
372
0
    AN(wa->len);
373
0
    ws->f = wa->ptr;
374
0
    ws->r = ws->f + wa->len;
375
0
  } else {
376
0
    ws->f = ws->r = ws->e;
377
0
  }
378
379
0
  b = pdiff(ws->f, ws->r);
380
0
  DSLb(DBG_WORKSPACE, "WS_ReserveAll(%p) = %u", ws, b);
381
0
  WS_Assert(ws);
382
0
  return (b);
383
0
}
384
385
unsigned
386
WS_ReserveSize(struct ws *ws, unsigned bytes)
387
0
{
388
0
  struct ws_emu *we;
389
0
  struct ws_alloc *wa;
390
391
0
  assert(bytes > 0);
392
0
  wa = ws_emu_alloc(ws, bytes);
393
0
  if (wa == NULL)
394
0
    return (0);
395
396
0
  AN(wa->ptr);
397
0
  assert(wa->len == bytes);
398
0
  ws->f = wa->ptr;
399
0
  ws->r = ws->f + bytes;
400
0
  we = ws_emu(ws);
401
0
  DSLb(DBG_WORKSPACE, "WS_ReserveSize(%p, %u/%u) = %u",
402
0
      ws, bytes, we->len - wa->off, bytes);
403
0
  WS_Assert(ws);
404
0
  return (bytes);
405
0
}
406
407
static void
408
ws_release(struct ws *ws, unsigned bytes)
409
0
{
410
0
  struct ws_emu *we;
411
0
  struct ws_alloc *wa;
412
413
0
  WS_Assert(ws);
414
0
  AN(ws->f);
415
0
  AN(ws->r);
416
0
  we = ws_emu(ws);
417
0
  wa = VTAILQ_LAST(&we->head, ws_alloc_head);
418
0
  AN(wa);
419
0
  assert(bytes <= wa->len);
420
0
  ws->f = ws->r = NULL;
421
422
0
  if (bytes == 0) {
423
0
    ws_alloc_free(we, &wa);
424
0
    return;
425
0
  }
426
427
0
  AN(wa->ptr);
428
#ifdef ASAN_POISON_MEMORY_REGION
429
  ASAN_POISON_MEMORY_REGION(wa->ptr + bytes, wa->len - bytes);
430
#endif
431
0
  wa->len = bytes;
432
0
  WS_Assert(ws);
433
0
}
434
435
void
436
WS_Release(struct ws *ws, unsigned bytes)
437
0
{
438
439
0
  ws_release(ws, bytes);
440
0
  DSLb(DBG_WORKSPACE, "WS_Release(%p, %u)", ws, bytes);
441
0
}
442
443
void
444
WS_ReleaseP(struct ws *ws, const char *ptr)
445
0
{
446
0
  unsigned l;
447
448
0
  WS_Assert(ws);
449
0
  assert(ws->r != NULL);
450
0
  assert(ptr >= ws->f);
451
0
  assert(ptr <= ws->r);
452
0
  l = pdiff(ws->f, ptr);
453
0
  ws_release(ws, l);
454
0
  DSLb(DBG_WORKSPACE, "WS_ReleaseP(%p, %p (%u))", ws, ptr, l);
455
0
}
456
457
void *
458
WS_AtOffset(const struct ws *ws, unsigned off, unsigned len)
459
0
{
460
0
  struct ws_emu *we;
461
0
  struct ws_alloc *wa;
462
463
0
  WS_Assert(ws);
464
0
  we = ws_emu(ws);
465
466
0
  VTAILQ_FOREACH(wa, &we->head, list) {
467
0
    if (wa->off == off) {
468
0
      assert(wa->len >= len);
469
0
      return (wa->ptr);
470
0
    }
471
0
  }
472
473
0
  WRONG("invalid offset");
474
0
  NEEDLESS(return (NULL));
475
0
}
476
477
unsigned
478
WS_ReservationOffset(const struct ws *ws)
479
0
{
480
0
  struct ws_emu *we;
481
0
  struct ws_alloc *wa;
482
483
0
  WS_Assert(ws);
484
0
  AN(ws->f);
485
0
  AN(ws->r);
486
0
  we = ws_emu(ws);
487
0
  wa = VTAILQ_LAST(&we->head, ws_alloc_head);
488
0
  AN(wa);
489
0
  return (wa->off);
490
0
}
491
492
unsigned
493
WS_Dump(const struct ws *ws, char where, size_t off, void *buf, size_t len)
494
0
{
495
0
  struct ws_emu *we;
496
0
  struct ws_alloc *wa;
497
0
  unsigned l;
498
0
  char *b;
499
500
0
  WS_Assert(ws);
501
0
  AN(buf);
502
0
  AN(len);
503
504
0
  if (strchr("sfr", where) == NULL) {
505
0
    errno = EINVAL;
506
0
    return (0);
507
0
  }
508
509
0
  if (where == 'r' && ws->r == NULL) {
510
0
    errno = EAGAIN;
511
0
    return (0);
512
0
  }
513
514
0
  we = ws_emu(ws);
515
0
  wa = VTAILQ_LAST(&we->head, ws_alloc_head);
516
517
0
  l = we->len;
518
0
  if (where != 's' && wa != NULL) {
519
0
    l -= wa->off;
520
0
    if (where == 'f')
521
0
      l -= wa->len;
522
0
  }
523
524
0
  if (off > l) {
525
0
    errno = EFAULT;
526
0
    return (0);
527
0
  }
528
529
0
  b = buf;
530
0
  if (where == 'f' && ws->r != NULL) {
531
0
    if (l > len)
532
0
      l = len;
533
0
    AN(wa);
534
0
    memcpy(b, wa->ptr, l);
535
0
    b += l;
536
0
    len -= l;
537
0
  }
538
539
0
  if (where == 's') {
540
0
    VTAILQ_FOREACH(wa, &we->head, list) {
541
0
      if (len == 0)
542
0
        break;
543
0
      if (wa->ptr == NULL)
544
0
        break;
545
0
      l = vmin_t(size_t, wa->len, len);
546
0
      memcpy(b, wa->ptr, l);
547
0
      b += l;
548
0
      len -= l;
549
0
    }
550
0
  }
551
552
0
  if (len > 0)
553
0
    memset(b, 0xa5, len);
554
0
  return (l);
555
0
}
556
557
static void
558
ws_emu_panic(struct vsb *vsb, const struct ws *ws)
559
0
{
560
0
  const struct ws_emu *we;
561
0
  const struct ws_alloc *wa;
562
563
0
  we = (void *)ws->s;
564
0
  if (PAN_dump_once(vsb, we, WS_EMU_MAGIC, "ws_emu"))
565
0
    return;
566
0
  VSB_printf(vsb, "len = %u,\n", we->len);
567
568
0
  VTAILQ_FOREACH(wa, &we->head, list) {
569
0
    if (PAN_dump_once_oneline(vsb, wa, WS_ALLOC_MAGIC, "ws_alloc"))
570
0
      break;
571
0
    VSB_printf(vsb, "off, len, ptr} = {%u, %u, %p}\n",
572
0
        wa->off, wa->len, wa->ptr);
573
0
  }
574
575
0
  VSB_indent(vsb, -2);
576
0
  VSB_cat(vsb, "},\n");
577
0
}
578
579
void
580
WS_Panic(struct vsb *vsb, const struct ws *ws)
581
0
{
582
583
0
  if (PAN_dump_struct(vsb, ws, WS_MAGIC, "ws"))
584
0
    return;
585
0
  if (ws->id[0] != '\0' && (!(ws->id[0] & 0x20))) // cheesy islower()
586
0
    VSB_cat(vsb, "OVERFLOWED ");
587
0
  VSB_printf(vsb, "id = \"%s\",\n", ws->id);
588
0
  VSB_printf(vsb, "{s, e} = {%p", ws->s);
589
0
  if (ws->e >= ws->s)
590
0
    VSB_printf(vsb, ", +%ld", (long) (ws->e - ws->s));
591
0
  else
592
0
    VSB_printf(vsb, ", %p", ws->e);
593
0
  VSB_cat(vsb, "},\n");
594
0
  VSB_printf(vsb, "{f, r} = {%p", ws->f);
595
0
  if (ws->r >= ws->f)
596
0
    VSB_printf(vsb, ", +%ld", (long) (ws->r - ws->f));
597
0
  else
598
0
    VSB_printf(vsb, ", %p", ws->r);
599
0
  VSB_cat(vsb, "},\n");
600
601
0
  ws_emu_panic(vsb, ws);
602
603
0
  VSB_indent(vsb, -2);
604
0
  VSB_cat(vsb, "},\n");
605
0
}
606
607
#endif /* ENABLE_WORKSPACE_EMULATOR */