1#!/bin/sh
2
3#
4# Copyright (c) 2016 EMC Corp.
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27#
28
29# uma_zalloc_arc() fail point test scenario.
30# Test scenario by Ryan Libby <rlibby@gmail.com>.
31
32# "panic: backing_object 0xfffff8016dd74420 was somehow re-referenced during
33#     collapse!" seen.
34# https://people.freebsd.org/~pho/stress/log/uma_zalloc_arg.txt
35
36# Hang seen:
37# https://people.freebsd.org/~pho/stress/log/kostik869.txt
38
39[ `id -u ` -ne 0 ] && echo "Must be root!" && exit 1
40
41. ../default.cfg
42
43sysctl debug.mnowait_failure.zalloc_whitelist > /dev/null 2>&1 || exit 0
44
45s1=`sysctl -n debug.mnowait_failure.zalloc_whitelist`
46s2=`sysctl -n debug.fail_point.uma_zalloc_arg`
47cleanup() {
48	sysctl debug.mnowait_failure.zalloc_whitelist="$s1" > /dev/null
49	sysctl debug.fail_point.uma_zalloc_arg="$s2" > /dev/null
50}
51trap "cleanup" EXIT INT
52sysctl debug.mnowait_failure.zalloc_whitelist='RADIX NODE'
53sysctl debug.fail_point.uma_zalloc_arg='1%return'
54
55start=`date '+%s'`
56while [ $((`date '+%s'` - start)) -lt 300 ]; do
57	sh -c "echo | cat | cat > /dev/null"
58done
59exit 0
60
61The patch from
62https://github.com/freebsd/freebsd/compare/master...rlibby:mnowait-dbg
63
64diff --git a/sys/kern/kern_malloc.c b/sys/kern/kern_malloc.c
65index 01aff78..9d557a1 100644
66--- a/sys/kern/kern_malloc.c
67+++ b/sys/kern/kern_malloc.c
68@@ -50,6 +50,7 @@ __FBSDID("$FreeBSD$");
69
70 #include <sys/param.h>
71 #include <sys/systm.h>
72+#include <sys/fail.h>
73 #include <sys/kdb.h>
74 #include <sys/kernel.h>
75 #include <sys/lock.h>
76@@ -472,6 +473,19 @@ malloc(unsigned long size, struct malloc_type *mtp, int flags)
77 		}
78 	}
79 #endif
80+	if (flags & M_NOWAIT) {
81+		KFAIL_POINT_CODE(DEBUG_FP, malloc, {
82+			if (uma_dbg_nowait_fail_enabled_malloc(
83+			    mtp->ks_shortdesc)) {
84+				/* XXX record call stack */
85+#ifdef MALLOC_MAKE_FAILURES
86+				atomic_add_int(&malloc_failure_count, 1);
87+				t_malloc_fail = time_uptime;
88+#endif
89+				return (NULL);
90+			}
91+		});
92+	}
93 	if (flags & M_WAITOK)
94 		KASSERT(curthread->td_intr_nesting_level == 0,
95 		   ("malloc(M_WAITOK) in interrupt context"));
96diff --git a/sys/vm/uma_core.c b/sys/vm/uma_core.c
97index 1f57dff..dfa18e6 100644
98--- a/sys/vm/uma_core.c
99+++ b/sys/vm/uma_core.c
100@@ -64,6 +64,7 @@ __FBSDID("$FreeBSD$");
101 #include <sys/param.h>
102 #include <sys/systm.h>
103 #include <sys/bitset.h>
104+#include <sys/fail.h>
105 #include <sys/kernel.h>
106 #include <sys/types.h>
107 #include <sys/queue.h>
108@@ -2148,6 +2149,23 @@ uma_zalloc_arg(uma_zone_t zone, void *udata, int flags)
109 	if (flags & M_WAITOK) {
110 		WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL,
111 		    "uma_zalloc_arg: zone \"%s\"", zone->uz_name);
112+	} else {
113+		KFAIL_POINT_CODE(DEBUG_FP, uma_zalloc_arg, {
114+			/*
115+			 * XXX hack.  Setting the fail point to 0 (default)
116+			 * causes it to ignore malloc zones, nonzero causes it
117+			 * to inject failures for malloc zones regardless of
118+			 * the malloc black/white lists.
119+			 */
120+			if (((zone->uz_flags & UMA_ZONE_MALLOC) == 0 ||
121+			    RETURN_VALUE != 0) &&
122+			    uma_dbg_nowait_fail_enabled_zalloc(
123+			    zone->uz_name)) {
124+				/* XXX record call stack */
125+				atomic_add_long(&zone->uz_fails, 1);
126+				return NULL;
127+			}
128+		});
129 	}
130
131 	KASSERT(curthread->td_critnest == 0,
132diff --git a/sys/vm/uma_dbg.c b/sys/vm/uma_dbg.c
133index 3fbd29b..bed3130 100644
134--- a/sys/vm/uma_dbg.c
135+++ b/sys/vm/uma_dbg.c
136@@ -42,6 +42,8 @@ __FBSDID("$FreeBSD$");
137 #include <sys/lock.h>
138 #include <sys/mutex.h>
139 #include <sys/malloc.h>
140+#include <sys/rwlock.h>
141+#include <sys/sysctl.h>
142
143 #include <vm/vm.h>
144 #include <vm/vm_object.h>
145@@ -292,4 +294,143 @@ uma_dbg_free(uma_zone_t zone, uma_slab_t slab, void *item)
146 	BIT_CLR_ATOMIC(SLAB_SETSIZE, freei, &slab->us_debugfree);
147 }
148
149+/* XXX explain */
150+struct rwlock g_uma_dbg_nowait_lock;
151+RW_SYSINIT(uma_dbg_nowait, &g_uma_dbg_nowait_lock, "uma dbg nowait");
152+
153+#define NOWAIT_FAIL_LIST_BUFSIZE 4096
154+char malloc_fail_blacklist[NOWAIT_FAIL_LIST_BUFSIZE] = "kobj";
155+char malloc_fail_whitelist[NOWAIT_FAIL_LIST_BUFSIZE] = "";
156+char zalloc_fail_blacklist[NOWAIT_FAIL_LIST_BUFSIZE] =
157+    "BUF TRIE,ata_request,sackhole";
158+char zalloc_fail_whitelist[NOWAIT_FAIL_LIST_BUFSIZE] = "";
159+
160+static bool
161+str_in_list(const char *list, char delim, const char *str)
162+{
163+       const char *b, *e;
164+       size_t blen, slen;
165+
166+       b = list;
167+       slen = strlen(str);
168+       for (;;) {
169+               e = strchr(b, delim);
170+               blen = e == NULL ? strlen(b) : e - b;
171+               if (blen == slen && strncmp(b, str, slen) == 0)
172+                       return true;
173+               if (e == NULL)
174+                       break;
175+               b = e + 1;
176+       }
177+       return false;
178+}
179+
180+static bool
181+uma_dbg_nowait_fail_enabled_internal(const char *blacklist,
182+    const char *whitelist, const char *name)
183+{
184+	bool fail;
185+
186+	/* Protect ourselves from the sysctl handlers. */
187+	rw_rlock(&g_uma_dbg_nowait_lock);
188+	if (whitelist[0] == '\0')
189+		fail = !str_in_list(blacklist, ',', name);
190+	else
191+		fail = str_in_list(whitelist, ',', name);
192+	rw_runlock(&g_uma_dbg_nowait_lock);
193+
194+	return fail;
195+}
196+
197+bool
198+uma_dbg_nowait_fail_enabled_malloc(const char *name)
199+{
200+	return uma_dbg_nowait_fail_enabled_internal(malloc_fail_blacklist,
201+	    malloc_fail_whitelist, name);
202+}
203+
204+bool
205+uma_dbg_nowait_fail_enabled_zalloc(const char *name)
206+{
207+	return uma_dbg_nowait_fail_enabled_internal(zalloc_fail_blacklist,
208+	    zalloc_fail_whitelist, name);
209+}
210+
211+/*
212+ * XXX provide SYSCTL_STRING_LOCKED / sysctl_string_locked_handler?
213+ * This is basically just a different sysctl_string_handler.  This one wraps
214+ * the string manipulation in a lock and in a way that will not cause a sleep
215+ * under that lock.
216+ */
217+static int
218+sysctl_debug_mnowait_failure_list(SYSCTL_HANDLER_ARGS)
219+{
220+	char *newbuf = NULL;
221+	int error, newlen;
222+	bool have_lock = false;
223+
224+	if (req->newptr != NULL) {
225+		newlen = req->newlen - req->newidx;
226+		if (newlen >= arg2) {
227+			error = EINVAL;
228+			goto out;
229+		}
230+		newbuf = malloc(newlen, M_TEMP, M_WAITOK);
231+		error = SYSCTL_IN(req, newbuf, newlen);
232+		if (error != 0)
233+			goto out;
234+	}
235+
236+	error = sysctl_wire_old_buffer(req, arg2);
237+	if (error != 0)
238+		goto out;
239+
240+	rw_wlock(&g_uma_dbg_nowait_lock);
241+	have_lock = true;
242+
243+	error = SYSCTL_OUT(req, arg1, strnlen(arg1, arg2 - 1) + 1);
244+	if (error != 0)
245+		goto out;
246+
247+	if (newbuf == NULL)
248+		goto out;
249+
250+	bcopy(newbuf, arg1, newlen);
251+	((char *)arg1)[newlen] = '\0';
252+ out:
253+	if (have_lock)
254+		rw_wunlock(&g_uma_dbg_nowait_lock);
255+	free(newbuf, M_TEMP);
256+	return error;
257+}
258+
259+SYSCTL_NODE(_debug, OID_AUTO, mnowait_failure, CTLFLAG_RW, 0,
260+    "Control of M_NOWAIT memory allocation failure injection.");
261+
262+SYSCTL_PROC(_debug_mnowait_failure, OID_AUTO, malloc_blacklist,
263+    CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, malloc_fail_blacklist,
264+    sizeof(malloc_fail_blacklist), sysctl_debug_mnowait_failure_list, "A",
265+    "With debug.fail_point.malloc and with an empty whitelist, CSV list of "
266+    "zones which remain unaffected.");
267+
268+SYSCTL_PROC(_debug_mnowait_failure, OID_AUTO, malloc_whitelist,
269+    CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, malloc_fail_whitelist,
270+    sizeof(malloc_fail_whitelist), sysctl_debug_mnowait_failure_list, "A",
271+    "With debug.fail_point.malloc, CSV list of zones exclusively affected.  "
272+    "With an empty whitelist, all zones but those on the blacklist"
273+    "are affected.");
274+
275+SYSCTL_PROC(_debug_mnowait_failure, OID_AUTO, zalloc_blacklist,
276+    CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, zalloc_fail_blacklist,
277+    sizeof(zalloc_fail_blacklist), sysctl_debug_mnowait_failure_list, "A",
278+    "With debug.fail_point.uma_zalloc_arg and with an empty whitelist, CSV "
279+    "list of zones which remain unaffected.");
280+
281+SYSCTL_PROC(_debug_mnowait_failure, OID_AUTO, zalloc_whitelist,
282+    CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, zalloc_fail_whitelist,
283+    sizeof(zalloc_fail_whitelist), sysctl_debug_mnowait_failure_list, "A",
284+    "With debug.fail_point.uma_zalloc_arg, CSV list of zones exclusively "
285+    "affected.  With an empty whitelist, all zones but those on the blacklist"
286+    "are affected.");
287+
288 #endif /* INVARIANTS */
289diff --git a/sys/vm/uma_int.h b/sys/vm/uma_int.h
290index ad2a405..284747f 100644
291--- a/sys/vm/uma_int.h
292+++ b/sys/vm/uma_int.h
293@@ -427,6 +427,9 @@ vsetslab(vm_offset_t va, uma_slab_t slab)
294 void *uma_small_alloc(uma_zone_t zone, vm_size_t bytes, uint8_t *pflag,
295     int wait);
296 void uma_small_free(void *mem, vm_size_t size, uint8_t flags);
297+
298+bool uma_dbg_nowait_fail_enabled_malloc(const char *name);
299+bool uma_dbg_nowait_fail_enabled_zalloc(const char *name);
300 #endif /* _KERNEL */
301
302 #endif /* VM_UMA_INT_H */
303