1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2024 Oxide Computer Company
14  */
15 
16 /*
17  * Test various mutex types to determine whether we properly deadlock or
18  * generate an error when attempting to take the lock. Note, that the issues
19  * described in 16200 only occur for a single threaded program which this does
20  * not test.
21  */
22 
23 #include <err.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <pthread.h>
27 #include <thread.h>
28 #include <synch.h>
29 #include <stdbool.h>
30 #include <sys/sysmacros.h>
31 #include <unistd.h>
32 #include <libproc.h>
33 #include <string.h>
34 #include <sys/debug.h>
35 
36 typedef enum {
37 	MUTEX_TEST_F_USE_ATTR	= 1 << 0,
38 	MUTEX_TEST_F_SET_TYPE	= 1 << 1,
39 	MUTEX_TEST_F_DEADLOCK	= 1 << 2,
40 	MUTEX_TEST_F_ILLUMOS	= 1 << 3,
41 	MUTEX_TEST_F_UNLOCK	= 1 << 4
42 } mutex_test_flags_t;
43 
44 typedef struct {
45 	const char *mt_desc;
46 	mutex_test_flags_t mt_flags;
47 	int mt_type;
48 	int mt_ret;
49 } mutex_test_t;
50 
51 const mutex_test_t mutex_tests[] = {
52 	{
53 		.mt_desc = "pthread attr NULL",
54 		.mt_flags = MUTEX_TEST_F_DEADLOCK,
55 		.mt_ret = INT32_MIN
56 	}, {
57 		.mt_desc = "pthread attr unset",
58 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_DEADLOCK,
59 		.mt_ret = INT32_MIN
60 	}, {
61 		.mt_desc = "pthrad attr default",
62 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
63 		    MUTEX_TEST_F_DEADLOCK,
64 		.mt_type = PTHREAD_MUTEX_DEFAULT,
65 		.mt_ret = INT32_MIN
66 	}, {
67 		.mt_desc = "pthread attr normal",
68 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
69 		    MUTEX_TEST_F_DEADLOCK,
70 		.mt_type = PTHREAD_MUTEX_NORMAL,
71 		/* Set to a value that we should never see or get to */
72 		.mt_ret = INT32_MIN
73 	}, {
74 		.mt_desc = "pthread attr recursive",
75 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE,
76 		.mt_type = PTHREAD_MUTEX_RECURSIVE,
77 		.mt_ret = 0
78 	}, {
79 		.mt_desc = "pthread attr errorcheck",
80 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE,
81 		.mt_type = PTHREAD_MUTEX_ERRORCHECK,
82 		.mt_ret = EDEADLK
83 	}, {
84 		.mt_desc = "pthread attr recursive unlock",
85 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
86 		    MUTEX_TEST_F_UNLOCK,
87 		.mt_type = PTHREAD_MUTEX_RECURSIVE,
88 		.mt_ret = EPERM
89 	}, {
90 		.mt_desc = "pthread attr errorcheck unlock",
91 		.mt_flags = MUTEX_TEST_F_USE_ATTR | MUTEX_TEST_F_SET_TYPE |
92 		    MUTEX_TEST_F_UNLOCK,
93 		.mt_type = PTHREAD_MUTEX_ERRORCHECK,
94 		.mt_ret = EPERM
95 	}, {
96 		.mt_desc = "illumos USYNC_THREAD",
97 		.mt_flags = MUTEX_TEST_F_DEADLOCK | MUTEX_TEST_F_ILLUMOS,
98 		.mt_type = USYNC_THREAD,
99 		.mt_ret = INT32_MAX
100 	}, {
101 		.mt_desc = "illumos error check",
102 		.mt_flags = MUTEX_TEST_F_ILLUMOS,
103 		.mt_type = USYNC_THREAD | LOCK_ERRORCHECK,
104 		.mt_ret = EDEADLK
105 	}, {
106 		.mt_desc = "illumos recursive",
107 		.mt_flags = MUTEX_TEST_F_ILLUMOS,
108 		.mt_type = USYNC_THREAD | LOCK_RECURSIVE,
109 		.mt_ret = 0
110 	}, {
111 		.mt_desc = "illumos recursive error check",
112 		.mt_flags = MUTEX_TEST_F_ILLUMOS,
113 		.mt_type = USYNC_THREAD | LOCK_RECURSIVE | LOCK_ERRORCHECK,
114 		.mt_ret = 0
115 	}, {
116 		.mt_desc = "illumos error check unlock",
117 		.mt_flags = MUTEX_TEST_F_ILLUMOS | MUTEX_TEST_F_UNLOCK,
118 		.mt_type = USYNC_THREAD | LOCK_ERRORCHECK,
119 		.mt_ret = EPERM
120 	}, {
121 		.mt_desc = "illumos recursive error check unlock",
122 		.mt_flags = MUTEX_TEST_F_ILLUMOS | MUTEX_TEST_F_UNLOCK,
123 		.mt_type = USYNC_THREAD | LOCK_RECURSIVE | LOCK_ERRORCHECK,
124 		.mt_ret = EPERM
125 	}
126 };
127 
128 static void *
mutex_test_thr(void * arg)129 mutex_test_thr(void *arg)
130 {
131 	int ret;
132 	pthread_mutexattr_t attr, *attrp = NULL;
133 	const mutex_test_t *test = arg;
134 
135 	if ((test->mt_flags & MUTEX_TEST_F_USE_ATTR) != 0) {
136 		VERIFY0(pthread_mutexattr_init(&attr));
137 		attrp = &attr;
138 
139 		if ((test->mt_flags & MUTEX_TEST_F_SET_TYPE) != 0) {
140 			VERIFY0(pthread_mutexattr_settype(&attr,
141 			    test->mt_type));
142 		}
143 	}
144 
145 	if ((test->mt_flags & MUTEX_TEST_F_UNLOCK) != 0) {
146 		if ((test->mt_flags & MUTEX_TEST_F_ILLUMOS) != 0) {
147 			mutex_t m;
148 
149 			VERIFY0(mutex_init(&m, test->mt_type, NULL));
150 			ret = mutex_unlock(&m);
151 		} else {
152 			pthread_mutex_t pm;
153 
154 			VERIFY0(pthread_mutex_init(&pm, attrp));
155 			ret = pthread_mutex_unlock(&pm);
156 		}
157 
158 		return ((void *)(uintptr_t)ret);
159 	}
160 
161 	if ((test->mt_flags & MUTEX_TEST_F_ILLUMOS) != 0) {
162 		mutex_t m;
163 
164 		VERIFY0(mutex_init(&m, test->mt_type, NULL));
165 		VERIFY0(mutex_lock(&m));
166 		ret = mutex_lock(&m);
167 	} else {
168 		pthread_mutex_t pm;
169 
170 		VERIFY0(pthread_mutex_init(&pm, attrp));
171 		VERIFY0(pthread_mutex_lock(&pm));
172 		ret = pthread_mutex_lock(&pm);
173 	}
174 
175 	return ((void *)(uintptr_t)ret);
176 }
177 
178 
179 /*
180  * Attempt to determine if a thread is still going and we should wait, if it has
181  * potentially terminated, or if it is blocked in lwp_park() suggesting it has
182  * been deadlocked.
183  */
184 typedef enum {
185 	THR_STATE_PARKED,
186 	THR_STATE_DEAD,
187 	THR_STATE_RUNNING
188 } thr_state_t;
189 
190 static thr_state_t
mutex_test_thr_state(thread_t thr)191 mutex_test_thr_state(thread_t thr)
192 {
193 	lwpstatus_t lwp;
194 	char name[SYS2STR_MAX];
195 
196 	if (proc_get_lwpstatus(getpid(), (uint_t)thr, &lwp) != 0) {
197 		int e = errno;
198 		switch (e) {
199 		case ENOENT:
200 			return (THR_STATE_DEAD);
201 		default:
202 			errc(EXIT_FAILURE, e, "fatal error: got unexpected "
203 			    "error while trying to get lwpstatus");
204 		}
205 	}
206 
207 	if ((lwp.pr_flags & PR_ASLEEP) == 0) {
208 		return (THR_STATE_RUNNING);
209 	}
210 
211 	if (proc_sysname(lwp.pr_syscall, name, sizeof (name)) == 0) {
212 		return (THR_STATE_RUNNING);
213 	}
214 
215 	if (strcmp(name, "lwp_park") == 0) {
216 		return (THR_STATE_PARKED);
217 	}
218 
219 	return (THR_STATE_RUNNING);
220 }
221 
222 static bool
mutex_test_run_one(const mutex_test_t * test)223 mutex_test_run_one(const mutex_test_t *test)
224 {
225 	int err, lock;
226 	thread_t thr;
227 	thr_state_t state;
228 	void *val;
229 
230 	err = thr_create(NULL, 0, mutex_test_thr, (void *)test, 0, &thr);
231 	if (err != 0) {
232 		errc(EXIT_FAILURE, err, "fatal test error: could not create "
233 		    "thread for %s", test->mt_desc);
234 	}
235 
236 	/*
237 	 * Wait for the thread to deadlock or exit and then continue.
238 	 */
239 	while ((state = mutex_test_thr_state(thr)) == THR_STATE_RUNNING) {
240 		struct timespec sleep;
241 
242 		sleep.tv_sec = 0;
243 		sleep.tv_nsec = MSEC2NSEC(10);
244 		(void) nanosleep(&sleep, NULL);
245 	}
246 
247 	if (state == THR_STATE_PARKED) {
248 		if ((test->mt_flags & MUTEX_TEST_F_DEADLOCK) != 0) {
249 			(void) printf("TEST PASSED: %s: successfully "
250 			    "deadlocked\n", test->mt_desc);
251 			return (true);
252 		}
253 
254 		(void) sleep(100000);
255 
256 		warnx("TEST FAILED: %s: thread deadlocked, but expected return "
257 		    "value %d", test->mt_desc, test->mt_ret);
258 		return (false);
259 	}
260 
261 	VERIFY0(thr_join(thr, NULL, &val));
262 	lock = (int)(uintptr_t)val;
263 	if ((test->mt_flags & MUTEX_TEST_F_DEADLOCK) != 0) {
264 		warnx("TEST FAILED: %s: expected deadlock, but mutex lock "
265 		    "returned %d", test->mt_desc, lock);
266 		return (false);
267 	} else if (lock != test->mt_ret) {
268 		warnx("TEST FAILED: %s: found return value %d, expected %d",
269 		    test->mt_desc, lock, test->mt_ret);
270 		return (false);
271 	} else {
272 		(void) printf("TEST PASSED: %s: got correct lock return value "
273 		    "(%d)\n", test->mt_desc, test->mt_ret);
274 		return (true);
275 	}
276 }
277 
278 int
main(void)279 main(void)
280 {
281 	int ret = EXIT_SUCCESS;
282 
283 	if (getenv("_THREAD_ASYNC_SAFE") != NULL) {
284 		errx(EXIT_FAILURE, "cannot run tests because "
285 		    "_THREAD_ASYNC_SAFE is set in the environment!");
286 	}
287 
288 	for (size_t i = 0; i < ARRAY_SIZE(mutex_tests); i++) {
289 		if (!mutex_test_run_one(&mutex_tests[i])) {
290 			ret = EXIT_FAILURE;
291 		}
292 	}
293 
294 	/*
295 	 * Ensure any lingering threads don't keep us around.
296 	 */
297 	exit(ret);
298 }
299