NetBSD Problem Report #59578

From www@netbsd.org  Wed Aug  6 18:58:36 2025
Return-Path: <www@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)
	 client-signature RSA-PSS (2048 bits))
	(Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id EE8A61A923A
	for <gnats-bugs@gnats.NetBSD.org>; Wed,  6 Aug 2025 18:58:35 +0000 (UTC)
Message-Id: <20250806185834.428BF1A923C@mollari.NetBSD.org>
Date: Wed,  6 Aug 2025 18:58:34 +0000 (UTC)
From: furkanonder@protonmail.com
Reply-To: furkanonder@protonmail.com
To: gnats-bugs@NetBSD.org
Subject: open() hangs indefinitely on FIFO with frequent signal interruption
X-Send-Pr-Version: www-1.0

>Number:         59578
>Category:       kern
>Synopsis:       open() hangs indefinitely on FIFO with frequent signal interruption
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          needs-pullups
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Wed Aug 06 19:00:00 +0000 2025
>Closed-Date:    
>Last-Modified:  Wed Sep 23 19:20:01 +0000 2026
>Originator:     Furkan Onder
>Release:        NetBSD 10.0
>Organization:
Cpython
>Environment:
NetBSD home.localhost 10.0 NetBSD 10.0 (GENERIC) #0: Thu Mar 28 08:33:33 UTC 2024  mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC amd64
>Description:
This issue was exposed through the CPython test suite, specifically the `test_eintr` module's `SocketEINTRTest.test_os_open` test,
which hangs indefinitely on NetBSD 10.0 (see https://github.com/python/cpython/issues/137397).

The underlying problem is that when attempting to open a FIFO for writing (O_WRONLY) while the process receives frequent signals,
the open() system call gets repeatedly interrupted with EINTR and never completes successfully, even when a reader process is
available on the other end.

This creates an infinite loop where the process hangs indefinitely, continuously retrying the open() call which always returns EINTR.
>How-To-Repeat:
Save the following C program as `reproducer.c`

```
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

volatile sig_atomic_t signal_count = 0;

/**
 * Signal handler for SIGALRM.
 */
void handle_signal(int sig) {
    (void)sig;   // Suppress unused parameter warning
    signal_count++;
}

/**
 * Retry open() if it's interrupted by a signal (EINTR).
 */
int safe_open(const char *path, int flags) {
    int fd;
    while ((fd = open(path, flags)) < 0) {
        if (errno != EINTR) {
            perror("open");
            exit(EXIT_FAILURE);
        }
        write(STDOUT_FILENO, ".", 1);
    }
    return fd;
}

/**
 * Retry close() if it's interrupted by a signal (EINTR).
 */
int safe_close(int fd) {
    int ret;
    while ((ret = close(fd)) < 0) {
        if (errno != EINTR) {
            perror("close");
            return ret;
        }
        write(STDOUT_FILENO, "C", 1);
    }
    return ret;
}

/**
 * Sleep for a specified number of milliseconds.
 */
void sleep_ms(long ms) {
    struct timespec ts;
    ts.tv_sec = ms / 1000;
    ts.tv_nsec = (ms % 1000) * 1000000L;
    nanosleep(&ts, NULL);
}

/**
 * Set up a timer to send SIGALRM every 10 milliseconds.
 */
void setup_timer(void) {
    struct sigaction sa = {0};
    sa.sa_handler = handle_signal;
    sigaction(SIGALRM, &sa, NULL);

    struct itimerval timer = {
        .it_value = {0, 10000},      // Start after 10ms
        .it_interval = {0, 10000}    // Repeat every 10ms
    };

    if (setitimer(ITIMER_REAL, &timer, NULL) < 0) {
        perror("setitimer");
        exit(EXIT_FAILURE);
    }
}

int main() {
    printf("EINTR test - Ctrl+C to stop\n");
    setup_timer();

    for (int i = 1; i <= 50; ++i) {
        char fifo[64];
        snprintf(fifo, sizeof(fifo), "/tmp/test_fifo_%d", i);
        unlink(fifo);
        if (mkfifo(fifo, 0666) < 0) {
            perror("mkfifo");
            exit(EXIT_FAILURE);
        }

        pid_t pid = fork();
        if (pid < 0) {
            perror("fork");
            exit(EXIT_FAILURE);
        }
        if (pid == 0) {
            // Child opens FIFO for reading
            sleep_ms(50);  // 50ms delay to let parent open writer
            int fd = safe_open(fifo, O_RDONLY);
            safe_close(fd);
            exit(EXIT_SUCCESS);
        }
        else {
            // Parent opens FIFO for writing
            int fd = safe_open(fifo, O_WRONLY);
            safe_close(fd);
            wait(NULL);
            unlink(fifo);
            printf("Loop %d OK (signals: %d)\n", i, signal_count);
            signal_count = 0;
        }

        sleep_ms(1);  // Small pause before next iteration
    }

    printf("Test complete.\n");
    return EXIT_SUCCESS;
}
```

Compile and run:
```
gcc -o reproducer reproducer.c
./reproducer
```

The program should complete successfully with output like:
```
EINTR test - Ctrl+C to stop
.....Loop 1 OK (signals: 5)
.....Loop 2 OK (signals: 5)
.....Loop 3 OK (signals: 5)
.....Loop 4 OK (signals: 5)
.....Loop 5 OK (signals: 5)
.....Loop 6 OK (signals: 5)
.....Loop 7 OK (signals: 6)
.....Loop 8 OK (signals: 5)
.....Loop 9 OK (signals: 5)
.....Loop 10 OK (signals: 5)
.....Loop 11 OK (signals: 5)
.....Loop 12 OK (signals: 5)
.....Loop 13 OK (signals: 5)
.....Loop 14 OK (signals: 6)
.....Loop 15 OK (signals: 5)
.....Loop 16 OK (signals: 5)
.....Loop 17 OK (signals: 5)
.....Loop 18 OK (signals: 5)
.....Loop 19 OK (signals: 5)
.....Loop 20 OK (signals: 5)
.....Loop 21 OK (signals: 6)
.....Loop 22 OK (signals: 5)
.....Loop 23 OK (signals: 5)
.....Loop 24 OK (signals: 5)
.....Loop 25 OK (signals: 5)
.....Loop 26 OK (signals: 5)
.....Loop 27 OK (signals: 5)
.....Loop 28 OK (signals: 6)
.....Loop 29 OK (signals: 5)
.....Loop 30 OK (signals: 5)
.....Loop 31 OK (signals: 5)
.....Loop 32 OK (signals: 5)
.....Loop 33 OK (signals: 5)
.....Loop 34 OK (signals: 5)
.....Loop 35 OK (signals: 6)
.....Loop 36 OK (signals: 5)
.....Loop 37 OK (signals: 5)
.....Loop 38 OK (signals: 5)
.....Loop 39 OK (signals: 5)
.....Loop 40 OK (signals: 5)
.....Loop 41 OK (signals: 5)
.....Loop 42 OK (signals: 6)
.....Loop 43 OK (signals: 5)
.....Loop 44 OK (signals: 5)
.....Loop 45 OK (signals: 5)
.....Loop 46 OK (signals: 5)
.....Loop 47 OK (signals: 5)
.....Loop 48 OK (signals: 5)
.....Loop 49 OK (signals: 6)
.....Loop 50 OK (signals: 5)
Test complete.
```

The program hangs indefinitely:
```
EINTR test - Ctrl+C to stop
...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................^C
```

```
ktrace -f trace.out ./reproducer
# After hanging, press Ctrl+C and examine trace
kdump -f trace.out | tail -50
```

Output:
```
 17368  17368 reproducer CALL  setcontext(0x7f7fff444000)
 17368  17368 reproducer RET   setcontext JUSTRETURN
 17368  17368 reproducer CALL  write(1,0x401173,1)
 17368  17368 reproducer GIO   fd 1 wrote 1 bytes
       "."
 17368  17368 reproducer RET   write 1
 17368  17368 reproducer CALL  open(0x7f7fff4443d0,1,1)
 17368  17368 reproducer NAMI  "/tmp/test_fifo_1"
 17368  17368 reproducer RET   open -1 errno 4 Interrupted system call
 17368  17368 reproducer PSIG  SIGALRM caught handler=0x400dba mask=(): code=SI_TIMER sent by pid=0, uid=0 with sigval 0x0)
 17368  17368 reproducer CALL  setcontext(0x7f7fff444000)
 17368  17368 reproducer RET   setcontext JUSTRETURN
 17368  17368 reproducer CALL  write(1,0x401173,1)
 17368  17368 reproducer GIO   fd 1 wrote 1 bytes
       "."
 17368  17368 reproducer RET   write 1
 17368  17368 reproducer CALL  open(0x7f7fff4443d0,1,1)
 17368  17368 reproducer NAMI  "/tmp/test_fifo_1"
 17368  17368 reproducer RET   open -1 errno 4 Interrupted system call
 17368  17368 reproducer PSIG  SIGALRM caught handler=0x400dba mask=(): code=SI_TIMER sent by pid=0, uid=0 with sigval 0x0)
 17368  17368 reproducer CALL  setcontext(0x7f7fff444000)
 17368  17368 reproducer RET   setcontext JUSTRETURN
 17368  17368 reproducer CALL  write(1,0x401173,1)
 17368  17368 reproducer GIO   fd 1 wrote 1 bytes
       "."
 17368  17368 reproducer RET   write 1
 17368  17368 reproducer CALL  open(0x7f7fff4443d0,1,1)
 17368  17368 reproducer NAMI  "/tmp/test_fifo_1"
 17368  17368 reproducer RET   open -1 errno 4 Interrupted system call
 17368  17368 reproducer PSIG  SIGALRM caught handler=0x400dba mask=(): code=SI_TIMER sent by pid=0, uid=0 with sigval 0x0)
 17368  17368 reproducer CALL  setcontext(0x7f7fff444000)
 17368  17368 reproducer RET   setcontext JUSTRETURN
 17368  17368 reproducer CALL  write(1,0x401173,1)
 17368  17368 reproducer GIO   fd 1 wrote 1 bytes
       "."
 17368  17368 reproducer RET   write 1
 17368  17368 reproducer CALL  open(0x7f7fff4443d0,1,1)
 17368  17368 reproducer NAMI  "/tmp/test_fifo_1"
 17368  17368 reproducer RET   open -1 errno 4 Interrupted system call
 17368  17368 reproducer PSIG  SIGALRM caught handler=0x400dba mask=(): code=SI_TIMER sent by pid=0, uid=0 with sigval 0x0)
 17368  17368 reproducer CALL  setcontext(0x7f7fff444000)
 17368  17368 reproducer RET   setcontext JUSTRETURN
 17368  17368 reproducer CALL  write(1,0x401173,1)
 17368  17368 reproducer GIO   fd 1 wrote 1 bytes
       "."
 17368  17368 reproducer RET   write 1
 17368  17368 reproducer CALL  open(0x7f7fff4443d0,1,1)
 17368  17368 reproducer NAMI  "/tmp/test_fifo_1"
 17368  17368 reproducer RET   open RESTART
 17368  17368 reproducer PSIG  SIGINT SIG_DFL: code=SI_NOINFO
```
>Fix:

>Release-Note:

>Audit-Trail:
From: RVP <rvp@SDF.ORG>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: kern/59578: open() hangs indefinitely on FIFO with frequent
 signal interruption
Date: Thu, 7 Aug 2025 07:07:55 +0000 (UTC)

 On Wed, 6 Aug 2025, furkanonder@protonmail.com wrote:

 > The underlying problem is that when attempting to open a FIFO for writing (O_WRONLY) while the process receives frequent signals,
 > the open() system call gets repeatedly interrupted with EINTR and never completes successfully, even when a reader process is
 > available on the other end.
 >

 It almost always isn't as per this reproducer because the child sleeps before
 opening the read end (rather than the writer--parent). For blocking FIFOs,
 there has to be a reader first, before an open(O_WRONLY) returns.

 Additionally, the child isn't even doing a read (and the parent doing a write)
 to synchronize things. The child just closes the FIFO immediately. This just
 makes the reproducer _very_ racy.

 Put the sleep in the parent (in lieu of a read/write sync) and the reproducer
 works as expected.

 >            sleep_ms(50);  // 50ms delay to let parent open writer

 The other issues are with the nanosleep() and wait(). With the timer firing
 every 10ms, these _will_ get interrupted and must be retried if errno == EINTR.

 -RVP

From: mlelstv@serpens.de (Michael van Elst)
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: kern/59578: open() hangs indefinitely on FIFO with frequent signal interruption
Date: Thu, 7 Aug 2025 07:32:11 -0000 (UTC)

 furkanonder@protonmail.com writes:

 >The underlying problem is that when attempting to open a FIFO for writing (O_WRONLY) while the process receives frequent signals,
 >the open() system call gets repeatedly interrupted with EINTR and never completes successfully, even when a reader process is
 >available on the other end.


 Actually the reproducer is bogus as the nanosleep and more important
 the wait system calls are also interrupted by the signal (and not
 retried).

 But if you handle all this, there still seems to be a bug.

 What happens is that:

 - the parent opens the fifo and gets blocked
 - the child opens the fifo and succeeds, waking the parent
 - the parent gets interrupted before resuming and finishing the
   open system call, so open fails with EINTR.
 - the child closes the fifo and exits.
 - your code retries the open operation of the parent and waits
   forever for the child that is already gone.


 I'm not sure if that is an allowed scenario, but it would probably be
 better if the interrupted open call would still succeed when the
 reason for blocking is gone at the same time.

 An alternative would be to only allow the peer to proceed when
 the open system call returns success. But that synchronisation
 is more complex.

From: furkanonder <furkanonder@protonmail.com>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: kern/59578: open() hangs indefinitely on FIFO with frequent signal interruption
Date: Thu, 07 Aug 2025 20:41:49 +0000

 Sent with Proton Mail secure email.

 On Thursday, August 7th, 2025 at 10:35 AM, Michael van Elst via gnats <gnat=
 s-admin@NetBSD.org> wrote:

 > The following reply was made to PR kern/59578; it has been noted by GNATS=
 .
 >=20
 > From: mlelstv@serpens.de (Michael van Elst)
 > To: gnats-bugs@netbsd.org
 > Cc:
 > Subject: Re: kern/59578: open() hangs indefinitely on FIFO with frequent =
 signal interruption
 > Date: Thu, 7 Aug 2025 07:32:11 -0000 (UTC)
 >=20
 > furkanonder@protonmail.com writes:
 >=20
 > > The underlying problem is that when attempting to open a FIFO for writi=
 ng (O_WRONLY) while the process receives frequent signals,
 >=20
 > > the open() system call gets repeatedly interrupted with EINTR and never=
  completes successfully, even when a reader process is
 >=20
 > > available on the other end.
 >=20
 >=20
 >=20
 > Actually the reproducer is bogus as the nanosleep and more important
 > the wait system calls are also interrupted by the signal (and not
 > retried).
 >=20
 > But if you handle all this, there still seems to be a bug.
 >=20
 > What happens is that:
 >=20
 > - the parent opens the fifo and gets blocked
 > - the child opens the fifo and succeeds, waking the parent
 > - the parent gets interrupted before resuming and finishing the
 > open system call, so open fails with EINTR.
 > - the child closes the fifo and exits.
 > - your code retries the open operation of the parent and waits
 > forever for the child that is already gone.
 >=20
 >=20
 > I'm not sure if that is an allowed scenario, but it would probably be
 > better if the interrupted open call would still succeed when the
 > reason for blocking is gone at the same time.
 >=20
 > An alternative would be to only allow the peer to proceed when
 > the open system call returns success. But that synchronisation
 > is more complex.

 Thank you Michael for the detailed analysis. You've correctly identified th=
 e core kernel issue.
 While I acknowledge the race conditions in my reproducer (nanosleep/wait EI=
 NTR handling), I respectfully disagree that it's fundamentally "bogus." The=
  reproducer demonstrates a real kernel bug affecting production software:

 It exposes the exact issue found in CPython's test suite - derived from Pyt=
 hon's test_eintr module, which runs successfully on other Unix systems.

 Similar reproducers exist for other systems - FreeBSD had an analogous issu=
 e (https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=3D203162) with "a C pr=
 ogram based on the Python unit test."

 The race conditions don't invalidate the core issue - Even with proper EINT=
 R handling, the fundamental bug you identified still exists: when a parent'=
 s open() gets interrupted after the child opens/closes the FIFO, the retry =
 hangs indefinitely.

 Your analysis pinpoints the exact problem: "the parent gets interrupted bef=
 ore resuming and finishing the open system call, so open fails with EINTR. =
 The child closes the fifo and exits. Your code retries the open operation o=
 f the parent and waits forever for the child that is already gone."

 This breaks Python's test suite on NetBSD while working correctly on Linux,=
  FreeBSD, and other Unix systems.

 I agree with your first proposed solution: allow interrupted open() to succ=
 eed when the blocking condition is resolved. This aligns better with user e=
 xpectations and POSIX semantics.

State-Changed-From-To: open->needs-pullups
State-Changed-By: riastradh@NetBSD.org
State-Changed-When: Tue, 22 Sep 2026 13:35:07 +0000
State-Changed-Why:
fixed in HEAD, needs pullup-11, pullup-10 after the testbed runs


From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc: 
Subject: PR/59578 CVS commit: src
Date: Tue, 22 Sep 2026 13:34:00 +0000

 Module Name:	src
 Committed By:	riastradh
 Date:		Tue Sep 22 13:34:00 UTC 2026

 Modified Files:
 	src/sys/miscfs/fifofs: fifo_vnops.c
 	src/tests/lib/libc/sys: t_mkfifo.c

 Log Message:
 fifo: Fix race between open and signal.

 The problem is:

 thread A                        thread B
 --------                        --------
 enter open("fifo", O_WRONLY)
 -> fi_writers++
 -> sleep since fi_readers == 0
                                 enter open("fifo", O_RDONLY)
                                 -> fi_readers++
                                 -> succeed since fi_writers > 0
                                 open succeeds with fd
                                 enter close(fd)
                                 -> fi_readers--
                                 close succeeds
 *SIGALRM*
 -> fi_writers--
 open fails with EINTR

 At this point, a reader open() has succeeded with no corresponding
 writer open() success, and if the writer retries, it will hang
 because fi_readers is back to zero.  Instead, when the reader open()
 succeeds, it should cause the writer open() to succeed as well (and
 handle the signal afterward).

 Solution: create a generation number that is advanced whenever a
 reader successfully opens; any writers will succeed on wakeup if the
 read generation has advanced since they went to sleep.

 thread A                        thread B
 --------                        --------
 enter open("fifo", O_WRONLY)
 -> fi_writers++
 -> save rgen = fi_rgen = 0
 -> sleep since fi_readers == 0
                                 enter open("fifo", O_RDONLY)
                                 -> fi_readers++
                                 -> succeed since fi_writers > 0
                                 -> fi_rgen++
                                 open succeeds with fd
                                 enter close(fd)
                                 -> fi_readers--
                                 close succeeds
 *SIGALRM*
 -> succeed since 0 = rgen != fi_rgen = 1
 open succeeds with fd

 Same problem occurs in with the reader/writer roles reversed; same
 solution in the other direction, with a writer generation number.

 PR kern/59578: open() hangs indefinitely on FIFO with frequent signal
 interruption

 Related bugs:

 https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=203162
 https://bugs.python.org/issue25122
 https://github.com/python/cpython/issues/137397

 Implementation based on FreeBSD rev. 288044 by Konstantin Belousov:
 https://svnweb.freebsd.org/base?view=revision&revision=288044
 I chose to use uint64_t generation numbers so that we don't have to
 think about overflow from counting.


 To generate a diff of this commit:
 cvs rdiff -u -r1.91 -r1.92 src/sys/miscfs/fifofs/fifo_vnops.c
 cvs rdiff -u -r1.5 -r1.6 src/tests/lib/libc/sys/t_mkfifo.c

 Please note that diffs are not public domain; they are subject to the
 copyright notices on the relevant files.

From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc: 
Subject: PR/59578 CVS commit: src/tests/lib/libc/sys
Date: Tue, 22 Sep 2026 13:32:24 +0000

 Module Name:	src
 Committed By:	riastradh
 Date:		Tue Sep 22 13:32:24 UTC 2026

 Modified Files:
 	src/tests/lib/libc/sys: t_mkfifo.c

 Log Message:
 t_mkfifo: Add test for PR kern/59578.

 The problem is:

 thread A                        thread B
 --------                        --------
 enter open("fifo", O_WRONLY)
 -> fi_writers++
 -> sleep since fi_readers == 0
                                 enter open("fifo", O_RDONLY)
                                 -> fi_readers++
                                 -> return since fi_writers > 0
                                 open succeeds with fd
                                 enter close(fd)
                                 -> fi_readers--
                                 close succeeds
 *SIGALRM*
 -> fi_writers--
 open fails with EINTR

 At this point, a reader open() has succeeded with no corresponding
 writer open() success, and if the writer retries, it will hang
 because fi_readers is back to zero.  Instead, when the reader open()
 succeeds, it should cause the writer open() to succeed as well (and
 handle the signal afterward).

 Same problem occurs in with the reader/writer roles reversed.

 PR kern/59578: open() hangs indefinitely on FIFO with frequent signal
 interruption

 Related bugs:

 https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=203162
 https://bugs.python.org/issue25122
 https://github.com/python/cpython/issues/137397


 To generate a diff of this commit:
 cvs rdiff -u -r1.4 -r1.5 src/tests/lib/libc/sys/t_mkfifo.c

 Please note that diffs are not public domain; they are subject to the
 copyright notices on the relevant files.

From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc: 
Subject: PR/59578 CVS commit: src/tests/lib/libc/sys
Date: Wed, 23 Sep 2026 19:19:40 +0000

 Module Name:	src
 Committed By:	riastradh
 Date:		Wed Sep 23 19:19:40 UTC 2026

 Modified Files:
 	src/tests/lib/libc/sys: t_mkfifo.c

 Log Message:
 t_mkfifo: Bump timeouts a little bit to exercise the race better.

 Currently on the releng testbed, the parent's open() call is not
 failing with _any_ EINTR, presumably because (a) the child is opening
 the fifo before any itimer alarms fire and then (b) signal no longer
 causes the parent's open() to fail once the child has opened the other
 end of the fifo.  So let's give the parent a bit more of a head start,
 and run the race a little longer.

 PR kern/59578: open() hangs indefinitely on FIFO with frequent signal
 interruption


 To generate a diff of this commit:
 cvs rdiff -u -r1.6 -r1.7 src/tests/lib/libc/sys/t_mkfifo.c

 Please note that diffs are not public domain; they are subject to the
 copyright notices on the relevant files.

>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.