[LTP] [PATCH v3] openposix: timer_*/speculative: fork() to isolate the SIGSEGV on old glibc

Avinesh Kumar avinesh.kumar@suse.com
Wed Sep 30 10:46:00 CEST 2026


From: Avinesh Kumar <avinesh.kumar@suse.com>

Commit d377d1cff1ec ("openposix: timer_*/speculative: Don't pass a
stack pointer as timerid") fixed a SIGSEGV on i586, where current
glibc's sign-bit check (timer_is_sigev_thread()) can misread a stack
address as a tagged pointer. A small constant avoids that.

But older glibc (<= 2.31, before this check existed) unconditionally
dereferences timerid as a pointer, so the same small constant now
faults instead:

  timer_delete_sp[13131]: segfault at 186a3 ip 00007f15973aed4e sp 00007ffefb52c5a0 error 4 in librt-2.31.so[7f15973aa000+8000]

fork() a child to make the call, then inspect the child's exit status.
POSIX defines no required behavior for an invalid timer ID (EINVAL is
only a recommendation), so the child being killed by SIGSEGV or SIGBUS
is treated as success.

Fixes: d377d1cff1ec ("openposix: timer_*/speculative: Don't pass a stack pointer as timerid")
Suggested-by: Martin Doucha <mdoucha@suse.cz>
Signed-off-by: Avinesh Kumar <avinesh.kumar@suse.com>
---
 .../interfaces/timer_delete/speculative/5-1.c | 56 +++++++++++-----
 .../timer_getoverrun/speculative/6-1.c        | 55 +++++++++++-----
 .../timer_gettime/speculative/6-1.c           | 58 +++++++++++-----
 .../timer_settime/speculative/12-1.c          | 66 +++++++++++++------
 4 files changed, 169 insertions(+), 66 deletions(-)

diff --git a/testcases/open_posix_testsuite/conformance/interfaces/timer_delete/speculative/5-1.c b/testcases/open_posix_testsuite/conformance/interfaces/timer_delete/speculative/5-1.c
index dce9277b5d6a..8e7d1b2e54dc 100644
--- a/testcases/open_posix_testsuite/conformance/interfaces/timer_delete/speculative/5-1.c
+++ b/testcases/open_posix_testsuite/conformance/interfaces/timer_delete/speculative/5-1.c
@@ -5,34 +5,58 @@
  * of this license, see the COPYING file at the top level of this
  * source tree.
 
- * Test to see if timer_delete() returns -1 and sets errno==EINVAL if
- * timerid is not a valid timer ID or not.
- * Since this is a "may" requirement, either option is a PASS.
+ * Test to see if timer_delete() sets errno==EINVAL for an invalid
+ * timerid. Recent POSIX versions leave this behavior undefined, so
+ * returning -1 with EINVAL, succeeding, or crashing (older glibc
+ * dereferences timerid as a pointer) are all accepted outcomes.
  */
 
 #include <time.h>
 #include <stdio.h>
 #include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/wait.h>
 #include "posixtest.h"
 
 #define BOGUSTIMERID 99999
 
 int test_main(int argc PTS_ATTRIBUTE_UNUSED, char **argv PTS_ATTRIBUTE_UNUSED)
 {
-	timer_t tid = (timer_t)BOGUSTIMERID;
-
-	if (timer_delete(tid) == -1) {
-		if (errno == EINVAL) {
-			printf
-			    ("timer_delete() returned -1 and set errno=EINVAL\n");
-			return PTS_PASS;
-		} else {
-			printf
-			    ("timer_delete() returned -1, but didn't set errno!=EINVAL\n");
-			return PTS_FAIL;
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	if (pid == -1) {
+		perror("fork");
+		return PTS_UNRESOLVED;
+	}
+
+	if (pid == 0) {
+		timer_t tid = (timer_t) BOGUSTIMERID;
+
+		if (timer_delete(tid) == -1) {
+			if (errno == EINVAL)
+				_exit(PTS_PASS);
+			printf("timer_delete() returned -1, but errno=%d (%s), not EINVAL\n", errno, strerror(errno));
+			fflush(stdout);
+			_exit(PTS_FAIL);
 		}
+		_exit(PTS_PASS);
+	}
+
+	if (waitpid(pid, &status, 0) == -1) {
+		perror("waitpid");
+		return PTS_UNRESOLVED;
+	}
+
+	if ((WIFSIGNALED(status) && (WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS)) ||
+		(WIFEXITED(status) && WEXITSTATUS(status) == PTS_PASS)) {
+		printf("Test PASSED\n");
+		return PTS_PASS;
 	}
 
-	printf("timer_delete() did not return -1\n");
-	return PTS_PASS;
+	printf("Test FAILED\n");
+	return PTS_FAIL;
 }
diff --git a/testcases/open_posix_testsuite/conformance/interfaces/timer_getoverrun/speculative/6-1.c b/testcases/open_posix_testsuite/conformance/interfaces/timer_getoverrun/speculative/6-1.c
index 3a8f1448d4d4..fbc0edc38f94 100644
--- a/testcases/open_posix_testsuite/conformance/interfaces/timer_getoverrun/speculative/6-1.c
+++ b/testcases/open_posix_testsuite/conformance/interfaces/timer_getoverrun/speculative/6-1.c
@@ -5,33 +5,58 @@
  * of this license, see the COPYING file at the top level of this
  * source tree.
  *
- * Test to see if timer_getoverrun() sets errno=EINVAL if no timers have been
- * created yet or not.  Since this is a "may" assertion, either way is
- * a pass.
+ * Test to see if timer_getoverrun() sets errno==EINVAL for an invalid
+ * timerid. Recent POSIX versions leave this behavior undefined, so
+ * returning -1 with EINVAL, succeeding, or crashing (older glibc
+ * dereferences timerid as a pointer) are all accepted outcomes.
  */
 
 #include <time.h>
 #include <stdio.h>
 #include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/wait.h>
 #include "posixtest.h"
 
 #define BOGUSTID 9999
 
 int test_main(int argc PTS_ATTRIBUTE_UNUSED, char **argv PTS_ATTRIBUTE_UNUSED)
 {
-	timer_t tid = (timer_t)BOGUSTID;
-
-	if (timer_getoverrun(tid) == -1) {
-		if (EINVAL == errno) {
-			printf("fcn returned -1 and errno=EINVAL\n");
-			return PTS_PASS;
-		} else {
-			printf("fcn returned -1, but errno!=EINVAL\n");
-			printf("Test FAILED\n");
-			return PTS_FAIL;
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	if (pid == -1) {
+		perror("fork");
+		return PTS_UNRESOLVED;
+	}
+
+	if (pid == 0) {
+		timer_t tid = (timer_t) BOGUSTID;
+
+		if (timer_getoverrun(tid) == -1) {
+			if (errno == EINVAL)
+				_exit(PTS_PASS);
+			printf("timer_getoverrun() returned -1, but errno=%d (%s), not EINVAL\n", errno, strerror(errno));
+			fflush(stdout);
+			_exit(PTS_FAIL);
 		}
+		_exit(PTS_PASS);
+	}
+
+	if (waitpid(pid, &status, 0) == -1) {
+		perror("waitpid");
+		return PTS_UNRESOLVED;
+	}
+
+	if ((WIFSIGNALED(status) && (WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS)) ||
+		(WIFEXITED(status) && WEXITSTATUS(status) == PTS_PASS)) {
+		printf("Test PASSED\n");
+		return PTS_PASS;
 	}
 
-	printf("fcn did not return -1\n");
-	return PTS_PASS;
+	printf("Test FAILED\n");
+	return PTS_FAIL;
 }
diff --git a/testcases/open_posix_testsuite/conformance/interfaces/timer_gettime/speculative/6-1.c b/testcases/open_posix_testsuite/conformance/interfaces/timer_gettime/speculative/6-1.c
index 586b0ed3a2b1..4bc1e7b43843 100644
--- a/testcases/open_posix_testsuite/conformance/interfaces/timer_gettime/speculative/6-1.c
+++ b/testcases/open_posix_testsuite/conformance/interfaces/timer_gettime/speculative/6-1.c
@@ -5,33 +5,59 @@
  * of this license, see the COPYING file at the top level of this
  * source tree.
  *
- * Test to see if timer_gettime() sets errno = EINVAL if no timers have been
- * created yet and it is called with a bogus timer ID.  Since this is a "may"
- * assertion, either way is pass.
+ * Test to see if timer_gettime() sets errno==EINVAL for an invalid
+ * timerid. Recent POSIX versions leave this behavior undefined, so
+ * returning -1 with EINVAL, succeeding, or crashing (older glibc
+ * dereferences timerid as a pointer) are all accepted outcomes.
  */
 
 #include <time.h>
 #include <stdio.h>
 #include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/wait.h>
 #include "posixtest.h"
 
 #define BOGUSTID 9999
 
 int test_main(int argc PTS_ATTRIBUTE_UNUSED, char **argv PTS_ATTRIBUTE_UNUSED)
 {
-	struct itimerspec its;
-	timer_t tid = (timer_t)BOGUSTID;
-
-	if (timer_gettime(tid, &its) == -1) {
-		if (EINVAL == errno) {
-			printf("fcn returned -1 and errno==EINVAL\n");
-			return PTS_PASS;
-		} else {
-			printf("fcn returned -1 but errno!=EINVAL\n");
-			printf("Test FAILED\n");
-			return PTS_FAIL;
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	if (pid == -1) {
+		perror("fork");
+		return PTS_UNRESOLVED;
+	}
+
+	if (pid == 0) {
+		struct itimerspec its;
+		timer_t tid = (timer_t) BOGUSTID;
+
+		if (timer_gettime(tid, &its) == -1) {
+			if (errno == EINVAL)
+				_exit(PTS_PASS);
+			printf("timer_gettime() returned -1, but errno=%d (%s), not EINVAL\n", errno, strerror(errno));
+			fflush(stdout);
+			_exit(PTS_FAIL);
 		}
+		_exit(PTS_PASS);
 	}
-	printf("fcn did not return -1\n");
-	return PTS_PASS;
+
+	if (waitpid(pid, &status, 0) == -1) {
+		perror("waitpid");
+		return PTS_UNRESOLVED;
+	}
+
+	if ((WIFSIGNALED(status) && (WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS)) ||
+		(WIFEXITED(status) && WEXITSTATUS(status) == PTS_PASS)) {
+		printf("Test PASSED\n");
+		return PTS_PASS;
+	}
+
+	printf("Test FAILED\n");
+	return PTS_FAIL;
 }
diff --git a/testcases/open_posix_testsuite/conformance/interfaces/timer_settime/speculative/12-1.c b/testcases/open_posix_testsuite/conformance/interfaces/timer_settime/speculative/12-1.c
index ac7f7bf24c39..0ae7ee28201c 100644
--- a/testcases/open_posix_testsuite/conformance/interfaces/timer_settime/speculative/12-1.c
+++ b/testcases/open_posix_testsuite/conformance/interfaces/timer_settime/speculative/12-1.c
@@ -5,36 +5,64 @@
  * of this license, see the COPYING file at the top level of this
  * source tree.
  *
- * Test to see if timer_settime() sets errno = EINVAL if no timers have been
- * created yet.  Since this is a "may" assertion, either way is a pass.
+ * Test to see if timer_settime() sets errno==EINVAL for an invalid
+ * timerid. Recent POSIX versions leave this behavior undefined, so
+ * returning -1 with EINVAL, succeeding, or crashing (older glibc
+ * dereferences timerid as a pointer) are all accepted outcomes.
  */
 
 #include <time.h>
 #include <stdio.h>
 #include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/wait.h>
 #include "posixtest.h"
 
 #define BOGUSTID 9999
 
 int test_main(int argc PTS_ATTRIBUTE_UNUSED, char **argv PTS_ATTRIBUTE_UNUSED)
 {
-	struct itimerspec its;
-	timer_t tid = (timer_t)BOGUSTID;
-	its.it_interval.tv_sec = 0;
-	its.it_interval.tv_nsec = 0;
-	its.it_value.tv_sec = 0;
-	its.it_value.tv_nsec = 0;
-
-	if (timer_settime(tid, 0, &its, NULL) == -1) {
-		if (EINVAL == errno) {
-			printf("fcn returned -1 and errno==EINVAL\n");
-			return PTS_PASS;
-		} else {
-			printf("fcn returned -1, but errno!=EINVAL\n");
-			printf("Test FAILED\n");
-			return PTS_FAIL;
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	if (pid == -1) {
+		perror("fork");
+		return PTS_UNRESOLVED;
+	}
+
+	if (pid == 0) {
+		struct itimerspec its;
+		timer_t tid = (timer_t) BOGUSTID;
+
+		its.it_interval.tv_sec = 0;
+		its.it_interval.tv_nsec = 0;
+		its.it_value.tv_sec = 0;
+		its.it_value.tv_nsec = 0;
+
+		if (timer_settime(tid, 0, &its, NULL) == -1) {
+			if (errno == EINVAL)
+				_exit(PTS_PASS);
+			printf("timer_settime() returned -1, but errno=%d (%s), not EINVAL\n", errno, strerror(errno));
+			fflush(stdout);
+			_exit(PTS_FAIL);
 		}
+		_exit(PTS_PASS);
 	}
-	printf("fcn did not return -1\n");
-	return PTS_PASS;
+
+	if (waitpid(pid, &status, 0) == -1) {
+		perror("waitpid");
+		return PTS_UNRESOLVED;
+	}
+
+	if ((WIFSIGNALED(status) && (WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS)) ||
+		(WIFEXITED(status) && WEXITSTATUS(status) == PTS_PASS)) {
+		printf("Test PASSED\n");
+		return PTS_PASS;
+	}
+
+	printf("Test FAILED\n");
+	return PTS_FAIL;
 }
-- 
2.55.0



More information about the ltp mailing list