[PATCH] [RFC] Add test to detect spurious suspend wakeups

Richard Weinberger <[email protected]>
Newsgroups dev.linux.lists.xenomai
Message-ID <[email protected]>
If a task is suspend and SIGSHADOW is used to harden it,
it can happen that the target task itself harends (e.g. by using
a syscall). Then the already hardened and suspended task will see
SIGSHADOW and resumes.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Richard Weinberger <[email protected]>
---
 configure.ac                                  |   1 +
 testsuite/smokey/Makefile.am                  |   3 +
 .../smokey/spurious-suspend-wake/Makefile.am  |  10 +
 .../spurious-suspend-wake.c                   | 238 ++++++++++++++++++
 4 files changed, 252 insertions(+)
 create mode 100644 testsuite/smokey/spurious-suspend-wake/Makefile.am
 create mode 100644 testsuite/smokey/spurious-suspend-wake/spurious-suspend-wake.c

diff --git a/configure.ac b/configure.ac
index 0d6dd87b6..69d9947fd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1086,6 +1086,7 @@ AC_CONFIG_FILES([ \
 	testsuite/smokey/bufp/Makefile \
 	testsuite/smokey/sigdebug/Makefile \
 	testsuite/smokey/timerfd/Makefile \
+	testsuite/smokey/spurious-suspend-wake/Makefile \
 	testsuite/smokey/tsc/Makefile \
 	testsuite/smokey/leaks/Makefile \
 	testsuite/smokey/memcheck/Makefile \
diff --git a/testsuite/smokey/Makefile.am b/testsuite/smokey/Makefile.am
index c49a5d77f..f3c1151b6 100644
--- a/testsuite/smokey/Makefile.am
+++ b/testsuite/smokey/Makefile.am
@@ -37,6 +37,7 @@ COBALT_SUBDIRS = 	\
 	setsched	\
 	sigdebug	\
 	timerfd		\
+	spurious-suspend-wake \
 	tsc		\
 	xddp		\
 	y2038		\
@@ -82,6 +83,7 @@ DIST_SUBDIRS = 		\
 	setsched	\
 	sigdebug	\
 	timerfd		\
+	spurious-suspend-wake \
 	tsc		\
 	xddp		\
 	y2038		\
@@ -130,6 +132,7 @@ smokey_LDADD = 					\
 	$(plugin_list)				\
 	../../lib/smokey/libsmokey@[email protected]		\
 	../../lib/copperplate/libcopperplate@[email protected]	\
+	../../lib/alchemy/libalchemy@[email protected] \
 	@XENO_CORE_LDADD@			\
 	 @XENO_USER_LDADD@			\
 	-lpthread -lrt
diff --git a/testsuite/smokey/spurious-suspend-wake/Makefile.am b/testsuite/smokey/spurious-suspend-wake/Makefile.am
new file mode 100644
index 000000000..b2f4f3b6c
--- /dev/null
+++ b/testsuite/smokey/spurious-suspend-wake/Makefile.am
@@ -0,0 +1,10 @@
+noinst_LIBRARIES = libspurious-suspend-wake.a
+
+libspurious_suspend_wake_a_SOURCES = spurious-suspend-wake.c
+
+CCLD = $(top_srcdir)/scripts/wrap-link.sh $(CC)
+
+libspurious_suspend_wake_a_CPPFLAGS =   \
+        @XENO_USER_CFLAGS@              \
+        -I$(top_srcdir)/include
+
diff --git a/testsuite/smokey/spurious-suspend-wake/spurious-suspend-wake.c b/testsuite/smokey/spurious-suspend-wake/spurious-suspend-wake.c
new file mode 100644
index 000000000..969bd5344
--- /dev/null
+++ b/testsuite/smokey/spurious-suspend-wake/spurious-suspend-wake.c
@@ -0,0 +1,238 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sched.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <smokey/smokey.h>
+#include <alchemy/task.h>
+
+smokey_test_plugin(spurious_suspend_wake,
+		   SMOKEY_ARGLIST(
+			   SMOKEY_INT(iters),
+			   SMOKEY_INT(settle_us),
+			   SMOKEY_INT(quiet_us),
+			   SMOKEY_INT(victim_cpu),
+			   SMOKEY_INT(suspender_cpu),
+		   ),
+		   "Detect SIGSHADOW vs xnthread_suspend race that "
+		   "spuriously resumes an rt_task_suspend()'d task.");
+
+static volatile unsigned long victim_counter;
+static volatile bool stop;
+static RT_TASK victim_task;
+static RT_TASK suspender_task;
+
+static int iters		= 5000;
+static int settle_us		= 100;
+static int quiet_us		= 200;
+static int victim_cpu		= 0;
+static int suspender_cpu	= 1;
+
+static unsigned int spurious;
+static unsigned long max_delta;
+static int cycles_done;
+
+static void victim_body(void *arg)
+{
+	(void)arg;
+
+	/*
+	 * Tight relax/harden bouncing:
+	 *  - syscall(getpid) is a raw Linux syscall, sets XNRELAX on
+	 *    return, leaving us in secondary mode.
+	 *  - rt_task_sleep() resolves via libcobalt as a primary-mode
+	 *    syscall, so the wrapper runs xnthread_harden() before the
+	 *    sleep proper. On return we are back in primary mode.
+	 *
+	 * The window between the two is when the deferred SIGSHADOW
+	 * HARDEN posted by a concurrent rt_task_suspend() can land on a
+	 * non-XNRELAX victim and trigger the spurious wake.
+	 */
+	while (!stop) {
+		syscall(__NR_getpid);
+		rt_task_sleep(1000);	/* 1us, also forces a harden */
+		__atomic_add_fetch(&victim_counter, 1, __ATOMIC_RELAXED);
+	}
+}
+
+static void suspender_body(void *arg)
+{
+	int i, ret;
+
+	(void)arg;
+
+	for (i = 0; i < iters && !stop; i++) {
+		unsigned long c1, c2, delta;
+
+		ret = rt_task_suspend(&victim_task);
+		if (ret) {
+			smokey_warning("rt_task_suspend at iter %d: %s",
+				       i, symerror(ret));
+			break;
+		}
+
+		/*
+		 * Let SIGSHADOW HARDEN actually fire so the victim
+		 * either properly blocks on XNSUSP or, if the race
+		 * fired, has already been spuriously resumed.
+		 */
+		rt_task_sleep((RTIME)settle_us * 1000);
+
+		c1 = __atomic_load_n(&victim_counter, __ATOMIC_RELAXED);
+
+		/* Quiet window: victim must NOT advance. */
+		rt_task_sleep((RTIME)quiet_us * 1000);
+
+		c2 = __atomic_load_n(&victim_counter, __ATOMIC_RELAXED);
+
+		ret = rt_task_resume(&victim_task);
+		if (ret) {
+			smokey_warning("rt_task_resume at iter %d: %s",
+				       i, symerror(ret));
+			break;
+		}
+
+		delta = c2 - c1;
+		if (delta != 0) {
+			spurious++;
+			if (delta > max_delta)
+				max_delta = delta;
+			if (spurious <= 5)
+				smokey_trace("iter %d: victim advanced by "
+					     "%lu in quiet window", i, delta);
+		}
+
+		/* Let the victim accumulate forward progress between
+		 * cycles, otherwise we hammer it continuously and can no
+		 * longer tell forward progress from wake. */
+		rt_task_sleep((RTIME)quiet_us * 1000);
+	}
+
+	cycles_done = i;
+}
+
+static int run_spurious_suspend_wake(struct smokey_test *t, int argc,
+				     char *const argv[])
+{
+	cpu_set_t set;
+	int ret, ncpu;
+
+	smokey_parse_args(t, argc, argv);
+	if (SMOKEY_ARG_ISSET(spurious_suspend_wake, iters))
+		iters = SMOKEY_ARG_INT(spurious_suspend_wake, iters);
+	if (SMOKEY_ARG_ISSET(spurious_suspend_wake, settle_us))
+		settle_us = SMOKEY_ARG_INT(spurious_suspend_wake, settle_us);
+	if (SMOKEY_ARG_ISSET(spurious_suspend_wake, quiet_us))
+		quiet_us = SMOKEY_ARG_INT(spurious_suspend_wake, quiet_us);
+	if (SMOKEY_ARG_ISSET(spurious_suspend_wake, victim_cpu))
+		victim_cpu = SMOKEY_ARG_INT(spurious_suspend_wake, victim_cpu);
+	if (SMOKEY_ARG_ISSET(spurious_suspend_wake, suspender_cpu))
+		suspender_cpu = SMOKEY_ARG_INT(spurious_suspend_wake,
+					       suspender_cpu);
+
+	ncpu = sysconf(_SC_NPROCESSORS_ONLN);
+	if (ncpu < 2) {
+		smokey_note("need >= 2 online CPUs to drive the race");
+		return -ENOSYS;
+	}
+	if (victim_cpu < 0 || suspender_cpu < 0 ||
+	    victim_cpu >= ncpu || suspender_cpu >= ncpu ||
+	    victim_cpu == suspender_cpu) {
+		smokey_warning("victim_cpu and suspender_cpu must be "
+			       "distinct values in [0, %d)", ncpu);
+		return -EINVAL;
+	}
+	if (iters <= 0 || settle_us <= 0 || quiet_us <= 0) {
+		smokey_warning("iters, settle_us, quiet_us must be positive");
+		return -EINVAL;
+	}
+
+	stop = false;
+	victim_counter = 0;
+	spurious = 0;
+	max_delta = 0;
+	cycles_done = 0;
+
+	ret = rt_task_create(&victim_task, "ssw-victim", 0, 50, T_JOINABLE);
+	if (ret) {
+		smokey_warning("rt_task_create(victim): %s", symerror(ret));
+		return ret;
+	}
+
+	CPU_ZERO(&set);
+	CPU_SET(victim_cpu, &set);
+	ret = rt_task_set_affinity(&victim_task, &set);
+	if (ret) {
+		smokey_warning("rt_task_set_affinity(victim, %d): %s",
+			       victim_cpu, symerror(ret));
+		goto fail_after_victim_create;
+	}
+
+	ret = rt_task_create(&suspender_task, "ssw-suspender", 0, 50,
+			     T_JOINABLE);
+	if (ret) {
+		smokey_warning("rt_task_create(suspender): %s", symerror(ret));
+		goto fail_after_victim_create;
+	}
+
+	CPU_ZERO(&set);
+	CPU_SET(suspender_cpu, &set);
+	ret = rt_task_set_affinity(&suspender_task, &set);
+	if (ret) {
+		smokey_warning("rt_task_set_affinity(suspender, %d): %s",
+			       suspender_cpu, symerror(ret));
+		goto fail_after_suspender_create;
+	}
+
+	ret = rt_task_start(&victim_task, victim_body, NULL);
+	if (ret) {
+		smokey_warning("rt_task_start(victim): %s", symerror(ret));
+		goto fail_after_suspender_create;
+	}
+
+	smokey_trace("victim on CPU%d, suspender on CPU%d, %d iters, "
+		     "settle=%dus, quiet=%dus",
+		     victim_cpu, suspender_cpu, iters, settle_us, quiet_us);
+
+	ret = rt_task_start(&suspender_task, suspender_body, NULL);
+	if (ret) {
+		smokey_warning("rt_task_start(suspender): %s", symerror(ret));
+		stop = true;
+		rt_task_join(&victim_task);
+		goto fail_after_suspender_create;
+	}
+
+	rt_task_join(&suspender_task);
+
+	stop = true;
+	/*
+	 * Belt and braces: the suspender may have left the victim
+	 * suspended if it broke out early. rt_task_resume is a no-op on
+	 * a non-suspended task, so this is always safe.
+	 */
+	rt_task_resume(&victim_task);
+	rt_task_join(&victim_task);
+
+	rt_task_delete(&suspender_task);
+	rt_task_delete(&victim_task);
+
+	if (spurious) {
+		smokey_warning("FAIL: %u/%d cycles saw the victim advance "
+			       "during the quiet window (max delta %lu)",
+			       spurious, cycles_done, max_delta);
+		return -EPROTO;
+	}
+
+	smokey_trace("no spurious wakes in %d cycles", cycles_done);
+	return 0;
+
+fail_after_suspender_create:
+	rt_task_delete(&suspender_task);
+fail_after_victim_create:
+	rt_task_delete(&victim_task);
+	return ret;
+}
-- 
2.51.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.