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