NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

lib/59125: pthread_once(3) races with concurrent fork



>Number:         59125
>Category:       lib
>Synopsis:       pthread_once(3) races with concurrent fork
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    lib-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Mon Mar 03 21:35:01 +0000 2025
>Originator:     Taylor R Campbell
>Release:        current, 10, 9, ...
>Organization:
The NetBSD Forkonceler
>Environment:
>Description:
Suppose thread A calls pthread_once(&once, &init), and thread B calls fork at the same time.

In thread A, pthread_once will (a) take the lock inside &once and then (b) call the init function.

But if the fork happens between (a) and (b), the child will see the lock held with no threads running to release it.  So when the child process tries pthread_once(&once, ...), it will hang.
>How-To-Repeat:
#include <sys/wait.h>

#include <err.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_once_t once = PTHREAD_ONCE_INIT;
static void
init(void)
{
}

static void *
forkit(void *cookie)
{
	pthread_barrier_t *bar = cookie;
	pid_t pid;
	int status;

	(void)pthread_barrier_wait(bar);
	if ((pid = fork()) == -1)
		err(1, "fork");
	if (pid == 0) {
		(void)alarm(1);
		(void)pthread_once(&once, &init);
		_exit(0);
	}
	if (waitpid(pid, &status, 0) == -1)
		err(1, "waitpid");
	if (WIFSIGNALED(status)) {
		errx(1, "child exited on signal %d (%s)", WTERMSIG(status),
		    strsignal(WTERMSIG(status)));
	}
	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
		errx(1, "child exited 0x%x", status);
	return NULL;
}

static void
test(void)
{
	pthread_barrier_t bar;
	pthread_t t;
	int error;

	error = pthread_barrier_init(&bar, NULL, 2);
	if (error)
		errc(1, error, "pthread_barrier_init");
	error = pthread_create(&t, NULL, &forkit, &bar);
	if (error)
		errc(1, error, "pthread_create");
	(void)pthread_barrier_wait(&bar);
	(void)pthread_once(&once, &init);
	error = pthread_join(t, NULL);
	if (error)
		errc(1, error, "pthread_join");
}

int
main(void)
{

	for (unsigned long long n = 1;; n++) {
		pid_t pid;
		int status;

		if ((pid = fork()) == -1)
			err(1, "fork");
		if (pid == 0) {
			test();
			_exit(0);
		}
		if (waitpid(pid, &status, 0) == -1)
			err(1, "waitpid");
		if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
			errx(1, "test exited 0x%x after %zu trial%s", status,
			    n, n == 1 ? "" : "s");
		}
	}
	return 0;
}
>Fix:
Yes, please!

See also: PR lib/59124: arc4random(3): first call in process races with concurrent fork



Home | Main Index | Thread Index | Old Index