NetBSD Problem Report #60846

From wiz@netbsd.org  Sat Oct  3 17:58:21 2026
Return-Path: <wiz@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
	 client-signature RSA-PSS (2048 bits) client-digest SHA256)
	(Client CN "mail.netbsd.org", Issuer "YR1" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id 0A1EB1A923C
	for <gnats-bugs@gnats.NetBSD.org>; Sat,  3 Oct 2026 17:58:20 +0000 (UTC)
Message-Id: <20261003175820.0A0AC84D93@mail.netbsd.org>
Date: Sat,  3 Oct 2026 17:58:20 +0000 (UTC)
From: wiz@NetBSD.org
Reply-To: wiz@NetBSD.org
To: gnats-bugs@NetBSD.org
Subject: fork() disrupts futex
X-Send-Pr-Version: 3.95
X-From4GNATS: "wiz@NetBSD.org via gnats" <gnats-admin@NetBSD.org>

>Number:         60846
>Category:       kern
>Synopsis:       fork() disrupts futex
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    chs
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sat Oct 03 18:00:01 +0000 2026
>Last-Modified:  Sun Oct 04 21:59:44 +0000 2026
>Originator:     Thomas Klausner
>Release:        NetBSD 11.99.9/20261002
>Organization:

>Environment:


Architecture: x86_64
Machine: amd64
>Description:
Still trying to get a Linux program running, I found a weird bug in the futex handling.

A thread gets started and waits for a private futex;
the main thread wakes the private futex - this works.

However, when in between the thread start and the futex wake,
a child is forked (and reaped), the futex wake is not received by the
waiting thread.
>How-To-Repeat:

Save the test program below as futex-test.c and compile it on Linux,
or with pkgsrc/emulators/suse_gcc12 with:

/emul/linux/usr/bin/gcc-12 -o futex-test futex-test.c -lpthread

By default, it does not fork, but if the first command line argument is 'fork' it does.

Running it both ways on Debian 12.15 gives:

--- Linux output ---
# ./futex-test ; echo; ./futex-test fork
cc -o futex-test futex-test.c -lpthread
thread: futex waiting on address 0x557c663b0078
main: waking futex on address 0x557c663b0078
main: futex wake returned 1; joining
thread: futex wait return value 0 errno 0
RESULT: woken OK

thread: futex waiting on address 0x55ec13905078
main: forking child
child: sleeping
child: exiting
main: child reaped, return value 1971108, status 0
main: waking futex on address 0x55ec13905078
main: futex wake returned 1; joining
thread: futex wait return value 0 errno 0
RESULT: woken OK
--- end Linux output ---

while running it both ways on NetBSD-current's Linux emulation gives

--- NetBSD output ---
./futex-test; echo; ./futex-test fork
/emul/linux/usr/bin/gcc-12 -o futex-test futex-test.c -lpthread
thread: futex waiting on address 0x602090
main: waking futex on address 0x602090
main: futex wake returned 1; joining
thread: futex wait return value 0 errno 0
RESULT: woken OK

thread: futex waiting on address 0x602090
main: forking child
child: sleeping
child: exiting
main: child reaped, return value 8019, status 0
main: waking futex on address 0x602090
main: futex wake returned 0; joining
RESULT: WAKE LOST (join timed out)
--- end NetBSD output ---

--- begin futex-test.c ---
#define _GNU_SOURCE
#include <errno.h>
#include <linux/futex.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

static volatile int __attribute__((aligned(4))) word = 1;

static void *waiter(void *arg)
{
	(void)arg;
	errno = 0;
	fprintf(stdout, "thread: futex waiting on address %p\n", &word);
	long r = syscall(SYS_futex, &word, FUTEX_WAIT_PRIVATE, 1, NULL, NULL, 0);
	printf("thread: futex wait return value %ld errno %d\n", r, errno);
	return NULL;
}

int main(int argc, char **argv)
{
	pthread_t tw;
	struct timespec dl;
	int do_fork = (argc > 1 && strcmp(argv[1], "fork") == 0);

	pthread_create(&tw, NULL, waiter, NULL);
	/* give thread time to block */
	usleep(500 * 1000);

	if (do_fork) {
		printf("main: forking child\n");
		pid_t c = fork();
		if (c == 0) {
			printf("child: sleeping\n");
			usleep(1000 * 1000);
			printf("child: exiting\n");
			_exit(0);
		}
		usleep(1000 * 1000);
		int status, ret;
		ret = waitpid(c, &status, 0);
		printf("main: child reaped, return value %d, status %d\n", ret, status);
	}

	word = 2;
	fprintf(stdout, "main: waking futex on address %p\n", &word);
	long nw = syscall(SYS_futex, &word, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);
	printf("main: futex wake returned %ld; joining\n", nw);

	clock_gettime(CLOCK_REALTIME, &dl);
	dl.tv_sec += 3;
	if (pthread_timedjoin_np(tw, NULL, &dl) == 0) {
		printf("RESULT: woken OK\n");
		return 0;
	}
	printf("RESULT: WAKE LOST (join timed out)\n");
	return 1;
}
--- end ---

>Fix:
Yes, please!

>Release-Note:

>Audit-Trail:

Responsible-Changed-From-To: kern-bug-people->chs
Responsible-Changed-By: chs@NetBSD.org
Responsible-Changed-When: Sun, 04 Oct 2026 21:59:44 +0000
Responsible-Changed-Why:
I have patches that fix this.


>Unformatted:

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.