Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F177546622
D61215.1794680081.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
7 KB
Referenced Files
None
Subscribers
None
D61215.1794680081.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D61215: libthr: Guard every key insertion on pshared cache bump
Attached
Detach File
Event Timeline