[PATCH] selftests/futex: Add test for robust exit with a foreign futex owner

Zhan Xusheng <[email protected]>
Newsgroups org.kernel.vger.linux-kselftest,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
commit 6d4514ca9cdf ("futex: Prevent robust futex exit race some more")
fixed a lost wakeup in handle_futex_death(): when a robust waiter dies
mid-lock (list_op_pending set) while the futex is owned by a third thread
with FUTEX_WAITERS clear, the kernel must still wake the next waiter,
otherwise a waiter parked behind that futex sleeps forever. Since the
dying thread is not the owner, FUTEX_OWNER_DIED must not be set either.

Add a regression test to robust_list.c reproducing this "state B"
sequence: a waiter parks on a futex whose word is a foreign TID without
FUTEX_WAITERS, and a robust thread then dies with that futex in its
list_op_pending. It reuses the existing robust-list scaffolding.

Without the fix the wakeup is lost and futex_wait() times out at
FUTEX_TIMEOUT; with it handle_futex_death() issues the wakeup. Checked
both ways: the test fails on a kernel without the fix and passes on one
with it.

Signed-off-by: Zhan Xusheng <[email protected]>
---
 .../selftests/futex/functional/robust_list.c  | 79 +++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/tools/testing/selftests/futex/functional/robust_list.c b/tools/testing/selftests/futex/functional/robust_list.c
index b3fab60181d5..be8149cc67f8 100644
--- a/tools/testing/selftests/futex/functional/robust_list.c
+++ b/tools/testing/selftests/futex/functional/robust_list.c
@@ -434,6 +434,85 @@ TEST(test_set_list_op_pending)
 		ksft_test_result_fail("%s\n", __func__);
 }
 
+/*
+ * State B reproducer for commit 6d4514ca9cdf ("futex: Prevent robust futex
+ * exit race some more").
+ *
+ * Model the sequence where the original owner unlocked and woke us, a third
+ * thread then took the futex uncontended (so the futex word holds a foreign
+ * TID with FUTEX_WAITERS clear), and we die mid-lock before acquiring it or
+ * re-arming FUTEX_WAITERS. handle_futex_death() must still wake the next
+ * waiter, otherwise a waiter parked behind a foreign-owned futex whose
+ * FUTEX_WAITERS bit was lost sleeps forever. As the dying thread is not the
+ * owner, it must not set FUTEX_OWNER_DIED either.
+ */
+#define FOREIGN_TID 0x00c0ffee
+
+static int child_fn_foreign_owner(void *arg)
+{
+	struct lock_struct *lock = arg;
+	struct robust_list_head head;
+
+	if (set_list(&head)) {
+		ksft_test_result_fail("set_robust_list error\n");
+		return -1;
+	}
+	if ((unsigned int)gettid() == FOREIGN_TID) {
+		ksft_test_result_fail("TID collided with FOREIGN_TID\n");
+		return -1;
+	}
+
+	/* We are mid-lock ... */
+	head.list_op_pending = &lock->list;
+	/* ... and the futex is owned by a third thread, FUTEX_WAITERS clear. */
+	atomic_store(&lock->futex, FOREIGN_TID);
+
+	/* Let the parent enter futex_wait() before we die (see child_fn_lock). */
+	pthread_barrier_wait(&barrier);
+	usleep(SLEEP_US);
+
+	return 0;
+}
+
+/*
+ * A waiter is parked on a futex whose word is a foreign TID with no
+ * FUTEX_WAITERS bit (state B). A robust thread dies with that futex in its
+ * list_op_pending. The kernel must wake the parked waiter even though the
+ * dying thread does not own the futex, and it must not set FUTEX_OWNER_DIED.
+ */
+TEST(test_robust_death_foreign_owner_wakeup)
+{
+	struct lock_struct lock = { .futex = 0 };
+	struct timespec to = { .tv_sec = FUTEX_TIMEOUT, .tv_nsec = 0 };
+	int ret, pid, wstatus;
+
+	ret = pthread_barrier_init(&barrier, NULL, 2);
+	ASSERT_EQ(ret, 0);
+
+	pid = create_child(&child_fn_foreign_owner, &lock);
+	ASSERT_NE(pid, -1);
+
+	pthread_barrier_wait(&barrier);
+
+	/*
+	 * *futex == FOREIGN_TID here, so this blocks. With the fix the dying
+	 * child's robust walk wakes us; without it, futex_wait() times out.
+	 */
+	ret = futex_wait((futex_t *)&lock.futex, FOREIGN_TID, &to, 0);
+	ASSERT_EQ(ret, 0);
+
+	/* The dying thread was not the owner: OWNER_DIED must not be set. */
+	ASSERT_FALSE(lock.futex & FUTEX_OWNER_DIED);
+
+	wait(&wstatus);
+	pthread_barrier_destroy(&barrier);
+
+	if (!WEXITSTATUS(wstatus))
+		ksft_test_result_pass("%s\n", __func__);
+	else
+		ksft_test_result_fail("%s\n", __func__);
+}
+
 #define CHILD_NR 10
 
 static int child_lock_holder(void *arg)
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.