[PATCH] nptl: avoid invalid memory access in pthread_join after fork
Wupeng
wu.pengA@h3c.com
Fri Jun 5 09:55:36 GMT 2026
Hi all,
We recently encountered a crash scenario related to pthread_join() after fork(), and would like to propose a fix for discussion.
Problem description
===================
In application, a shared library creates worker threads internally and joins them in the library destructor. Under normal conditions this works correctly.
However, after fork(), the child process may crash with SIGSEGV during pthread_join().
After reviewing the the glibc internals, we found that:
After fork(), only the calling thread survives in the child process. All other threads are terminated, but their stack mappings are not unmapped immediately . Instead, glibc moves them into dl_stack_cache:
list_splice (&GL (dl_stack_used), &GL (dl_stack_cache));
// sysdeps/nptl/fork.h:135
These cached stacks may later be partially unmapped when the cache exceeds its size limit:
if (__glibc_unlikely (GL (dl_stack_cache_actsize)
> __nptl_stack_cache_maxsize))
__nptl_free_stacks (__nptl_stack_cache_maxsize);
This means that a stale pthread_t in the child process may eventually point to memory that has already been unmapped or reused.
If pthread_join() is called on such a thread descriptor, glibc may dereference invalid memory and crash.
Calling pthread_join() on threads that no longer exist after fork() is technically undefined behavior. However, in real-world engineering scenarios this is difficult to avoid completely, especially when:
- pthread_join() is located in shared cleanup/destructor paths
- parent and child processes share common cleanup logic
- third-party libraries internally manage threads
It may be worthwhile for glibc to handle this case more defensively, to avoid potential segmentation faults.
Proposed fix
============
The patch checks whether the target pthread descriptor still exists in:
- dl_stack_used
- dl_stack_user
If the descriptor is already in dl_stack_cache, or no longer exists in any known stack list, pthread_join() returns ESRCH instead of dereferencing potentially invalid memory.
Patch
---
commit 5966c8e3750f3fa9555b0693e3510e705168be5e
Author: PengWu <wu.pengA@h3c.com>
Date: Fri Jun 5 17:15:39 2026 +0800
nptl: avoid invalid memory access in pthread_join after fork
Signed-off-by: PengWu <wu.pengA@h3c.com>
Co-authored-by: MingxiangLu <lu.mingxiang@h3c.com>
diff --git a/nptl/pthread_join_common.c b/nptl/pthread_join_common.c
index 701f6a53..f4f1bfe6 100644
--- a/nptl/pthread_join_common.c
+++ b/nptl/pthread_join_common.c
@@ -21,6 +21,8 @@
#include <stap-probe.h>
#include <time.h>
#include <futex-internal.h>
+#include <list.h>
+#include <ldsodefs.h>
static void
cleanup (void *arg)
@@ -39,6 +41,37 @@ __pthread_clockjoin_ex (pthread_t threadid, void **thread_return,
{
struct pthread *pd = (struct pthread *) threadid;
+ lll_lock (GL (dl_stack_cache_lock), LLL_PRIVATE);
+ int tid_in_maps = 0;
+ list_t *runp;
+
+ list_for_each (runp, &GL (dl_stack_cache))
+ {
+ struct pthread *curr = list_entry (runp, struct pthread, list);
+ if (pd == curr)
+ return ESRCH;
+ }
+ list_for_each (runp, &GL (dl_stack_used))
+ {
+ struct pthread *curr = list_entry (runp, struct pthread, list);
+ if (tid_in_maps)
+ break;
+ if (pd == curr)
+ tid_in_maps = 1;
+ }
+ list_for_each (runp, &GL (dl_stack_user))
+ {
+ struct pthread *curr = list_entry (runp, struct pthread, list);
+ if (tid_in_maps)
+ break;
+ if (pd == curr)
+ tid_in_maps = 1;
+ }
+ lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
+
+ if (!tid_in_maps)
+ return ESRCH;
+
/* Make sure the descriptor is valid. */
if (INVALID_NOT_TERMINATED_TD_P (pd))
/* Not a valid thread handle. */
-----
Testcase:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#define THREAD_STACK_SIZE (256 * 1024)
int thread_count = 0;
void *thread_func(void *arg)
{
sleep(10);
return NULL;
}
int main(int argc, char *argv[])
{
int thread_max;
pthread_t *threads;
pthread_attr_t attr;
pid_t pid;
int i, ret;
if (argc != 2)
return 1;
thread_max = atoi(argv[1]);
threads = malloc(sizeof(pthread_t) * thread_max);
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE);
for (i = 0; i < thread_max; i++) {
ret = pthread_create(&threads[i], &attr,
thread_func, NULL);
if (ret != 0)
break;
thread_count++;
}
pid = fork();
if (pid == 0) {
printf("Child process start\n");
} else {
int status;
printf("Parent process wait child pid=%d\n", pid);
waitpid(pid, &status, 0);
printf("Parent detected child exit\n");
if (WIFSIGNALED(status)) {
printf("child killed by signal %d\n", WTERMSIG(status));
}
}
for (i = 0; i < thread_count; i++) {
ret = pthread_join(threads[i], NULL);
if (ret)
printf("pthread_join error: %s\n",
strerror(ret));
}
return 0;
}
Reproducer result before fix:
Main process before fork, pid=1616
Parent process wait child pid=1717
Child process start, pid=1717
...
tid[58]:0x7f3e19a156c0
child killed by signal 11
Reproducer result after fix:
Main process before fork, pid=1732
Parent process wait child pid=1833
Child process start, pid=1833
...
pthread_join threads[96] error:
Resource temporarily unavailable(ret=11)
...
pthread_join over
-------------------------------------------------------------------------------------------------------------------------------------
本邮件及其附件含有新华三集团的保密信息,仅限于发送给上面地址中列出的个人或群组。
禁止任何其他人以任何形式使用(包括但不限于全部或部分地泄露、复制、或散发)本邮件中的信息。
如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件!
This e-mail and its attachments contain confidential information from New H3C, which is intended only for the person or entity whose address is listed above.
Any use of the information contained herein in any way (including, but not limited to, total or partial disclosure, reproduction, or dissemination) by persons other than the intended recipient(s) is prohibited.
If you receive this e-mail in error, please notify the sender by phone or email immediately and delete it!
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20260605/e79371d7/attachment-0001.htm>
More information about the Libc-alpha
mailing list