Commit c574fb2e authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'locking-futex-2025-09-29' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull futex updates from Thomas Gleixner:
 "A set of updates for futexes and related selftests:

   - Plug the ptrace_may_access() race against a concurrent exec() which
     allows to pass the check before the target's process transition in
     exec() by taking a read lock on signal->ext_update_lock.

   - A large set of cleanups and enhancement to the futex selftests. The
     bulk of changes is the conversion to the kselftest harness"

* tag 'locking-futex-2025-09-29' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (25 commits)
  selftest/futex: Fix spelling mistake "boundarie" -> "boundary"
  selftests/futex: Remove logging.h file
  selftests/futex: Drop logging.h include from futex_numa
  selftests/futex: Refactor futex_numa_mpol with kselftest_harness.h
  selftests/futex: Refactor futex_priv_hash with kselftest_harness.h
  selftests/futex: Refactor futex_waitv with kselftest_harness.h
  selftests/futex: Refactor futex_requeue with kselftest_harness.h
  selftests/futex: Refactor futex_wait with kselftest_harness.h
  selftests/futex: Refactor futex_wait_private_mapped_file with kselftest_harness.h
  selftests/futex: Refactor futex_wait_unitialized_heap with kselftest_harness.h
  selftests/futex: Refactor futex_wait_wouldblock with kselftest_harness.h
  selftests/futex: Refactor futex_wait_timeout with kselftest_harness.h
  selftests/futex: Refactor futex_requeue_pi_signal_restart with kselftest_harness.h
  selftests/futex: Refactor futex_requeue_pi_mismatched_ops with kselftest_harness.h
  selftests/futex: Refactor futex_requeue_pi with kselftest_harness.h
  selftests: kselftest: Create ksft_print_dbg_msg()
  futex: Don't leak robust_list pointer on exec race
  selftest/futex: Compile also with libnuma < 2.0.16
  selftest/futex: Reintroduce "Memory out of range" numa_mpol's subtest
  selftest/futex: Make the error check more precise for futex_numa_mpol
  ...
parents d8de3685 4386f716
Loading
Loading
Loading
Loading
+56 −50
Original line number Diff line number Diff line
@@ -39,46 +39,74 @@ SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head,
	return 0;
}

/**
 * sys_get_robust_list() - Get the robust-futex list head of a task
 * @pid:	pid of the process [zero for current task]
 * @head_ptr:	pointer to a list-head pointer, the kernel fills it in
 * @len_ptr:	pointer to a length field, the kernel fills in the header size
 */
SYSCALL_DEFINE3(get_robust_list, int, pid,
		struct robust_list_head __user * __user *, head_ptr,
		size_t __user *, len_ptr)
static inline void __user *futex_task_robust_list(struct task_struct *p, bool compat)
{
	struct robust_list_head __user *head;
	unsigned long ret;
	struct task_struct *p;
#ifdef CONFIG_COMPAT
	if (compat)
		return p->compat_robust_list;
#endif
	return p->robust_list;
}

	rcu_read_lock();
static void __user *futex_get_robust_list_common(int pid, bool compat)
{
	struct task_struct *p = current;
	void __user *head;
	int ret;

	ret = -ESRCH;
	if (!pid)
		p = current;
	else {
	scoped_guard(rcu) {
		if (pid) {
			p = find_task_by_vpid(pid);
			if (!p)
			goto err_unlock;
				return (void __user *)ERR_PTR(-ESRCH);
		}
		get_task_struct(p);
	}

	/*
	 * Hold exec_update_lock to serialize with concurrent exec()
	 * so ptrace_may_access() is checked against stable credentials
	 */
	ret = down_read_killable(&p->signal->exec_update_lock);
	if (ret)
		goto err_put;

	ret = -EPERM;
	if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
		goto err_unlock;

	head = p->robust_list;
	rcu_read_unlock();
	head = futex_task_robust_list(p, compat);

	if (put_user(sizeof(*head), len_ptr))
		return -EFAULT;
	return put_user(head, head_ptr);
	up_read(&p->signal->exec_update_lock);
	put_task_struct(p);

	return head;

err_unlock:
	rcu_read_unlock();
	up_read(&p->signal->exec_update_lock);
err_put:
	put_task_struct(p);
	return (void __user *)ERR_PTR(ret);
}

	return ret;
/**
 * sys_get_robust_list() - Get the robust-futex list head of a task
 * @pid:	pid of the process [zero for current task]
 * @head_ptr:	pointer to a list-head pointer, the kernel fills it in
 * @len_ptr:	pointer to a length field, the kernel fills in the header size
 */
SYSCALL_DEFINE3(get_robust_list, int, pid,
		struct robust_list_head __user * __user *, head_ptr,
		size_t __user *, len_ptr)
{
	struct robust_list_head __user *head = futex_get_robust_list_common(pid, false);

	if (IS_ERR(head))
		return PTR_ERR(head);

	if (put_user(sizeof(*head), len_ptr))
		return -EFAULT;
	return put_user(head, head_ptr);
}

long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
@@ -455,36 +483,14 @@ COMPAT_SYSCALL_DEFINE3(get_robust_list, int, pid,
			compat_uptr_t __user *, head_ptr,
			compat_size_t __user *, len_ptr)
{
	struct compat_robust_list_head __user *head;
	unsigned long ret;
	struct task_struct *p;
	struct compat_robust_list_head __user *head = futex_get_robust_list_common(pid, true);

	rcu_read_lock();

	ret = -ESRCH;
	if (!pid)
		p = current;
	else {
		p = find_task_by_vpid(pid);
		if (!p)
			goto err_unlock;
	}

	ret = -EPERM;
	if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS))
		goto err_unlock;

	head = p->compat_robust_list;
	rcu_read_unlock();
	if (IS_ERR(head))
		return PTR_ERR(head);

	if (put_user(sizeof(*head), len_ptr))
		return -EFAULT;
	return put_user(ptr_to_compat(head), head_ptr);

err_unlock:
	rcu_read_unlock();

	return ret;
}
#endif /* CONFIG_COMPAT */

+5 −3
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0
PKG_CONFIG ?= pkg-config
LIBNUMA_TEST = $(shell sh -c "$(PKG_CONFIG) numa --atleast-version 2.0.16 > /dev/null 2>&1 && echo SUFFICIENT || echo NO")

INCLUDES := -I../include -I../../ $(KHDR_INCLUDES)
CFLAGS := $(CFLAGS) -g -O2 -Wall -pthread $(INCLUDES) $(KHDR_INCLUDES)
CFLAGS := $(CFLAGS) -g -O2 -Wall -pthread -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64 $(INCLUDES) $(KHDR_INCLUDES) -DLIBNUMA_VER_$(LIBNUMA_TEST)=1
LDLIBS := -lpthread -lrt -lnuma

LOCAL_HDRS := \
	../include/futextest.h \
	../include/atomic.h \
	../include/logging.h
	../include/atomic.h
TEST_GEN_PROGS := \
	futex_wait_timeout \
	futex_wait_wouldblock \
+2 −1
Original line number Diff line number Diff line
@@ -5,9 +5,10 @@
#include <sys/mman.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include "logging.h"
#include "futextest.h"
#include "futex2test.h"

+42 −58
Original line number Diff line number Diff line
@@ -16,9 +16,9 @@
#include <linux/futex.h>
#include <sys/mman.h>

#include "logging.h"
#include "futextest.h"
#include "futex2test.h"
#include "../../kselftest_harness.h"

#define MAX_THREADS	64

@@ -77,7 +77,7 @@ static void join_max_threads(void)
	}
}

static void __test_futex(void *futex_ptr, int must_fail, unsigned int futex_flags)
static void __test_futex(void *futex_ptr, int err_value, unsigned int futex_flags)
{
	int to_wake, ret, i, need_exit = 0;

@@ -88,11 +88,17 @@ static void __test_futex(void *futex_ptr, int must_fail, unsigned int futex_flag

	do {
		ret = futex2_wake(futex_ptr, to_wake, futex_flags);
		if (must_fail) {
			if (ret < 0)
				break;

		if (err_value) {
			if (ret >= 0)
				ksft_exit_fail_msg("futex2_wake(%d, 0x%x) should fail, but didn't\n",
						   to_wake, futex_flags);

			if (errno != err_value)
				ksft_exit_fail_msg("futex2_wake(%d, 0x%x) expected error was %d, but returned %d (%s)\n",
						   to_wake, futex_flags, err_value, errno, strerror(errno));

			break;
		}
		if (ret < 0) {
			ksft_exit_fail_msg("Failed futex2_wake(%d, 0x%x): %m\n",
@@ -106,12 +112,12 @@ static void __test_futex(void *futex_ptr, int must_fail, unsigned int futex_flag
	join_max_threads();

	for (i = 0; i < MAX_THREADS; i++) {
		if (must_fail && thread_args[i].result != -1) {
		if (err_value && thread_args[i].result != -1) {
			ksft_print_msg("Thread %d should fail but succeeded (%d)\n",
				       i, thread_args[i].result);
			need_exit = 1;
		}
		if (!must_fail && thread_args[i].result != 0) {
		if (!err_value && thread_args[i].result != 0) {
			ksft_print_msg("Thread %d failed (%d)\n", i, thread_args[i].result);
			need_exit = 1;
		}
@@ -120,58 +126,30 @@ static void __test_futex(void *futex_ptr, int must_fail, unsigned int futex_flag
		ksft_exit_fail_msg("Aborting due to earlier errors.\n");
}

static void test_futex(void *futex_ptr, int must_fail)
static void test_futex(void *futex_ptr, int err_value)
{
	__test_futex(futex_ptr, must_fail, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA);
	__test_futex(futex_ptr, err_value, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA);
}

static void test_futex_mpol(void *futex_ptr, int must_fail)
static void test_futex_mpol(void *futex_ptr, int err_value)
{
	__test_futex(futex_ptr, must_fail, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA | FUTEX2_MPOL);
	__test_futex(futex_ptr, err_value, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA | FUTEX2_MPOL);
}

static void usage(char *prog)
{
	printf("Usage: %s\n", prog);
	printf("  -c    Use color\n");
	printf("  -h    Display this help message\n");
	printf("  -v L  Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
	       VQUIET, VCRITICAL, VINFO);
}

int main(int argc, char *argv[])
TEST(futex_numa_mpol)
{
	struct futex32_numa *futex_numa;
	int mem_size, i;
	void *futex_ptr;
	int c;

	while ((c = getopt(argc, argv, "chv:")) != -1) {
		switch (c) {
		case 'c':
			log_color(1);
			break;
		case 'h':
			usage(basename(argv[0]));
			exit(0);
			break;
		case 'v':
			log_verbosity(atoi(optarg));
			break;
		default:
			usage(basename(argv[0]));
			exit(1);
		}
	}

	ksft_print_header();
	ksft_set_plan(1);
	int mem_size;

	mem_size = sysconf(_SC_PAGE_SIZE);
	futex_ptr = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
	futex_ptr = mmap(NULL, mem_size * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
	if (futex_ptr == MAP_FAILED)
		ksft_exit_fail_msg("mmap() for %d bytes failed\n", mem_size);

	/* Create an invalid memory region for the "Memory out of range" test */
	mprotect(futex_ptr + mem_size, mem_size, PROT_NONE);

	futex_numa = futex_ptr;

	ksft_print_msg("Regular test\n");
@@ -182,27 +160,31 @@ int main(int argc, char *argv[])
	if (futex_numa->numa == FUTEX_NO_NODE)
		ksft_exit_fail_msg("NUMA node is left uninitialized\n");

	ksft_print_msg("Memory too small\n");
	test_futex(futex_ptr + mem_size - 4, 1);
	/* FUTEX2_NUMA futex must be 8-byte aligned */
	ksft_print_msg("Mis-aligned futex\n");
	test_futex(futex_ptr + mem_size - 4, EINVAL);

	ksft_print_msg("Memory out of range\n");
	test_futex(futex_ptr + mem_size, 1);
	test_futex(futex_ptr + mem_size, EFAULT);

	futex_numa->numa = FUTEX_NO_NODE;
	mprotect(futex_ptr, mem_size, PROT_READ);
	ksft_print_msg("Memory, RO\n");
	test_futex(futex_ptr, 1);
	test_futex(futex_ptr, EFAULT);

	mprotect(futex_ptr, mem_size, PROT_NONE);
	ksft_print_msg("Memory, no access\n");
	test_futex(futex_ptr, 1);
	test_futex(futex_ptr, EFAULT);

	mprotect(futex_ptr, mem_size, PROT_READ | PROT_WRITE);
	ksft_print_msg("Memory back to RW\n");
	test_futex(futex_ptr, 0);

	ksft_test_result_pass("futex2 memory boundary tests passed\n");

	/* MPOL test. Does not work as expected */
	for (i = 0; i < 4; i++) {
#ifdef LIBNUMA_VER_SUFFICIENT
	for (int i = 0; i < 4; i++) {
		unsigned long nodemask;
		int ret;

@@ -221,15 +203,17 @@ int main(int argc, char *argv[])
			ret = futex2_wake(futex_ptr, 0, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA | FUTEX2_MPOL);
			if (ret < 0)
				ksft_test_result_fail("Failed to wake 0 with MPOL: %m\n");
			if (0)
				test_futex_mpol(futex_numa, 0);
			if (futex_numa->numa != i) {
				ksft_exit_fail_msg("Returned NUMA node is %d expected %d\n",
						   futex_numa->numa, i);
			}
		}
	}
	ksft_test_result_pass("NUMA MPOL tests passed\n");
	ksft_finished();
	return 0;
	ksft_test_result_pass("futex2 MPOL hints test passed\n");
#else
	ksft_test_result_skip("futex2 MPOL hints test requires libnuma 2.0.16+\n");
#endif
	munmap(futex_ptr, mem_size * 2);
}

TEST_HARNESS_MAIN
+16 −51
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
#include <linux/prctl.h>
#include <sys/prctl.h>

#include "logging.h"
#include "../../kselftest_harness.h"

#define MAX_THREADS	64

@@ -128,46 +128,14 @@ static void futex_dummy_op(void)
		ksft_exit_fail_msg("pthread_mutex_timedlock() did not timeout: %d.\n", ret);
}

static void usage(char *prog)
{
	printf("Usage: %s\n", prog);
	printf("  -c    Use color\n");
	printf("  -g    Test global hash instead intead local immutable \n");
	printf("  -h    Display this help message\n");
	printf("  -v L  Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
	       VQUIET, VCRITICAL, VINFO);
}

static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n";
static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n";

int main(int argc, char *argv[])
TEST(priv_hash)
{
	int futex_slots1, futex_slotsn, online_cpus;
	pthread_mutexattr_t mutex_attr_pi;
	int ret, retry = 20;
	int c;

	while ((c = getopt(argc, argv, "chv:")) != -1) {
		switch (c) {
		case 'c':
			log_color(1);
			break;
		case 'h':
			usage(basename(argv[0]));
			exit(0);
			break;
		case 'v':
			log_verbosity(atoi(optarg));
			break;
		default:
			usage(basename(argv[0]));
			exit(1);
		}
	}

	ksft_print_header();
	ksft_set_plan(21);

	ret = pthread_mutexattr_init(&mutex_attr_pi);
	ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT);
@@ -189,14 +157,14 @@ int main(int argc, char *argv[])
	if (ret != 0)
		ksft_exit_fail_msg("pthread_join() failed: %d, %m\n", ret);

	/* First thread, has to initialiaze private hash */
	/* First thread, has to initialize private hash */
	futex_slots1 = futex_hash_slots_get();
	if (futex_slots1 <= 0) {
		ksft_print_msg("Current hash buckets: %d\n", futex_slots1);
		ksft_exit_fail_msg(test_msg_auto_create);
		ksft_exit_fail_msg("%s", test_msg_auto_create);
	}

	ksft_test_result_pass(test_msg_auto_create);
	ksft_test_result_pass("%s", test_msg_auto_create);

	online_cpus = sysconf(_SC_NPROCESSORS_ONLN);
	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);
@@ -237,11 +205,11 @@ int main(int argc, char *argv[])
			}
			ksft_print_msg("Expected increase of hash buckets but got: %d -> %d\n",
				       futex_slots1, futex_slotsn);
			ksft_exit_fail_msg(test_msg_auto_inc);
			ksft_exit_fail_msg("%s", test_msg_auto_inc);
		}
		ksft_test_result_pass(test_msg_auto_inc);
		ksft_test_result_pass("%s", test_msg_auto_inc);
	} else {
		ksft_test_result_skip(test_msg_auto_inc);
		ksft_test_result_skip("%s", test_msg_auto_inc);
	}
	ret = pthread_mutex_unlock(&global_lock);

@@ -257,17 +225,17 @@ int main(int argc, char *argv[])

	futex_hash_slots_set_verify(2);
	join_max_threads();
	ksft_test_result(counter == MAX_THREADS, "Created of waited for %d of %d threads\n",
	ksft_test_result(counter == MAX_THREADS, "Created and waited for %d of %d threads\n",
			 counter, MAX_THREADS);
	counter = 0;
	/* Once the user set something, auto reisze must be disabled */
	/* Once the user set something, auto resize must be disabled */
	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);

	create_max_threads(thread_lock_fn);
	join_max_threads();

	ret = futex_hash_slots_get();
	ksft_test_result(ret == 2, "No more auto-resize after manaul setting, got %d\n",
	ksft_test_result(ret == 2, "No more auto-resize after manual setting, got %d\n",
			 ret);

	futex_hash_slots_set_must_fail(1 << 29);
@@ -280,7 +248,7 @@ int main(int argc, char *argv[])
	ret = futex_hash_slots_set(0);
	ksft_test_result(ret == 0, "Global hash request\n");
	if (ret != 0)
		goto out;
		return;

	futex_hash_slots_set_must_fail(4);
	futex_hash_slots_set_must_fail(8);
@@ -289,17 +257,14 @@ int main(int argc, char *argv[])
	futex_hash_slots_set_must_fail(6);

	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
	if (ret != 0) {
	if (ret != 0)
		ksft_exit_fail_msg("pthread_barrier_init failed: %m\n");
		return 1;
	}

	create_max_threads(thread_lock_fn);
	join_max_threads();

	ret = futex_hash_slots_get();
	ksft_test_result(ret == 0, "Continue to use global hash\n");

out:
	ksft_finished();
	return 0;
}

TEST_HARNESS_MAIN
Loading