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