xref: /linux/tools/testing/selftests/futex/functional/futex_lock_pi_exiting.c (revision b844715e8aca2929c0a97a32b3a5650496b5872f)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /******************************************************************************
3  *
4  * futex_lock_pi_exiting.c
5  *
6  * Coverage for the FUTEX_LOCK_PI owner-exiting path.  futex_wait_timeout.c
7  * already covers FUTEX_LOCK_PI timeout semantics and robust_list.c covers
8  * owner death via the robust list, but nothing exercises FUTEX_LOCK_PI when a
9  * non-robust PI owner exits while holding the lock, nor the basic ownership /
10  * EDEADLK / unlock word semantics.
11  *
12  * DESCRIPTION
13  *      Three tests:
14  *
15  *      1. lock_unlock_basic - uncontended FUTEX_LOCK_PI semantics: the futex
16  *         word carries the owner TID, a recursive lock by the owner returns
17  *         EDEADLK, and FUTEX_UNLOCK_PI clears the word.
18  *
19  *      2. owner_dies_with_blocked_waiter - a thread acquires a PI futex and
20  *         exits while holding it.  do_exit() runs futex_cleanup_begin() (which
21  *         flips the task's futex state to FUTEX_STATE_EXITING) and
22  *         exit_pi_state_list() (which hands off / tears down the pi_state).  A
23  *         contending FUTEX_LOCK_PI waiter must end up in one of:
24  *
25  *           0          - ownership was transferred to / acquired by the waiter
26  *           EOWNERDEAD - previous owner died holding the lock; the caller is
27  *                        now the owner and must acknowledge by unlocking
28  *           ESRCH      - the owner encoded in the futex word is already gone
29  *
30  *         and on the first two it must actually own the lock afterwards.
31  *
32  *      3. stress_owner_exits - hammer that same exiting-owner path.  This is
33  *         where the following bug lived: the 'exiting' task pointer was not
34  *         reset at the retry label, so after wait_for_owner_exiting() dropped
35  *         its reference a subsequent retry that returned a non-EBUSY error fed
36  *         the stale pointer back in and tripped WARN_ON_ONCE(exiting).  That
37  *         warning is invisible to user space, so this test cannot observe it
38  *         through a syscall return value; it only becomes a visible failure
39  *         (crash) on a kernel booted with panic_on_warn=1 (or built with
40  *         CONFIG_BUG_ON_DATA_CORRUPTION).  The loop drives the path so that
41  *         such a kernel trips on it - the canonical way fuzz/CI catch these.
42  *
43  *        Fix:    210d36d892de ("futex: Clear stale exiting pointer in
44  *                              futex_lock_pi() retry path")
45  *        Fixes:  3ef240eaff36 ("futex: Prevent exit livelock")
46  *
47  * AUTHOR
48  *      Based on futex test boilerplate by Darren Hart <dvhart@linux.intel.com>
49  *
50  *****************************************************************************/
51 
52 #define _GNU_SOURCE
53 
54 #include <errno.h>
55 #include <pthread.h>
56 #include <stdint.h>
57 #include <string.h>
58 #include <unistd.h>
59 #include <sys/syscall.h>
60 
61 #include "futextest.h"
62 #include "kselftest_harness.h"
63 
64 /*
65  * Iterations for the stress variant.  Enough to repeatedly land in the narrow
66  * EXITING window while keeping the test fast.
67  */
68 #define STRESS_ITERS 1000
69 
70 static futex_t pi_futex;
71 static pthread_barrier_t locked_barrier;
72 static pthread_barrier_t release_barrier;
73 
74 static pid_t sys_gettid(void)
75 {
76 	return syscall(SYS_gettid);
77 }
78 
79 /*
80  * Owner thread: acquire the PI futex and exit while still holding it.  Two
81  * modes:
82  *   park == 0: signal that we hold the lock, then exit immediately (racy; the
83  *              waiter races against our exit path).
84  *   park == 1: signal that we hold the lock and keep holding until released
85  *              via release_barrier, so a waiter has time to contend as a real
86  *              PI waiter before we die.
87  */
88 static void *owner_thread(void *arg)
89 {
90 	long park = (long)arg;
91 
92 	if (futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG) != 0)
93 		return (void *)(intptr_t)-errno;
94 
95 	pthread_barrier_wait(&locked_barrier);
96 
97 	if (park)
98 		pthread_barrier_wait(&release_barrier);
99 
100 	/* Die while still holding the lock. */
101 	pthread_exit((void *)0);
102 }
103 
104 /*
105  * Block on the PI futex as a waiter.  Returns 0 on acquisition, otherwise the
106  * positive errno.
107  */
108 static int waiter_lock_pi(void)
109 {
110 	int ret = futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG);
111 
112 	return ret == 0 ? 0 : errno;
113 }
114 
115 static int outcome_ok(int outcome)
116 {
117 	return outcome == 0 || outcome == EOWNERDEAD || outcome == ESRCH;
118 }
119 
120 /* Results published by waiter_thread() for the owning thread to assert on. */
121 static int waiter_outcome;
122 static int waiter_owns;
123 
124 /*
125  * Waiter thread for the blocked-waiter test.  Contends for the lock and, when
126  * it acquires, records whether the futex word actually carries its TID and
127  * releases the lock itself (FUTEX_UNLOCK_PI must run in the owning thread).
128  */
129 static void *waiter_thread(void *arg)
130 {
131 	pid_t tid = sys_gettid();
132 
133 	waiter_outcome = waiter_lock_pi();
134 	if (waiter_outcome == 0 || waiter_outcome == EOWNERDEAD) {
135 		waiter_owns = (pi_futex & FUTEX_TID_MASK) == (futex_t)tid;
136 		futex_unlock_pi(&pi_futex, FUTEX_PRIVATE_FLAG);
137 	}
138 	return NULL;
139 }
140 
141 FIXTURE(lock_pi_exiting) {
142 };
143 
144 FIXTURE_SETUP(lock_pi_exiting) {
145 }
146 
147 FIXTURE_TEARDOWN(lock_pi_exiting) {
148 }
149 
150 /*
151  * Uncontended FUTEX_LOCK_PI semantics, fully deterministic.
152  */
153 TEST_F(lock_pi_exiting, lock_unlock_basic)
154 {
155 	pid_t tid = sys_gettid();
156 	int ret;
157 
158 	pi_futex = FUTEX_INITIALIZER;
159 
160 	/* Acquire: we become the owner, our TID lands in the futex word. */
161 	ret = futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG);
162 	ASSERT_EQ(ret, 0)
163 		TH_LOG("lock failed: errno=%d (%s)", errno, strerror(errno));
164 	ASSERT_EQ(pi_futex & FUTEX_TID_MASK, (futex_t)tid)
165 		TH_LOG("owner TID not in futex word: 0x%08x", pi_futex);
166 
167 	/* A recursive lock by the owner must be refused, not deadlock. */
168 	errno = 0;
169 	ret = futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG);
170 	ASSERT_EQ(ret, -1);
171 	ASSERT_EQ(errno, EDEADLK)
172 		TH_LOG("recursive lock: expected EDEADLK, got errno=%d", errno);
173 
174 	/* Release: the futex word is handed back clean. */
175 	ret = futex_unlock_pi(&pi_futex, FUTEX_PRIVATE_FLAG);
176 	ASSERT_EQ(ret, 0)
177 		TH_LOG("unlock failed: errno=%d", errno);
178 	ASSERT_EQ(pi_futex, (futex_t)0)
179 		TH_LOG("futex word not cleared after unlock: 0x%08x", pi_futex);
180 }
181 
182 /*
183  * A PI waiter inherits the lock when the owner dies holding it.
184  *
185  * The owner parks while holding the lock, this thread contends for it, then
186  * the owner exits.  The waiter must come out cleanly (no hang, no unexpected
187  * error) and, when it acquires, must actually own the lock.
188  */
189 TEST_F(lock_pi_exiting, owner_dies_with_blocked_waiter)
190 {
191 	pthread_t owner, waiter;
192 
193 	pthread_barrier_init(&locked_barrier, NULL, 2);
194 	pthread_barrier_init(&release_barrier, NULL, 2);
195 	pi_futex = FUTEX_INITIALIZER;
196 	waiter_outcome = -1;
197 	waiter_owns = 0;
198 
199 	ASSERT_EQ(pthread_create(&owner, NULL, owner_thread, (void *)1), 0);
200 
201 	/* Wait until the owner actually holds the lock. */
202 	pthread_barrier_wait(&locked_barrier);
203 
204 	/* Start the waiter and give it time to block as a real PI waiter. */
205 	ASSERT_EQ(pthread_create(&waiter, NULL, waiter_thread, NULL), 0);
206 	usleep(1000);
207 
208 	/* Release the owner so it dies while the waiter is queued on it. */
209 	pthread_barrier_wait(&release_barrier);
210 
211 	pthread_join(waiter, NULL);
212 	pthread_join(owner, NULL);
213 
214 	ASSERT_TRUE(outcome_ok(waiter_outcome)) {
215 		TH_LOG("unexpected FUTEX_LOCK_PI outcome: %d (%s)",
216 		       waiter_outcome, strerror(waiter_outcome));
217 	}
218 	if (waiter_outcome == 0 || waiter_outcome == EOWNERDEAD) {
219 		ASSERT_TRUE(waiter_owns)
220 			TH_LOG("waiter acquired but futex word lacks its TID");
221 	}
222 
223 	pthread_barrier_destroy(&locked_barrier);
224 	pthread_barrier_destroy(&release_barrier);
225 }
226 
227 /*
228  * Stress: repeatedly let an owner exit while a waiter contends for the lock.
229  *
230  * Each iteration drives the FUTEX_STATE_EXITING -> -EBUSY -> retry path that
231  * the stale-'exiting'-pointer bug lived on (210d36d892de).  The warning it
232  * fixed is invisible to user space, so on a normally-configured kernel both
233  * the buggy and fixed kernels pass here; the point is to make a kernel booted
234  * with panic_on_warn=1 trip during one of these iterations.
235  */
236 TEST_F(lock_pi_exiting, stress_owner_exits)
237 {
238 	for (int i = 0; i < STRESS_ITERS; i++) {
239 		pthread_t owner;
240 		int outcome;
241 
242 		pthread_barrier_init(&locked_barrier, NULL, 2);
243 		pi_futex = FUTEX_INITIALIZER;
244 
245 		ASSERT_EQ(pthread_create(&owner, NULL, owner_thread, (void *)0), 0);
246 
247 		/* Owner holds the lock; race FUTEX_LOCK_PI against its exit. */
248 		pthread_barrier_wait(&locked_barrier);
249 
250 		outcome = waiter_lock_pi();
251 		ASSERT_TRUE(outcome_ok(outcome)) {
252 			TH_LOG("iter %d: unexpected outcome %d (%s)",
253 			       i, outcome, strerror(outcome));
254 		}
255 		if (outcome == 0 || outcome == EOWNERDEAD)
256 			futex_unlock_pi(&pi_futex, FUTEX_PRIVATE_FLAG);
257 
258 		pthread_join(owner, NULL);
259 		pthread_barrier_destroy(&locked_barrier);
260 	}
261 }
262 
263 TEST_HARNESS_MAIN
264