[LTP] [PATCH v2] futex: Add error coverage tests for wait, wake and cmp_requeue

Cyril Hrubis chrubis@suse.cz
Wed Apr 15 17:35:08 CEST 2026


Hi!
> Improve error handling coverage for futex syscalls by adding tests
> for missing error conditions that were previously untested.
> 
> futex_wait06 verifies EFAULT is returned when uaddr or timeout
> points to unmapped memory.
> 
> futex_wait07 verifies EINTR is returned when futex_wait() is
> interrupted by a signal.
> 
> futex_wake05 verifies EFAULT is returned when uaddr points to
> unmapped or PROT_NONE memory.
> 
> futex_cmp_requeue03 verifies EFAULT is returned when uaddr or
> uaddr2 points to unmapped memory, and EACCES or EFAULT when uaddr
> points to memory without read permission (PROT_NONE). The EACCES
> behavior was introduced in kernel 5.9.

Generally it would be probably easier to review and applly if you split
this into a series with one test per per patch.

> ---
> v2: Use TST_EXP_FAIL macro as requested.
>     Store address directly in testcase structs and not using flags.
>     Use SAFE_MMAP and not raw as requested, also added a sign off.
>  runtest/syscalls                              |   4 +
>  testcases/kernel/syscalls/futex/.gitignore    |   4 +
>  .../syscalls/futex/futex_cmp_requeue03.c      |  96 ++++++++++++++
>  .../kernel/syscalls/futex/futex_wait06.c      |  73 +++++++++++
>  .../kernel/syscalls/futex/futex_wait07.c      | 117 ++++++++++++++++++
>  .../kernel/syscalls/futex/futex_wake05.c      |  86 +++++++++++++
>  6 files changed, 380 insertions(+)
>  create mode 100644 testcases/kernel/syscalls/futex/futex_cmp_requeue03.c
>  create mode 100644 testcases/kernel/syscalls/futex/futex_wait06.c
>  create mode 100644 testcases/kernel/syscalls/futex/futex_wait07.c
>  create mode 100644 testcases/kernel/syscalls/futex/futex_wake05.c
> 
> diff --git a/runtest/syscalls b/runtest/syscalls
> index 6ba0227a8..6c12dc225 100644
> --- a/runtest/syscalls
> +++ b/runtest/syscalls
> @@ -1859,11 +1859,14 @@ perf_event_open02 perf_event_open02
>  
>  futex_cmp_requeue01 futex_cmp_requeue01
>  futex_cmp_requeue02 futex_cmp_requeue02
> +futex_cmp_requeue03 futex_cmp_requeue03
>  futex_wait01 futex_wait01
>  futex_wait02 futex_wait02
>  futex_wait03 futex_wait03
>  futex_wait04 futex_wait04
>  futex_wait05 futex_wait05
> +futex_wait06 futex_wait06
> +futex_wait07 futex_wait07
>  futex_waitv01 futex_waitv01
>  futex_waitv02 futex_waitv02
>  futex_waitv03 futex_waitv03
> @@ -1871,6 +1874,7 @@ futex_wake01 futex_wake01
>  futex_wake02 futex_wake02
>  futex_wake03 futex_wake03
>  futex_wake04 futex_wake04
> +futex_wake05 futex_wake05
>  futex_wait_bitset01 futex_wait_bitset01
>  
>  memfd_create01 memfd_create01
> diff --git a/testcases/kernel/syscalls/futex/.gitignore b/testcases/kernel/syscalls/futex/.gitignore
> index 9d08ba7d3..c47d39b5b 100644
> --- a/testcases/kernel/syscalls/futex/.gitignore
> +++ b/testcases/kernel/syscalls/futex/.gitignore
> @@ -1,15 +1,19 @@
>  /futex_cmp_requeue01
>  /futex_cmp_requeue02
> +/futex_cmp_requeue03
>  /futex_wait01
>  /futex_wait02
>  /futex_wait03
>  /futex_wait04
>  /futex_wait05
> +/futex_wait06
> +/futex_wait07
>  /futex_wait_bitset01
>  /futex_wake01
>  /futex_wake02
>  /futex_wake03
>  /futex_wake04
> +/futex_wake05
>  /futex_waitv01
>  /futex_waitv02
>  /futex_waitv03
> diff --git a/testcases/kernel/syscalls/futex/futex_cmp_requeue03.c b/testcases/kernel/syscalls/futex/futex_cmp_requeue03.c
> new file mode 100644
> index 000000000..099d5e35c
> --- /dev/null
> +++ b/testcases/kernel/syscalls/futex/futex_cmp_requeue03.c
> @@ -0,0 +1,96 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2026 Red Hat, Inc.
> + */
> +
> +/*\
> + * Check that futex(FUTEX_CMP_REQUEUE) returns EFAULT when uaddr or
> + * uaddr2 points to unmapped memory, and EACCES (or EFAULT on older kernels)
> + * when uaddr points to memory without read permission (PROT_NONE).
> + *
> + * The EACCES behavior for PROT_NONE was introduced in kernel 5.9.
> + */
> +
> +#include <errno.h>
> +#include <sys/mman.h>
> +
> +#include "futextest.h"
> +
> +static futex_t futex_var = FUTEX_INITIALIZER;
> +static futex_t *prot_none_addr;
> +
> +static struct futex_test_variants variants[] = {
> +#if (__NR_futex != __LTP__NR_INVALID_SYSCALL)
> +	{ .fntype = FUTEX_FN_FUTEX, .desc = "syscall with old kernel spec"},
> +#endif
> +
> +#if (__NR_futex_time64 != __LTP__NR_INVALID_SYSCALL)
> +	{ .fntype = FUTEX_FN_FUTEX64, .desc = "syscall time64 with kernel spec"},
> +#endif
> +};
> +
> +static struct testcase {
> +	const char *desc;
> +	futex_t *uaddr;
> +	futex_t *uaddr2;
> +	int exp_errno;
> +} testcases[3];
> +
> +static void run(unsigned int n)
> +{
> +	struct futex_test_variants *tv = &variants[tst_variant];
> +	struct testcase *tc = &testcases[n];
> +
> +	TST_EXP_FAIL(futex_cmp_requeue(tv->fntype, tc->uaddr, futex_var,
> +		tc->uaddr2, 1, 1, 0), tc->exp_errno, "%s", tc->desc);
> +}
> +
> +static void setup(void)
> +{
> +	struct futex_test_variants *tv = &variants[tst_variant];
> +	size_t pagesize = getpagesize();
> +	futex_t *unmapped;
> +
> +	tst_res(TINFO, "Testing variant: %s", tv->desc);
> +	futex_supported_by_kernel(tv->fntype);
> +
> +	unmapped = SAFE_MMAP(NULL, pagesize, PROT_READ | PROT_WRITE,
> +		MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +	SAFE_MUNMAP(unmapped, pagesize);
> +
> +	prot_none_addr = SAFE_MMAP(NULL, pagesize, PROT_NONE,
> +		MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +
> +	testcases[0] = (struct testcase){
> +		.desc = "uaddr unmapped",
> +		.uaddr = unmapped,
> +		.uaddr2 = &futex_var,
> +		.exp_errno = EFAULT,
> +	};
> +	testcases[1] = (struct testcase){
> +		.desc = "uaddr2 unmapped",
> +		.uaddr = &futex_var,
> +		.uaddr2 = unmapped,
> +		.exp_errno = EFAULT,
> +	};
> +	testcases[2] = (struct testcase){
> +		.desc = "uaddr PROT_NONE",
> +		.uaddr = prot_none_addr,
> +		.uaddr2 = &futex_var,
> +		.exp_errno = tst_kvercmp(5, 9, 0) >= 0 ? EACCES : EFAULT,
> +	};
> +}
> +
> +static void cleanup(void)
> +{
> +	if (prot_none_addr)
> +		SAFE_MUNMAP(prot_none_addr, getpagesize());
> +}
> +
> +static struct tst_test test = {
> +	.setup = setup,
> +	.cleanup = cleanup,
> +	.test = run,
> +	.tcnt = ARRAY_SIZE(testcases),
> +	.test_variants = ARRAY_SIZE(variants),
> +};
> diff --git a/testcases/kernel/syscalls/futex/futex_wait06.c b/testcases/kernel/syscalls/futex/futex_wait06.c
> new file mode 100644
> index 000000000..8bb563fb2
> --- /dev/null
> +++ b/testcases/kernel/syscalls/futex/futex_wait06.c
> @@ -0,0 +1,73 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2026 Red Hat, Inc.
> + */
> +
> +/*\
> + * Check that futex(FUTEX_WAIT) returns EFAULT when:
> + *
> + * 1) uaddr points to unmapped memory
> + * 2) timeout points to unmapped memory
> + */
> +#include <errno.h>
> +#include <sys/mman.h>
> +
> +#include "futextest.h"
> +
> +static futex_t futex = FUTEX_INITIALIZER;
> +
> +static struct futex_test_variants variants[] = {
> +#if (__NR_futex != __LTP__NR_INVALID_SYSCALL)
> +	{ .fntype = FUTEX_FN_FUTEX, .tstype = TST_KERN_OLD_TIMESPEC, .desc = "syscall with old kernel spec"},
> +#endif
> +
> +#if (__NR_futex_time64 != __LTP__NR_INVALID_SYSCALL)
> +	{ .fntype = FUTEX_FN_FUTEX64, .tstype = TST_KERN_TIMESPEC, .desc = "syscall time64 with kernel spec"},
> +#endif
> +};
> +
> +static struct testcase {
> +	const char *desc;
> +	futex_t *uaddr;
> +	void *timeout;
> +} testcases[2];
> +
> +static void run(unsigned int n)
> +{
> +	struct futex_test_variants *tv = &variants[tst_variant];
> +	struct testcase *tc = &testcases[n];
> +
> +	TST_EXP_FAIL(futex_syscall(tv->fntype, tc->uaddr, FUTEX_WAIT, futex,
> +		tc->timeout, NULL, 0, 0), EFAULT, "%s", tc->desc);
> +}
> +
> +static void setup(void)
> +{
> +	struct futex_test_variants *tv = &variants[tst_variant];
> +	void *bad;
> +
> +	tst_res(TINFO, "Testing variant: %s", tv->desc);
> +	futex_supported_by_kernel(tv->fntype);
> +
> +	bad = SAFE_MMAP(NULL, getpagesize(), PROT_READ | PROT_WRITE,
> +		MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +	SAFE_MUNMAP(bad, getpagesize());
> +
> +	testcases[0] = (struct testcase){
> +		.desc = "uaddr points to unmapped memory",
> +		.uaddr = bad,
> +		.timeout = NULL,
> +	};
> +	testcases[1] = (struct testcase){
> +		.desc = "timeout points to unmapped memory",
> +		.uaddr = &futex,
> +		.timeout = bad,
> +	};
> +}
> +
> +static struct tst_test test = {
> +	.setup = setup,
> +	.test = run,
> +	.tcnt = ARRAY_SIZE(testcases),
> +	.test_variants = ARRAY_SIZE(variants),
> +};
> diff --git a/testcases/kernel/syscalls/futex/futex_wait07.c b/testcases/kernel/syscalls/futex/futex_wait07.c
> new file mode 100644
> index 000000000..a3c1c153d
> --- /dev/null
> +++ b/testcases/kernel/syscalls/futex/futex_wait07.c
> @@ -0,0 +1,117 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2026 Red Hat, Inc.
> + */
> +
> +/*\
> + * Check that futex(FUTEX_WAIT) returns EINTR when interrupted by a signal.
> + * A child process blocks on futex_wait() with a long timeout. The parent
> + * waits for the child to enter sleep state, then sends SIGUSR1 to it.
> + * The child verifies it received EINTR and exits accordingly.
> + */
> +
> +#include <errno.h>
> +#include <signal.h>
> +#include <sys/wait.h>
> +
> +#include "futextest.h"
> +
> +static futex_t *futex;
> +
> +static struct futex_test_variants variants[] = {
> +#if (__NR_futex != __LTP__NR_INVALID_SYSCALL)
> +	{ .fntype = FUTEX_FN_FUTEX, .tstype = TST_KERN_OLD_TIMESPEC, .desc = "syscall with old kernel spec"},
> +#endif
> +
> +#if (__NR_futex_time64 != __LTP__NR_INVALID_SYSCALL)
> +	{ .fntype = FUTEX_FN_FUTEX64, .tstype = TST_KERN_TIMESPEC, .desc = "syscall time64 with kernel spec"},
> +#endif
> +};
> +
> +/* We need a handler so SIGUSR1 is caught instead of killing the process.
> + * The empty body is needed, just receiving the signal is enough to
> + * interrupt futex_wait() and make it return into EINTR -1 status.
> + */
> +static void sigusr1_handler(int sig LTP_ATTRIBUTE_UNUSED)
> +{
> +}

We have SIG_IGN for this purpose.

> +static void do_child(void)
> +{
> +	struct futex_test_variants *tv = &variants[tst_variant];
> +	struct sigaction sa;
> +	struct tst_ts timeout;
> +	int res;
> +
> +	/* Set up the signal handler for SIGUSR1 */

Please avoid commenting obvious such as this comment. There is plenty of
obvious comments that does not help at all.

> +	sa.sa_handler = sigusr1_handler;
> +	sa.sa_flags = 0;
> +	SAFE_SIGEMPTYSET(&sa.sa_mask);
> +	SAFE_SIGACTION(SIGUSR1, &sa, NULL);
> +
> +	/* Create a timeout for 5 sec for this variant.
> +	 * if no one wakes the child before 5 sec, futex_wait() will return
> +	 * on its own with ETIMEDOUT and will not wait any longer
> +	 */
> +	timeout = tst_ts_from_ms(tv->tstype, 5000);
> +	res = futex_wait(tv->fntype, futex, *futex, &timeout, 0);
> +
> +	if (res != -1) {
> +		tst_res(TFAIL, "futex_wait() should have failed with EINTR but returned success instead");
> +		exit(1);
> +	}
> +	if (errno != EINTR) {
> +		tst_res(TFAIL | TERRNO, "futex_wait() expected EINTR but got something else, errno");
> +		exit(1);
> +	}
> +	tst_res(TPASS | TERRNO, "futex_wait() returned EINTR as expected");

This could be another TST_EXP_FAIL macro, right?

> +	exit(0);
> +}
> +
> +static void run(void)
> +{
> +	pid_t child;
> +	int status;
> +
> +	child = SAFE_FORK();
> +
> +	if (child == 0) {
> +		do_child();
> +	}

LKML does prefer not having curly braces around single line statements.

> +	/* Wait until child is sleeping before sending signal */
> +	TST_PROCESS_STATE_WAIT(child, 'S', 0);
> +	/* Wake up the child so it will return EINTR -1 status */
> +	SAFE_KILL(child, SIGUSR1);
> +	SAFE_WAITPID(child, &status, 0);
> +	/* Check if the child finished everything as it should */
> +	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
> +		tst_res(TFAIL, "child exited abnormally");
> +	}

You can let the test library collect the child, no need to do that
manually here.

> +}
> +
> +static void setup(void)
> +{
> +	struct futex_test_variants *tv = &variants[tst_variant];
> +
> +	tst_res(TINFO, "Testing variant: %s", tv->desc);
> +	futex_supported_by_kernel(tv->fntype);
> +
> +	/* Futex needs to be in a shared memory so the parent and the child can access into it */
> +	futex = SAFE_MMAP(NULL, sizeof(*futex), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
> +	*futex = FUTEX_INITIALIZER;
> +}
> +
> +static void cleanup(void)
> +{
> +	if (futex) {
> +		SAFE_MUNMAP((void *)futex, sizeof(*futex));
> +	}
> +}
> +
> +static struct tst_test test = {
> +	.setup = setup,
> +	.cleanup = cleanup,
> +	.test_all = run,
> +	.test_variants = ARRAY_SIZE(variants),
> +	.forks_child = 1,
> +};

-- 
Cyril Hrubis
chrubis@suse.cz


More information about the ltp mailing list