Skip to content

Commit e52b2c4

Browse files
committed
futex: Add error coverage tests for wait, wake and cmp_requeue
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. Signed-off-by: Michael Menasherov <mmenashe@redhat.com>
1 parent bdf8db2 commit e52b2c4

6 files changed

Lines changed: 354 additions & 0 deletions

File tree

runtest/syscalls

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,18 +1859,22 @@ perf_event_open02 perf_event_open02
18591859

18601860
futex_cmp_requeue01 futex_cmp_requeue01
18611861
futex_cmp_requeue02 futex_cmp_requeue02
1862+
futex_cmp_requeue03 futex_cmp_requeue03
18621863
futex_wait01 futex_wait01
18631864
futex_wait02 futex_wait02
18641865
futex_wait03 futex_wait03
18651866
futex_wait04 futex_wait04
18661867
futex_wait05 futex_wait05
1868+
futex_wait06 futex_wait06
1869+
futex_wait07 futex_wait07
18671870
futex_waitv01 futex_waitv01
18681871
futex_waitv02 futex_waitv02
18691872
futex_waitv03 futex_waitv03
18701873
futex_wake01 futex_wake01
18711874
futex_wake02 futex_wake02
18721875
futex_wake03 futex_wake03
18731876
futex_wake04 futex_wake04
1877+
futex_wake05 futex_wake05
18741878
futex_wait_bitset01 futex_wait_bitset01
18751879

18761880
memfd_create01 memfd_create01
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
/futex_cmp_requeue01
22
/futex_cmp_requeue02
3+
/futex_cmp_requeue03
34
/futex_wait01
45
/futex_wait02
56
/futex_wait03
67
/futex_wait04
78
/futex_wait05
9+
/futex_wait06
10+
/futex_wait07
811
/futex_wait_bitset01
912
/futex_wake01
1013
/futex_wake02
1114
/futex_wake03
1215
/futex_wake04
16+
/futex_wake05
1317
/futex_waitv01
1418
/futex_waitv02
1519
/futex_waitv03
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (C) 2026 Red Hat, Inc.
4+
*/
5+
6+
/*\
7+
* Check that futex(FUTEX_CMP_REQUEUE) returns EFAULT when uaddr or
8+
* uaddr2 points to unmapped memory, and EACCES (or EFAULT on older kernels)
9+
* when uaddr points to memory without read permission (PROT_NONE).
10+
*
11+
* The EACCES behavior for PROT_NONE was introduced in kernel 5.9.
12+
*/
13+
14+
#include <errno.h>
15+
#include <sys/mman.h>
16+
17+
#include "futextest.h"
18+
19+
static futex_t futex_var = FUTEX_INITIALIZER;
20+
static futex_t *prot_none_addr;
21+
22+
static struct futex_test_variants variants[] = {
23+
#if (__NR_futex != __LTP__NR_INVALID_SYSCALL)
24+
{ .fntype = FUTEX_FN_FUTEX, .desc = "syscall with old kernel spec"},
25+
#endif
26+
27+
#if (__NR_futex_time64 != __LTP__NR_INVALID_SYSCALL)
28+
{ .fntype = FUTEX_FN_FUTEX64, .desc = "syscall time64 with kernel spec"},
29+
#endif
30+
};
31+
32+
static struct testcase {
33+
const char *desc;
34+
futex_t *uaddr;
35+
futex_t *uaddr2;
36+
int exp_errno;
37+
} testcases[3];
38+
39+
static void run(unsigned int n)
40+
{
41+
struct futex_test_variants *tv = &variants[tst_variant];
42+
struct testcase *tc = &testcases[n];
43+
44+
TST_EXP_FAIL(futex_cmp_requeue(tv->fntype, tc->uaddr, futex_var,
45+
tc->uaddr2, 1, 1, 0), tc->exp_errno, "%s", tc->desc);
46+
}
47+
48+
static void setup(void)
49+
{
50+
struct futex_test_variants *tv = &variants[tst_variant];
51+
size_t pagesize = getpagesize();
52+
futex_t *unmapped;
53+
54+
tst_res(TINFO, "Testing variant: %s", tv->desc);
55+
futex_supported_by_kernel(tv->fntype);
56+
57+
unmapped = SAFE_MMAP(NULL, pagesize, PROT_READ | PROT_WRITE,
58+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
59+
SAFE_MUNMAP(unmapped, pagesize);
60+
61+
prot_none_addr = SAFE_MMAP(NULL, pagesize, PROT_NONE,
62+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
63+
64+
testcases[0] = (struct testcase){
65+
.desc = "uaddr unmapped",
66+
.uaddr = unmapped,
67+
.uaddr2 = &futex_var,
68+
.exp_errno = EFAULT,
69+
};
70+
testcases[1] = (struct testcase){
71+
.desc = "uaddr2 unmapped",
72+
.uaddr = &futex_var,
73+
.uaddr2 = unmapped,
74+
.exp_errno = EFAULT,
75+
};
76+
testcases[2] = (struct testcase){
77+
.desc = "uaddr PROT_NONE",
78+
.uaddr = prot_none_addr,
79+
.uaddr2 = &futex_var,
80+
.exp_errno = tst_kvercmp(5, 9, 0) >= 0 ? EACCES : EFAULT,
81+
};
82+
}
83+
84+
static void cleanup(void)
85+
{
86+
if (prot_none_addr)
87+
SAFE_MUNMAP(prot_none_addr, getpagesize());
88+
}
89+
90+
static struct tst_test test = {
91+
.setup = setup,
92+
.cleanup = cleanup,
93+
.test = run,
94+
.tcnt = ARRAY_SIZE(testcases),
95+
.test_variants = ARRAY_SIZE(variants),
96+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (C) 2026 Red Hat, Inc.
4+
*/
5+
6+
/*\
7+
* Check that futex(FUTEX_WAIT) returns EFAULT when:
8+
*
9+
* 1) uaddr points to unmapped memory
10+
* 2) timeout points to unmapped memory
11+
*/
12+
#include <errno.h>
13+
#include <sys/mman.h>
14+
15+
#include "futextest.h"
16+
17+
static futex_t futex = FUTEX_INITIALIZER;
18+
19+
static struct futex_test_variants variants[] = {
20+
#if (__NR_futex != __LTP__NR_INVALID_SYSCALL)
21+
{ .fntype = FUTEX_FN_FUTEX, .tstype = TST_KERN_OLD_TIMESPEC, .desc = "syscall with old kernel spec"},
22+
#endif
23+
24+
#if (__NR_futex_time64 != __LTP__NR_INVALID_SYSCALL)
25+
{ .fntype = FUTEX_FN_FUTEX64, .tstype = TST_KERN_TIMESPEC, .desc = "syscall time64 with kernel spec"},
26+
#endif
27+
};
28+
29+
static struct testcase {
30+
const char *desc;
31+
futex_t *uaddr;
32+
void *timeout;
33+
} testcases[2];
34+
35+
static void run(unsigned int n)
36+
{
37+
struct futex_test_variants *tv = &variants[tst_variant];
38+
struct testcase *tc = &testcases[n];
39+
40+
TST_EXP_FAIL(futex_syscall(tv->fntype, tc->uaddr, FUTEX_WAIT, futex,
41+
tc->timeout, NULL, 0, 0), EFAULT, "%s", tc->desc);
42+
}
43+
44+
static void setup(void)
45+
{
46+
struct futex_test_variants *tv = &variants[tst_variant];
47+
void *bad;
48+
49+
tst_res(TINFO, "Testing variant: %s", tv->desc);
50+
futex_supported_by_kernel(tv->fntype);
51+
52+
bad = SAFE_MMAP(NULL, getpagesize(), PROT_READ | PROT_WRITE,
53+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
54+
SAFE_MUNMAP(bad, getpagesize());
55+
56+
testcases[0] = (struct testcase){
57+
.desc = "uaddr points to unmapped memory",
58+
.uaddr = bad,
59+
.timeout = NULL,
60+
};
61+
testcases[1] = (struct testcase){
62+
.desc = "timeout points to unmapped memory",
63+
.uaddr = &futex,
64+
.timeout = bad,
65+
};
66+
}
67+
68+
static struct tst_test test = {
69+
.setup = setup,
70+
.test = run,
71+
.tcnt = ARRAY_SIZE(testcases),
72+
.test_variants = ARRAY_SIZE(variants),
73+
};
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (C) 2026 Red Hat, Inc.
4+
*/
5+
6+
/*\
7+
* Check that futex(FUTEX_WAIT) returns EINTR when interrupted by a signal.
8+
* A child process blocks on futex_wait() with a long timeout. The parent
9+
* waits for the child to enter sleep state, then sends SIGUSR1 to it.
10+
* The child verifies it received EINTR and exits accordingly.
11+
*/
12+
13+
#include <errno.h>
14+
#include <signal.h>
15+
16+
#include "futextest.h"
17+
18+
static futex_t *futex;
19+
20+
static struct futex_test_variants variants[] = {
21+
#if (__NR_futex != __LTP__NR_INVALID_SYSCALL)
22+
{ .fntype = FUTEX_FN_FUTEX, .tstype = TST_KERN_OLD_TIMESPEC, .desc = "syscall with old kernel spec"},
23+
#endif
24+
25+
#if (__NR_futex_time64 != __LTP__NR_INVALID_SYSCALL)
26+
{ .fntype = FUTEX_FN_FUTEX64, .tstype = TST_KERN_TIMESPEC, .desc = "syscall time64 with kernel spec"},
27+
#endif
28+
};
29+
30+
/* We need a handler so SIGUSR1 is caught instead of killing the process.
31+
* The empty body is needed, just receiving the signal is enough to
32+
* interrupt futex_wait() and make it return into EINTR -1 status.
33+
*/
34+
static void sigusr1_handler(int sig LTP_ATTRIBUTE_UNUSED)
35+
{
36+
}
37+
38+
static void do_child(void)
39+
{
40+
struct futex_test_variants *tv = &variants[tst_variant];
41+
struct sigaction sa;
42+
struct tst_ts timeout;
43+
44+
sa.sa_handler = sigusr1_handler;
45+
sa.sa_flags = 0;
46+
SAFE_SIGEMPTYSET(&sa.sa_mask);
47+
SAFE_SIGACTION(SIGUSR1, &sa, NULL);
48+
49+
timeout = tst_ts_from_ms(tv->tstype, 5000);
50+
TST_EXP_FAIL(futex_wait(tv->fntype, futex, *futex, &timeout, 0), EINTR);
51+
exit(0);
52+
}
53+
54+
static void run(void)
55+
{
56+
pid_t child;
57+
58+
child = SAFE_FORK();
59+
60+
if (child == 0)
61+
do_child();
62+
63+
TST_PROCESS_STATE_WAIT(child, 'S', 0);
64+
SAFE_KILL(child, SIGUSR1);
65+
}
66+
67+
static void setup(void)
68+
{
69+
struct futex_test_variants *tv = &variants[tst_variant];
70+
71+
tst_res(TINFO, "Testing variant: %s", tv->desc);
72+
futex_supported_by_kernel(tv->fntype);
73+
74+
futex = SAFE_MMAP(NULL, sizeof(*futex), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
75+
*futex = FUTEX_INITIALIZER;
76+
}
77+
78+
static void cleanup(void)
79+
{
80+
if (futex) {
81+
SAFE_MUNMAP((void *)futex, sizeof(*futex));
82+
}
83+
}
84+
85+
static struct tst_test test = {
86+
.setup = setup,
87+
.cleanup = cleanup,
88+
.test_all = run,
89+
.test_variants = ARRAY_SIZE(variants),
90+
.forks_child = 1,
91+
};
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (C) 2026 Red Hat, Inc.
4+
*/
5+
6+
/*\
7+
* Check that futex(FUTEX_WAKE) returns EFAULT when uaddr points to
8+
* unmapped or PROT_NONE memory.
9+
*
10+
* Note: FUTEX_WAKE never reads *uaddr, so PROT_NONE triggers EFAULT
11+
* (not EACCES). The EACCES behavior only applies to syscalls that read
12+
* *uaddr (e.g. FUTEX_WAIT, FUTEX_CMP_REQUEUE).
13+
*/
14+
15+
#include <errno.h>
16+
#include <sys/mman.h>
17+
#include "futextest.h"
18+
19+
static futex_t *prot_none_addr;
20+
21+
static struct futex_test_variants variants[] = {
22+
#if (__NR_futex != __LTP__NR_INVALID_SYSCALL)
23+
{ .fntype = FUTEX_FN_FUTEX, .desc = "syscall with old kernel spec"},
24+
#endif
25+
26+
#if (__NR_futex_time64 != __LTP__NR_INVALID_SYSCALL)
27+
{ .fntype = FUTEX_FN_FUTEX64, .desc = "syscall time64 with kernel spec"},
28+
#endif
29+
};
30+
31+
static struct testcase {
32+
const char *desc;
33+
futex_t *addr;
34+
int exp_errno;
35+
} testcases[2];
36+
37+
static void run(unsigned int n)
38+
{
39+
struct futex_test_variants *tv = &variants[tst_variant];
40+
struct testcase *tc = &testcases[n];
41+
42+
TST_EXP_FAIL(futex_wake(tv->fntype, tc->addr, 1, 0),
43+
tc->exp_errno, "%s", tc->desc);
44+
}
45+
46+
static void setup(void)
47+
{
48+
struct futex_test_variants *tv = &variants[tst_variant];
49+
size_t pagesize = getpagesize();
50+
futex_t *unmapped;
51+
52+
tst_res(TINFO, "Testing variant: %s", tv->desc);
53+
futex_supported_by_kernel(tv->fntype);
54+
55+
unmapped = SAFE_MMAP(NULL, pagesize, PROT_READ | PROT_WRITE,
56+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
57+
SAFE_MUNMAP(unmapped, pagesize);
58+
59+
prot_none_addr = SAFE_MMAP(NULL, pagesize, PROT_NONE,
60+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
61+
62+
testcases[0] = (struct testcase){
63+
.desc = "uaddr unmapped",
64+
.addr = unmapped,
65+
.exp_errno = EFAULT,
66+
};
67+
testcases[1] = (struct testcase){
68+
.desc = "uaddr PROT_NONE",
69+
.addr = prot_none_addr,
70+
.exp_errno = EFAULT,
71+
};
72+
}
73+
74+
static void cleanup(void)
75+
{
76+
if (prot_none_addr)
77+
SAFE_MUNMAP(prot_none_addr, getpagesize());
78+
}
79+
80+
static struct tst_test test = {
81+
.setup = setup,
82+
.cleanup = cleanup,
83+
.test = run,
84+
.tcnt = ARRAY_SIZE(testcases),
85+
.test_variants = ARRAY_SIZE(variants),
86+
};

0 commit comments

Comments
 (0)