Page MenuHomeFreeBSD

D61215.1794680081.diff
No OneTemporary

Authored By
Unknown
Size
7 KB
Referenced Files
None
Subscribers
None

D61215.1794680081.diff

diff --git a/lib/libthr/tests/Makefile b/lib/libthr/tests/Makefile
--- a/lib/libthr/tests/Makefile
+++ b/lib/libthr/tests/Makefile
@@ -37,6 +37,7 @@
ATF_TESTS_C+= umtx_op_test
ATF_TESTS_C+= pthread_sigqueue_test
ATF_TESTS_C+= pthread_tryjoin_test
+ATF_TESTS_C+= pshared_cache_test
LIBADD+= pthread
LIBADD.fpu_test+= m
diff --git a/lib/libthr/tests/pshared_cache_test.c b/lib/libthr/tests/pshared_cache_test.c
new file mode 100644
--- /dev/null
+++ b/lib/libthr/tests/pshared_cache_test.c
@@ -0,0 +1,169 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2026 ShengYi Hung <aokblast@FreeBSD.org>
+ */
+
+/*
+ * Stress the per-thread cache of process-shared off-page lookups
+ * against signal handlers that use process-shared mutexes.
+ *
+ * The main thread holds mutex C and keeps alternating lookups of the
+ * free mutex A and of C, so each lookup misses the one-entry cache and
+ * refills it. Another thread signals the main thread continuously,
+ * and the handler takes A with pthread_mutex_trylock().
+ *
+ * If the handler runs after the cache key was updated to A but before
+ * the value was, the lookup returns C's off-page. The handler then
+ * finds that "A" is owned by its own thread and trylock fails with
+ * EBUSY. If the handler refills the cache in the same window, the
+ * main thread finishes the interrupted insert and leaves A paired with
+ * C's off-page, so pthread_mutex_isowned_np(A) returns true.
+ */
+
+#include <sys/mman.h>
+#include <sys/time.h>
+
+#include <atf-c.h>
+#include <pthread.h>
+#include <pthread_np.h>
+#include <signal.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define TEST_SECONDS 5
+
+static pthread_mutex_t *mtx_a, *mtx_c;
+static pthread_t main_thr;
+static atomic_bool stop;
+static volatile sig_atomic_t handler_err;
+static atomic_uint handler_runs;
+
+static void
+handler(int sig __unused)
+{
+ int error;
+
+ error = pthread_mutex_trylock(mtx_a);
+ if (error == 0)
+ error = pthread_mutex_unlock(mtx_a);
+ if (error != 0)
+ handler_err = error;
+ atomic_fetch_add_explicit(&handler_runs, 1, memory_order_release);
+}
+
+/*
+ * Send one signal at a time and wait for it to be handled. Flooding
+ * the target keeps a signal pending across sigreturn, so it is
+ * redelivered at the same PC and the main thread barely advances.
+ * The random pause spreads the interruption points over the loop.
+ */
+static void *
+signaller(void *arg __unused)
+{
+ unsigned int runs;
+ volatile unsigned int spin;
+
+ while (!atomic_load_explicit(&stop, memory_order_relaxed)) {
+ runs = atomic_load_explicit(&handler_runs,
+ memory_order_acquire);
+ (void)pthread_kill(main_thr, SIGUSR1);
+ while (atomic_load_explicit(&handler_runs,
+ memory_order_acquire) == runs &&
+ !atomic_load_explicit(&stop, memory_order_relaxed))
+ ;
+ for (spin = arc4random_uniform(1024); spin > 0; spin--)
+ ;
+ }
+ return (NULL);
+}
+
+static void
+pshared_mutex_init(pthread_mutex_t *m)
+{
+ pthread_mutexattr_t attr;
+
+ ATF_REQUIRE_EQ(pthread_mutexattr_init(&attr), 0);
+ ATF_REQUIRE_EQ(pthread_mutexattr_setpshared(&attr,
+ PTHREAD_PROCESS_SHARED), 0);
+ ATF_REQUIRE_EQ(pthread_mutexattr_settype(&attr,
+ PTHREAD_MUTEX_ERRORCHECK), 0);
+ ATF_REQUIRE_EQ(pthread_mutex_init(m, &attr), 0);
+ ATF_REQUIRE_EQ(pthread_mutexattr_destroy(&attr), 0);
+}
+
+ATF_TC(pshared_cache_signal);
+ATF_TC_HEAD(pshared_cache_signal, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that a signal handler interrupting a pshared cache "
+ "refill does not see a mismatched key and off-page");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+
+ATF_TC_BODY(pshared_cache_signal, tc)
+{
+ struct sigaction sa;
+ struct timespec now, end;
+ pthread_t thr;
+ void *p;
+ unsigned long iter;
+
+ p = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_SHARED, -1, 0);
+ ATF_REQUIRE(p != MAP_FAILED);
+ mtx_a = p;
+ mtx_c = mtx_a + 1;
+ pshared_mutex_init(mtx_a);
+ pshared_mutex_init(mtx_c);
+ ATF_REQUIRE_EQ(pthread_mutex_lock(mtx_c), 0);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = handler;
+ sigemptyset(&sa.sa_mask);
+ ATF_REQUIRE_EQ(sigaction(SIGUSR1, &sa, NULL), 0);
+
+ main_thr = pthread_self();
+ ATF_REQUIRE_EQ(pthread_create(&thr, NULL, signaller, NULL), 0);
+
+ ATF_REQUIRE_EQ(clock_gettime(CLOCK_MONOTONIC, &end), 0);
+ end.tv_sec += TEST_SECONDS;
+ for (iter = 0; handler_err == 0; iter++) {
+ if (pthread_mutex_isowned_np(mtx_a))
+ break;
+ if (!pthread_mutex_isowned_np(mtx_c))
+ break;
+ if ((iter & 0xfff) == 0) {
+ ATF_REQUIRE_EQ(clock_gettime(CLOCK_MONOTONIC, &now), 0);
+ if (timespeccmp(&now, &end, >=))
+ break;
+ }
+ }
+
+ atomic_store_explicit(&stop, true, memory_order_relaxed);
+ ATF_REQUIRE_EQ(pthread_join(thr, NULL), 0);
+
+ ATF_CHECK_MSG(handler_err == 0,
+ "trylock of A in signal handler failed: %s",
+ strerror(handler_err));
+ ATF_CHECK_MSG(!pthread_mutex_isowned_np(mtx_a),
+ "mutex A reported as owned after %lu iterations", iter);
+ ATF_CHECK_MSG(pthread_mutex_isowned_np(mtx_c),
+ "mutex C reported as not owned after %lu iterations", iter);
+ ATF_REQUIRE(handler_runs > 0);
+
+ ATF_REQUIRE_EQ(pthread_mutex_unlock(mtx_c), 0);
+ ATF_REQUIRE_EQ(pthread_mutex_destroy(mtx_a), 0);
+ ATF_REQUIRE_EQ(pthread_mutex_destroy(mtx_c), 0);
+ ATF_REQUIRE_EQ(munmap(p, getpagesize()), 0);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, pshared_cache_signal);
+
+ return (atf_no_error());
+}
diff --git a/lib/libthr/thread/thr_private.h b/lib/libthr/thread/thr_private.h
--- a/lib/libthr/thread/thr_private.h
+++ b/lib/libthr/thread/thr_private.h
@@ -519,6 +519,7 @@
void *pshared_key;
void *pshared_val;
unsigned long pshared_gen;
+ unsigned long pshared_seq;
/*
* Current locks bitmap for rtld. */
diff --git a/lib/libthr/thread/thr_pshared.c b/lib/libthr/thread/thr_pshared.c
--- a/lib/libthr/thread/thr_pshared.c
+++ b/lib/libthr/thread/thr_pshared.c
@@ -107,19 +107,35 @@
static void *
pshared_cache_lookup(struct pthread *curthread, void *key)
{
+ unsigned long seq;
+ void *res;
+
+ seq = curthread->pshared_seq;
+ __compiler_membar();
if (curthread->pshared_key == key &&
curthread->pshared_gen == atomic_load_long(&pshared_gen.val))
- return (curthread->pshared_val);
- return (NULL);
+ res = curthread->pshared_val;
+ else
+ res = NULL;
+ __compiler_membar();
+ if (curthread->pshared_seq != seq)
+ res = NULL;
+ return (res);
}
static void
pshared_cache_insert(struct pthread *curthread, void *key, void *val,
unsigned long gen)
{
+ // Hold the pshared_lock
+ THR_ASSERT_LOCKLEVEL(curthread);
+ THR_ASSERT((pshared_lock.rw_state &
+ (URWLOCK_WRITE_OWNER | URWLOCK_MAX_READERS)) != 0,
+ "pshared_cache_insert: pshared_lock not held");
curthread->pshared_key = key;
curthread->pshared_val = val;
curthread->pshared_gen = gen;
+ curthread->pshared_seq++;
}
/*
@@ -276,7 +292,6 @@
{
struct pthread *curthread;
void *res;
- unsigned long gen;
int fd, ins_done;
curthread = _get_curthread();
@@ -288,13 +303,13 @@
if (res != NULL)
return (res);
pshared_rlock(curthread);
- gen = pshared_gen.val;
res = pshared_lookup(key);
+ if (res != NULL)
+ pshared_cache_insert(curthread, key, res,
+ pshared_gen.val);
pshared_unlock(curthread);
- if (res != NULL) {
- pshared_cache_insert(curthread, key, res, gen);
+ if (res != NULL)
return (res);
- }
}
fd = _umtx_op(NULL, UMTX_OP_SHM, doalloc ? UMTX_SHM_CREAT :
UMTX_SHM_LOOKUP, key, NULL);
@@ -305,14 +320,13 @@
if (res == MAP_FAILED)
return (NULL);
pshared_wlock(curthread);
- gen = pshared_gen.val;
ins_done = pshared_insert(key, &res);
+ if (ins_done)
+ pshared_cache_insert(curthread, key, res, pshared_gen.val);
pshared_unlock(curthread);
if (!ins_done) {
pshared_clean(key, res);
res = NULL;
- } else {
- pshared_cache_insert(curthread, key, res, gen);
}
return (res);
}

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 14, 6:14 PM (12 h, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41695791
Default Alt Text
D61215.1794680081.diff (7 KB)

Event Timeline