[master] cfa0983df ws_emu: Introduce the workspace emulator

Dridi Boukelmoune dridi.boukelmoune at gmail.com
Fri Aug 27 15:30:09 UTC 2021


commit cfa0983dfe3f8582d8bcd69d061671f2afc4eadd
Author: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
Date:   Tue Jul 13 09:29:47 2021 +0200

    ws_emu: Introduce the workspace emulator
    
    The goal of the workspace emulator is to replicate the regular workspace
    behavior with individual allocations and make it work transparently.
    
    It's the successor of the workspace sanitizer from #3320 with notable
    differences:
    
    - enabled at configure time instead of run time
    - in a separate source file instead of mixed in
    - using sparse allocations instead of built-in red zones
    
    This means that the workspace emulator can be combined with regular
    sanitizer, in particular asan and lsan. If available, asan's public
    interface is used to mitigate the possible overflow of a reservation
    after some of it was released.
    
    Even without sanitizers, the fact that we integrate with jemalloc by
    default and enable its abort and junk options in varnishtest is enough
    to detect a use-after-free in some cases.
    
    With sanitizers though, the workspace emulator can observe #3550.
    
    One drawback is that the logic is split in two files, and some functions
    are identical in the two files. It might be possible to split cache_ws.c
    into something like cache_ws_alloc.c and cache_ws_util.c for example.
    
    Closes #3320
    Refs #3550
    Refs #3600

diff --git a/bin/varnishd/Makefile.am b/bin/varnishd/Makefile.am
index ba666267f..e129b9a8a 100644
--- a/bin/varnishd/Makefile.am
+++ b/bin/varnishd/Makefile.am
@@ -53,7 +53,6 @@ varnishd_SOURCES = \
 	cache/cache_vrt_vcl.c \
 	cache/cache_vrt_vmod.c \
 	cache/cache_wrk.c \
-	cache/cache_ws.c \
 	common/common_vsc.c \
 	common/common_vsmw.c \
 	hash/hash_classic.c \
@@ -110,6 +109,14 @@ varnishd_SOURCES = \
 	waiter/cache_waiter_ports.c \
 	waiter/mgt_waiter.c
 
+if ENABLE_WORKSPACE_EMULATOR
+varnishd_SOURCES += \
+	cache/cache_ws_emu.c
+else
+varnishd_SOURCES += \
+	cache/cache_ws.c
+endif
+
 if WITH_PERSISTENT_STORAGE
 varnishd_SOURCES += \
 	storage/mgt_storage_persistent.c \
diff --git a/bin/varnishd/cache/cache_varnishd.h b/bin/varnishd/cache/cache_varnishd.h
index 24746b0f7..bf246677a 100644
--- a/bin/varnishd/cache/cache_varnishd.h
+++ b/bin/varnishd/cache/cache_varnishd.h
@@ -534,16 +534,9 @@ WS_IsReserved(const struct ws *ws)
 
 void WS_Rollback(struct ws *, uintptr_t);
 void *WS_AtOffset(const struct ws *ws, unsigned off, unsigned len);
+unsigned WS_ReservationOffset(const struct ws *ws);
 unsigned WS_ReqPipeline(struct ws *, const void *b, const void *e);
 
-static inline unsigned
-WS_ReservationOffset(const struct ws *ws)
-{
-
-	AN(ws->r);
-	return (ws->f - ws->s);
-}
-
 /* http1/cache_http1_pipe.c */
 void V1P_Init(void);
 
diff --git a/bin/varnishd/cache/cache_ws.c b/bin/varnishd/cache/cache_ws.c
index 38f439779..1db047a1a 100644
--- a/bin/varnishd/cache/cache_ws.c
+++ b/bin/varnishd/cache/cache_ws.c
@@ -386,6 +386,14 @@ WS_AtOffset(const struct ws *ws, unsigned off, unsigned len)
 	return (ptr);
 }
 
+unsigned
+WS_ReservationOffset(const struct ws *ws)
+{
+
+	AN(ws->r);
+	return (ws->f - ws->s);
+}
+
 /*---------------------------------------------------------------------
  * Build a VSB on a workspace.
  * Usage pattern:
diff --git a/bin/varnishd/cache/cache_ws_emu.c b/bin/varnishd/cache/cache_ws_emu.c
new file mode 100644
index 000000000..bc9a4c67e
--- /dev/null
+++ b/bin/varnishd/cache/cache_ws_emu.c
@@ -0,0 +1,724 @@
+/*-
+ * Copyright (c) 2021 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include "config.h"
+
+#if HAVE_SANITIZER_ASAN_INTERFACE_H
+#  include <sanitizer/asan_interface.h>
+#endif
+
+#include "cache_varnishd.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+struct ws_alloc {
+	unsigned		magic;
+#define WS_ALLOC_MAGIC		0x22e7fd05
+	unsigned		off;
+	unsigned		len;
+	char			*ptr;
+	VTAILQ_ENTRY(ws_alloc)	list;
+};
+
+VTAILQ_HEAD(ws_alloc_head, ws_alloc);
+
+struct ws_emu {
+	unsigned		magic;
+#define WS_EMU_MAGIC		0x1c89b6ab
+	unsigned		len;
+	struct ws		*ws;
+	struct ws_alloc_head	head;
+};
+
+static const uintptr_t snap_overflowed = (uintptr_t)&snap_overflowed;
+
+static struct ws_emu *
+ws_emu(const struct ws *ws)
+{
+	struct ws_emu *we;
+
+	CAST_OBJ_NOTNULL(we, (void *)ws->s, WS_EMU_MAGIC);
+	return (we);
+}
+
+void
+WS_Assert(const struct ws *ws)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa, *wa2 = NULL;
+	size_t len;
+
+	CHECK_OBJ_NOTNULL(ws, WS_MAGIC);
+	assert(ws->s != NULL);
+	assert(PAOK(ws->s));
+	assert(ws->e != NULL);
+	assert(PAOK(ws->e));
+
+	we = ws_emu(ws);
+	len = pdiff(ws->s, ws->e);
+	assert(len == we->len);
+
+	len = 0;
+	VTAILQ_FOREACH(wa, &we->head, list) {
+		CHECK_OBJ_NOTNULL(wa, WS_ALLOC_MAGIC);
+		wa2 = wa;
+		assert(len == wa->off);
+		if (wa->ptr == ws->f || wa->ptr == NULL) /* reservation */
+			break;
+		AN(wa->len);
+		len += PRNDUP(wa->len);
+		assert(len <= we->len);
+	}
+
+	if (wa != NULL) {
+		AZ(VTAILQ_NEXT(wa, list));
+		if (wa->ptr == NULL) {
+			AZ(wa->len);
+			assert(ws->f == ws->e);
+			assert(ws->r == ws->e);
+		} else {
+			AN(wa->len);
+			assert(ws->f == wa->ptr);
+			assert(ws->r == ws->f + wa->len);
+		}
+		len += PRNDUP(wa->len);
+		assert(len <= we->len);
+	} else {
+		AZ(ws->f);
+		AZ(ws->r);
+	}
+
+	DSL(DBG_WORKSPACE, 0, "WS(%p) = (%s, %p %zu %zu %zu)",
+	    ws, ws->id, ws->s, wa2 == NULL ? 0 : wa2->off + PRNDUP(wa2->len),
+	    ws->r == NULL ? 0 : pdiff(ws->f, ws->r),
+	    pdiff(ws->s, ws->e));
+}
+
+int
+WS_Allocated(const struct ws *ws, const void *ptr, ssize_t len)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+	uintptr_t p, pa;
+
+	WS_Assert(ws);
+	AN(ptr);
+	if (len < 0)
+		len = strlen(ptr) + 1;
+	p = (uintptr_t)ptr;
+	we = ws_emu(ws);
+
+	VTAILQ_FOREACH(wa, &we->head, list) {
+		pa = (uintptr_t)wa->ptr;
+		if (p >= (uintptr_t)ws->f && p <= (uintptr_t)ws->r)
+			return (1);
+		/* XXX: clang 12's ubsan triggers a pointer overflow on
+		 * the if statement below. Since the purpose is to check
+		 * that a pointer+length is within bounds of another
+		 * pointer+length it's unclear whether a pointer overflow
+		 * is relevant. Worked around for now with uintptr_t.
+		 */
+		if (p >= pa && p + len <= pa + wa->len)
+			return (1);
+	}
+	return (0);
+}
+
+void
+WS_Init(struct ws *ws, const char *id, void *space, unsigned len)
+{
+	struct ws_emu *we;
+
+	DSL(DBG_WORKSPACE, 0,
+	    "WS_Init(%p, \"%s\", %p, %u)", ws, id, space, len);
+	assert(space != NULL);
+	assert(PAOK(space));
+	assert(len >= sizeof *we);
+
+	len = PRNDDN(len - 1);
+	INIT_OBJ(ws, WS_MAGIC);
+	ws->s = space;
+	ws->e = ws->s + len;
+
+	assert(id[0] & 0x20);		// cheesy islower()
+	bstrcpy(ws->id, id);
+
+	we = space;
+	INIT_OBJ(we, WS_EMU_MAGIC);
+	VTAILQ_INIT(&we->head);
+	we->len = len;
+
+	WS_Assert(ws);
+}
+
+void
+WS_Id(const struct ws *ws, char *id)
+{
+
+	WS_Assert(ws);
+	AN(id);
+	memcpy(id, ws->id, WS_ID_SIZE);
+	id[0] |= 0x20;			// cheesy tolower()
+}
+
+void
+WS_MarkOverflow(struct ws *ws)
+{
+	CHECK_OBJ_NOTNULL(ws, WS_MAGIC);
+
+	ws->id[0] &= ~0x20;		// cheesy toupper()
+}
+
+static void
+ws_ClearOverflow(struct ws *ws)
+{
+	CHECK_OBJ_NOTNULL(ws, WS_MAGIC);
+
+	ws->id[0] |= 0x20;		// cheesy tolower()
+}
+
+static void
+ws_alloc_free(struct ws_emu *we, struct ws_alloc **wap)
+{
+	struct ws_alloc *wa;
+
+	TAKE_OBJ_NOTNULL(wa, wap, WS_ALLOC_MAGIC);
+	AZ(VTAILQ_NEXT(wa, list));
+	VTAILQ_REMOVE(&we->head, wa, list);
+	free(wa->ptr);
+	FREE_OBJ(wa);
+}
+
+void
+WS_Reset(struct ws *ws, uintptr_t pp)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+	char *p;
+
+	WS_Assert(ws);
+	AN(pp);
+	if (pp == snap_overflowed) {
+		DSL(DBG_WORKSPACE, 0, "WS_Reset(%p, overflowed)", ws);
+		AN(WS_Overflowed(ws));
+		return;
+	}
+	p = (char *)pp;
+	DSL(DBG_WORKSPACE, 0, "WS_Reset(%p, %p)", ws, p);
+	AZ(ws->r);
+
+	we = ws_emu(ws);
+	while ((wa = VTAILQ_LAST(&we->head, ws_alloc_head)) != NULL &&
+	    wa->ptr != p)
+		ws_alloc_free(we, &wa);
+	if (wa == NULL)
+		assert(p == ws->s);
+
+	WS_Assert(ws);
+}
+
+void
+WS_Rollback(struct ws *ws, uintptr_t pp)
+{
+
+	WS_Assert(ws);
+
+	if (pp == 0)
+		pp = (uintptr_t)ws->s;
+	ws_ClearOverflow(ws);
+	WS_Reset(ws, pp);
+}
+
+unsigned
+WS_ReqPipeline(struct ws *ws, const void *b, const void *e)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+	unsigned l;
+
+	WS_Assert(ws);
+	AZ(ws->f);
+	AZ(ws->r);
+
+	if (strcasecmp(ws->id, "req"))
+		AZ(b);
+
+	if (b == NULL) {
+		AZ(e);
+		if (!strcasecmp(ws->id, "req"))
+			WS_Rollback(ws, 0);
+		(void)WS_ReserveAll(ws);
+		return (0);
+	}
+
+	we = ws_emu(ws);
+	ALLOC_OBJ(wa, WS_ALLOC_MAGIC);
+	AN(wa);
+	wa->len = we->len;
+	wa->ptr = malloc(wa->len);
+	AN(wa->ptr);
+
+	AN(e);
+	l = pdiff(b, e);
+	assert(l <= wa->len);
+	memcpy(wa->ptr, b, l);
+
+	WS_Rollback(ws, 0);
+	ws->f = wa->ptr;
+	ws->r = ws->f + wa->len;
+	VTAILQ_INSERT_TAIL(&we->head, wa, list);
+	WS_Assert(ws);
+	return (l);
+}
+
+static struct ws_alloc *
+ws_emu_alloc(struct ws *ws, unsigned len)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+	size_t off = 0;
+
+	WS_Assert(ws);
+	AZ(ws->r);
+
+	we = ws_emu(ws);
+	wa = VTAILQ_LAST(&we->head, ws_alloc_head);
+	CHECK_OBJ_ORNULL(wa, WS_ALLOC_MAGIC);
+
+	if (wa != NULL)
+		off = wa->off + PRNDUP(wa->len);
+	if (off + len > we->len) {
+		WS_MarkOverflow(ws);
+		return (NULL);
+	}
+	if (len == 0)
+		len = we->len - off;
+
+	ALLOC_OBJ(wa, WS_ALLOC_MAGIC);
+	AN(wa);
+	wa->off = off;
+	wa->len = len;
+	if (len > 0) {
+		wa->ptr = malloc(len);
+		AN(wa->ptr);
+	}
+	VTAILQ_INSERT_TAIL(&we->head, wa, list);
+	return (wa);
+}
+
+void *
+WS_Alloc(struct ws *ws, unsigned bytes)
+{
+	struct ws_alloc *wa;
+
+	assert(bytes > 0);
+	wa = ws_emu_alloc(ws, bytes);
+	WS_Assert(ws);
+	if (wa != NULL) {
+		AN(wa->ptr);
+		DSL(DBG_WORKSPACE, 0, "WS_Alloc(%p, %u) = %p",
+		    ws, bytes, wa->ptr);
+		return (wa->ptr);
+	}
+	return (NULL);
+}
+
+void *
+WS_Copy(struct ws *ws, const void *str, int len)
+{
+	struct ws_alloc *wa;
+
+	AN(str);
+	if (len == -1)
+		len = strlen(str) + 1;
+	assert(len > 0);
+	wa = ws_emu_alloc(ws, len);
+	WS_Assert(ws);
+	if (wa != NULL) {
+		AN(wa->ptr);
+		memcpy(wa->ptr, str, len);
+		DSL(DBG_WORKSPACE, 0, "WS_Copy(%p, %d) = %p",
+		    ws, len, wa->ptr);
+		return (wa->ptr);
+	}
+	return (NULL);
+}
+
+const char *
+WS_Printf(struct ws *ws, const char *fmt, ...)
+{
+	unsigned u, v;
+	va_list ap;
+	char *p;
+
+	u = WS_ReserveAll(ws);
+	p = ws->f;
+	va_start(ap, fmt);
+	v = vsnprintf(p, u, fmt, ap);
+	va_end(ap);
+	if (v >= u) {
+		WS_Release(ws, 0);
+		WS_MarkOverflow(ws);
+		p = NULL;
+	} else {
+		WS_Release(ws, v + 1);
+	}
+	return (p);
+}
+
+uintptr_t
+WS_Snapshot(struct ws *ws)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+	void *p;
+
+	WS_Assert(ws);
+	assert(ws->r == NULL);
+	if (WS_Overflowed(ws)) {
+		DSL(DBG_WORKSPACE, 0, "WS_Snapshot(%p) = overflowed", ws);
+		return (snap_overflowed);
+	}
+
+	we = ws_emu(ws);
+	wa = VTAILQ_LAST(&we->head, ws_alloc_head);
+	CHECK_OBJ_ORNULL(wa, WS_ALLOC_MAGIC);
+	p = (wa == NULL ? ws->s : wa->ptr);
+	DSL(DBG_WORKSPACE, 0, "WS_Snapshot(%p) = %p", ws, p);
+	return ((uintptr_t)p);
+}
+
+unsigned
+WS_ReserveAll(struct ws *ws)
+{
+	struct ws_alloc *wa;
+	unsigned b;
+
+	wa = ws_emu_alloc(ws, 0);
+	AN(wa);
+
+	if (wa->ptr != NULL) {
+		AN(wa->len);
+		ws->f = wa->ptr;
+		ws->r = ws->f + wa->len;
+	} else {
+		ws->f = ws->r = ws->e;
+	}
+
+	b = pdiff(ws->f, ws->r);
+	DSL(DBG_WORKSPACE, 0, "WS_ReserveAll(%p) = %u", ws, b);
+	WS_Assert(ws);
+	return (b);
+}
+
+unsigned
+WS_ReserveSize(struct ws *ws, unsigned bytes)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+
+	assert(bytes > 0);
+	wa = ws_emu_alloc(ws, bytes);
+	if (wa == NULL)
+		return (0);
+
+	AN(wa->ptr);
+	assert(wa->len == bytes);
+	ws->f = wa->ptr;
+	ws->r = ws->f + bytes;
+	we = ws_emu(ws);
+	DSL(DBG_WORKSPACE, 0, "WS_ReserveSize(%p, %u/%u) = %u",
+	    ws, bytes, we->len - wa->off, bytes);
+	WS_Assert(ws);
+	return (bytes);
+}
+
+unsigned
+WS_ReserveLumps(struct ws *ws, size_t sz)
+{
+	return (WS_ReserveAll(ws) / sz);
+}
+
+static void
+ws_release(struct ws *ws, unsigned bytes)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+
+	WS_Assert(ws);
+	AN(ws->f);
+	AN(ws->r);
+	we = ws_emu(ws);
+	wa = VTAILQ_LAST(&we->head, ws_alloc_head);
+	AN(wa);
+	assert(bytes <= wa->len);
+	ws->f = ws->r = NULL;
+
+	if (bytes == 0) {
+		ws_alloc_free(we, &wa);
+		return;
+	}
+
+	AN(wa->ptr);
+#ifdef ASAN_POISON_MEMORY_REGION
+	ASAN_POISON_MEMORY_REGION(wa->ptr + bytes, wa->len - bytes);
+#endif
+	wa->len = bytes;
+	WS_Assert(ws);
+}
+
+void
+WS_Release(struct ws *ws, unsigned bytes)
+{
+
+	ws_release(ws, bytes);
+	DSL(DBG_WORKSPACE, 0, "WS_Release(%p, %u)", ws, bytes);
+}
+
+void
+WS_ReleaseP(struct ws *ws, const char *ptr)
+{
+	unsigned l;
+
+	WS_Assert(ws);
+	assert(ws->r != NULL);
+	assert(ptr >= ws->f);
+	assert(ptr <= ws->r);
+	l = pdiff(ws->f, ptr);
+	ws_release(ws, l);
+	DSL(DBG_WORKSPACE, 0, "WS_ReleaseP(%p, %p (%u))", ws, ptr, l);
+}
+
+int
+WS_Overflowed(const struct ws *ws)
+{
+	CHECK_OBJ_NOTNULL(ws, WS_MAGIC);
+	AN(ws->id[0]);
+
+	if (ws->id[0] & 0x20)		// cheesy islower()
+		return (0);
+	return (1);
+}
+
+void *
+WS_AtOffset(const struct ws *ws, unsigned off, unsigned len)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+
+	WS_Assert(ws);
+	we = ws_emu(ws);
+
+	VTAILQ_FOREACH(wa, &we->head, list) {
+		if (wa->off == off) {
+			assert(wa->len >= len);
+			return (wa->ptr);
+		}
+	}
+
+	WRONG("invalid offset");
+	NEEDLESS(return (NULL));
+}
+
+unsigned
+WS_ReservationOffset(const struct ws *ws)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+
+	WS_Assert(ws);
+	AN(ws->f);
+	AN(ws->r);
+	we = ws_emu(ws);
+	wa = VTAILQ_LAST(&we->head, ws_alloc_head);
+	AN(wa);
+	return (wa->off);
+}
+
+void
+WS_VSB_new(struct vsb *vsb, struct ws *ws)
+{
+	unsigned u;
+	static char bogus[2];	// Smallest possible vsb
+
+	AN(vsb);
+	WS_Assert(ws);
+	u = WS_ReserveAll(ws);
+	if (WS_Overflowed(ws) || u < 2)
+		AN(VSB_init(vsb, bogus, sizeof bogus));
+	else
+		AN(VSB_init(vsb, WS_Reservation(ws), u));
+}
+
+char *
+WS_VSB_finish(struct vsb *vsb, struct ws *ws, size_t *szp)
+{
+	char *p;
+
+	AN(vsb);
+	WS_Assert(ws);
+	if (!VSB_finish(vsb)) {
+		p = VSB_data(vsb);
+		if (p == ws->f) {
+			WS_Release(ws, VSB_len(vsb) + 1);
+			if (szp != NULL)
+				*szp = VSB_len(vsb);
+			VSB_fini(vsb);
+			return (p);
+		}
+	}
+	WS_MarkOverflow(ws);
+	VSB_fini(vsb);
+	WS_Release(ws, 0);
+	if (szp)
+		*szp = 0;
+	return (NULL);
+}
+
+unsigned
+WS_Dump(const struct ws *ws, char where, size_t off, void *buf, size_t len)
+{
+	struct ws_emu *we;
+	struct ws_alloc *wa;
+	unsigned l, r;
+	char *b;
+
+	WS_Assert(ws);
+	AN(buf);
+	AN(len);
+
+	if (strchr("sfr", where) == NULL) {
+		errno = EINVAL;
+		return (0);
+	}
+
+	if (where == 'r' && ws->r == NULL) {
+		errno = EAGAIN;
+		return (0);
+	}
+
+	we = ws_emu(ws);
+	wa = VTAILQ_LAST(&we->head, ws_alloc_head);
+
+	l = we->len;
+	if (where != 's' && wa != NULL) {
+		l -= wa->off;
+		if (where == 'f')
+			l -= wa->len;
+	}
+
+	if (off > l) {
+		errno = EFAULT;
+		return (0);
+	}
+
+	b = buf;
+	r = 0;
+	if (where == 'f' && ws->r != NULL) {
+		if (l > len)
+			l = len;
+		memcpy(b, wa->ptr, l);
+		b += l;
+		r += l;
+		len -= l;
+	}
+
+	if (where == 's') {
+		VTAILQ_FOREACH(wa, &we->head, list) {
+			if (len == 0)
+				break;
+			if (wa->ptr == NULL)
+				break;
+			l = wa->len;
+			if (l > len)
+				l = len;
+			memcpy(b, wa->ptr, l);
+			b += l;
+			r += l;
+			len -= l;
+		}
+	}
+
+	if (len > 0)
+		memset(b, 0xa5, len);
+	return (l);
+}
+
+static void
+ws_emu_panic(struct vsb *vsb, const struct ws *ws)
+{
+	const struct ws_emu *we;
+	const struct ws_alloc *wa;
+
+	we = (void *)ws->s;
+	if (PAN_dump_once(vsb, we, WS_EMU_MAGIC, "ws_emu"))
+		return;
+	VSB_printf(vsb, "len = %u,\n", we->len);
+
+	VTAILQ_FOREACH(wa, &we->head, list) {
+		if (PAN_dump_once_oneline(vsb, wa, WS_ALLOC_MAGIC, "ws_alloc"))
+			break;
+		VSB_printf(vsb, "off, len, ptr} = {%u, %u, %p}\n",
+		    wa->off, wa->len, wa->ptr);
+	}
+
+	VSB_indent(vsb, -2);
+	VSB_cat(vsb, "},\n");
+}
+
+void
+WS_Panic(struct vsb *vsb, const struct ws *ws)
+{
+
+	if (PAN_dump_struct(vsb, ws, WS_MAGIC, "ws"))
+		return;
+	if (ws->id[0] != '\0' && (!(ws->id[0] & 0x20)))	// cheesy islower()
+		VSB_cat(vsb, "OVERFLOWED ");
+	VSB_printf(vsb, "id = \"%s\",\n", ws->id);
+	VSB_printf(vsb, "{s, e} = {%p", ws->s);
+	if (ws->e >= ws->s)
+		VSB_printf(vsb, ", +%ld", (long) (ws->e - ws->s));
+	else
+		VSB_printf(vsb, ", %p", ws->e);
+	VSB_cat(vsb, "},\n");
+	VSB_printf(vsb, "{f, r} = {%p", ws->f);
+	if (ws->r >= ws->f)
+		VSB_printf(vsb, ", +%ld", (long) (ws->r - ws->f));
+	else
+		VSB_printf(vsb, ", %p", ws->r);
+	VSB_cat(vsb, "},\n");
+
+	ws_emu_panic(vsb, ws);
+
+	VSB_indent(vsb, -2);
+	VSB_cat(vsb, "},\n");
+}
diff --git a/bin/varnishtest/tests/b00068.vtc b/bin/varnishtest/tests/b00068.vtc
index dea15e802..0559f393e 100644
--- a/bin/varnishtest/tests/b00068.vtc
+++ b/bin/varnishtest/tests/b00068.vtc
@@ -1,5 +1,7 @@
 varnishtest "Check timeout_linger"
 
+feature !workspace_emulator
+
 # XXX this test exploits the fact that the struct waited is
 # left near the free pointer of the session ws when a session
 # made a tour over the waiter
diff --git a/bin/varnishtest/tests/m00017.vtc b/bin/varnishtest/tests/m00017.vtc
index 4e2e8cdd8..57158b020 100644
--- a/bin/varnishtest/tests/m00017.vtc
+++ b/bin/varnishtest/tests/m00017.vtc
@@ -1,5 +1,8 @@
 varnishtest "Test std.rollback"
 
+# ws_emu triggers #3550
+feature !workspace_emulator
+
 # bug regressions:
 # - #3009
 # - #3083
diff --git a/bin/varnishtest/tests/r03353.vtc b/bin/varnishtest/tests/r03353.vtc
index d5ab5f1a4..864a2357f 100644
--- a/bin/varnishtest/tests/r03353.vtc
+++ b/bin/varnishtest/tests/r03353.vtc
@@ -1,5 +1,8 @@
 varnishtest "Test rollback and retry"
 
+# ws_emu triggers #3550
+feature !workspace_emulator
+
 server s1 {
 	rxreq
 	txresp -nolen -hdr "Content-Length: 3"
diff --git a/bin/varnishtest/vtc_misc.c b/bin/varnishtest/vtc_misc.c
index f72963428..9efe6fd5a 100644
--- a/bin/varnishtest/vtc_misc.c
+++ b/bin/varnishtest/vtc_misc.c
@@ -450,6 +450,8 @@ addr_no_randomize_works(void)
  *        Varnish was built with the undefined behavior sanitizer.
  * sanitizer
  *        Varnish was built with a sanitizer.
+ * workspace_emulator
+ *        Varnish was built with its workspace emulator.
  *
  * A feature name can be prefixed with an exclamation mark (!) to skip a
  * test if a feature is present.
@@ -495,6 +497,12 @@ static const unsigned sanitizer = 1;
 static const unsigned sanitizer = 0;
 #endif
 
+#if ENABLE_WORKSPACE_EMULATOR
+static const unsigned workspace_emulator = 1;
+#else
+static const unsigned workspace_emulator = 0;
+#endif
+
 #if WITH_PERSISTENT_STORAGE
 static const unsigned with_persistent_storage = 1;
 #else
@@ -560,6 +568,7 @@ cmd_feature(CMD_ARGS)
 		FEATURE("ubsan", ubsan);
 		FEATURE("sanitizer", sanitizer);
 		FEATURE("SO_RCVTIMEO_WORKS", so_rcvtimeo_works);
+		FEATURE("workspace_emulator", workspace_emulator);
 
 		if (!strcmp(feat, "cmd")) {
 			good = 1;
diff --git a/configure.ac b/configure.ac
index 4bcecd4bf..ccc5afd8f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -331,6 +331,20 @@ if test "x$UBSAN_FLAGS$TSAN_FLAGS$ASAN_FLAGS$MSAN_FLAGS" != "x"; then
 	LDFLAGS="$LDFLAGS $SAN_LDFLAGS"
 fi
 
+AC_ARG_ENABLE(workspace-emulator,
+	AS_HELP_STRING([--enable-workspace-emulator],
+		[emulate workspace allocations (default is NO)]),
+    [], [enable_workspace_emulator=no])
+
+AM_CONDITIONAL([ENABLE_WORKSPACE_EMULATOR],
+	[test "$enable_workspace_emulator" = yes])
+
+AM_COND_IF([ENABLE_WORKSPACE_EMULATOR], [
+	AC_CHECK_HEADERS([sanitizer/asan_interface.h])
+	AC_DEFINE([ENABLE_WORKSPACE_EMULATOR], [1],
+              [Define to 1 if the workspace emulator is enabled])
+])
+
 # Use jemalloc on Linux
 JEMALLOC_LDADD=
 AC_ARG_WITH([jemalloc],


More information about the varnish-commit mailing list