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