NetBSD Problem Report #60786

From www@netbsd.org  Thu Sep 24 22:00:56 2026
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) 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 766461A923B
	for <gnats-bugs@gnats.NetBSD.org>; Thu, 24 Sep 2026 22:00:56 +0000 (UTC)
Message-Id: <20260924220055.4EC7B1A923C@mollari.NetBSD.org>
Date: Thu, 24 Sep 2026 22:00:55 +0000 (UTC)
From: campbell+netbsd@mumble.net
Reply-To: campbell+netbsd@mumble.net
To: gnats-bugs@NetBSD.org
Subject: poll reports socket writable after shutdown(SHUT_WR)
X-Send-Pr-Version: www-1.0
X-From4GNATS: "campbell+netbsd@mumble.net via gnats" <gnats-admin@NetBSD.org>

>Number:         60786
>Category:       kern
>Synopsis:       poll reports socket writable after shutdown(SHUT_WR)
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Thu Sep 24 22:05:00 +0000 2026
>Last-Modified:  Fri Sep 25 10:25:01 +0000 2026
>Originator:     Taylor R Campbell
>Release:        current, 11, 10, ...
>Organization:
The PollBSD Shutdown, Inc.
>Environment:
>Description:

	I don't know if there's language in POSIX about this but surely
	it is nonsensical for poll to claim that a socket is writable
	after it has been shut down in the writing direction!

	(This came up while trying to write a test that ensures SIGIO
	is reported in a particular direction, by shutting down writes
	so it won't report POLLOUT any more.)

>How-To-Repeat:
#include <sys/socket.h>

#include <err.h>
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int
main(void)
{
	int s[2];
	struct pollfd pfd;
	int nfds;

	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, s) == -1)
		err(EXIT_FAILURE, "socketpair");
	if (shutdown(s[0], SHUT_WR) == -1)
		err(EXIT_FAILURE, "shutdown");
	pfd.fd = s[0];
	pfd.events = POLLIN|POLLOUT;
	nfds = poll(&pfd, 1, 0);
	if (nfds == -1)
		err(EXIT_FAILURE, "poll");
	if (nfds == 1 && pfd.fd & (POLLIN|POLLOUT))
		errx(EXIT_FAILURE, "bad");
	return 0;
}

>Fix:

	Yes, please!

>Audit-Trail:
From: Robert Elz <kre@munnari.OZ.AU>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: kern/60786: poll reports socket writable after shutdown(SHUT_WR)
Date: Fri, 25 Sep 2026 05:49:25 +0700

     Date:        Thu, 24 Sep 2026 22:05:00 +0000 (UTC)
     From:        "campbell+netbsd@mumble.net via gnats" <gnats-admin@NetBSD.org>
     Message-ID:  <20260924220500.D099A1A923E@mollari.NetBSD.org>

   | 	I don't know if there's language in POSIX about this but surely
   | 	it is nonsensical for poll to claim that a socket is writable
   | 	after it has been shut down in the writing direction!

 poll() (and select()) have never claimed anything was writable or
 readable - they merely indicate whether or not a write (or read) will
 hang if attempted at the time of the poll/select (and assuming some
 other process/thread isn't manipulating the same object).

 If you want to know if something is writable, write() to it.   If you
 don't want the possibility of a hang while doing that, do a poll first,
 if that indicates no hang will occur, do a write() (and if you really
 want to be certain, turn on O_NONBLOCK as well).   If the fd is closed,
 shut down, revoked, or similar, the write will return an error.

 kre

From: Taylor R Campbell <riastradh@NetBSD.org>
To: Robert Elz <kre@munnari.OZ.AU>
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org
Subject: Re: kern/60786: poll reports socket writable after shutdown(SHUT_WR)
Date: Fri, 25 Sep 2026 01:06:50 +0000

 This is a multi-part message in MIME format.
 --=_nsoQjY3CD7bBvFEdm/FiyAa+axP8majm
 Content-Transfer-Encoding: quoted-printable

 > Date: Fri, 25 Sep 2026 05:49:25 +0700
 > From: Robert Elz <kre@munnari.OZ.AU>
 >=20
 >     Date:        Thu, 24 Sep 2026 22:05:00 +0000 (UTC)
 >     From:        "campbell+netbsd@mumble.net via gnats" <gnats-admin@NetB=
 SD.org>
 >     Message-ID:  <20260924220500.D099A1A923E@mollari.NetBSD.org>
 >=20
 >   | 	I don't know if there's language in POSIX about this but surely
 >   | 	it is nonsensical for poll to claim that a socket is writable
 >   | 	after it has been shut down in the writing direction!
 >=20
 > poll() (and select()) have never claimed anything was writable or
 > readable - they merely indicate whether or not a write (or read) will
 > hang if attempted at the time of the poll/select (and assuming some
 > other process/thread isn't manipulating the same object).

 OK, in that case shutdown(SHUT_WR) should cause concurrent poll for
 POLLOUT to wake, but it doesn't!

 --=_nsoQjY3CD7bBvFEdm/FiyAa+axP8majm
 Content-Type: text/plain; charset="ISO-8859-1"; name="shutpoll"
 Content-Transfer-Encoding: quoted-printable
 Content-Disposition: attachment; filename="shutpoll.c"

 #include <sys/socket.h>

 #include <netinet/in.h>

 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>

 struct context {
 	pthread_barrier_t bar;
 	int peer;
 	int fd;
 };

 static void *
 start_thread(void *cookie)
 {
 	struct context *C =3D cookie;

 	(void)pthread_barrier_wait(&C->bar);
 	if (usleep(1) =3D=3D -1)
 		err(EXIT_FAILURE, "usleep");
 	if (shutdown(C->peer, SHUT_WR) =3D=3D -1)
 		err(EXIT_FAILURE, "shutdown");
 	(void)pthread_barrier_wait(&C->bar);
 	if (usleep(1) =3D=3D -1)
 		err(EXIT_FAILURE, "usleep");
 	if (close(C->fd) =3D=3D -1)
 		err(EXIT_FAILURE, "close");

 	return NULL;
 }

 int
 main(void)
 {
 	struct context ctx, *C =3D &ctx;
 	int listener;
 	union {
 		struct sockaddr sa;
 		struct sockaddr_in sin;
 	} addr;
 	socklen_t addrlen;
 	int flags;
 	char buf[BUFSIZ] =3D {0};
 	pthread_t t;
 	struct pollfd pfd;
 	int nfds;

 	memset(C, 0, sizeof(*C));
 	errno =3D pthread_barrier_init(&C->bar, NULL, 2);
 	if (errno)
 		err(EXIT_FAILURE, "pthread_barrier_init");

 	/*
 	 * Listen on a random port number on 127.0.0.1.
 	 */
 	listener =3D socket(AF_INET, SOCK_STREAM, 0);
 	if (listener =3D=3D -1)
 		err(EXIT_FAILURE, "socket");

 	memset(&addr, 0, sizeof(addr));
 	addr.sin.sin_family =3D AF_INET;
 	addr.sin.sin_port =3D htons(0);
 	addr.sin.sin_addr.s_addr =3D htonl(INADDR_LOOPBACK);
 	if (bind(listener, &addr.sa, sizeof(addr.sin)) =3D=3D -1)
 		err(EXIT_FAILURE, "bind");
 	if (listen(listener, 1) =3D=3D -1)
 		err(EXIT_FAILURE, "listen");

 	/*
 	 * Get the socket address that was assigned so we can connect
 	 * to it (we need to find the port number).
 	 */
 	addrlen =3D sizeof(addr);
 	if (getsockname(listener, &addr.sa, &addrlen) =3D=3D -1)
 		err(EXIT_FAILURE, "getsockname");

 	/*
 	 * Connect to the listener.
 	 */
 	C->fd =3D socket(AF_INET, SOCK_STREAM, 0);
 	if (C->fd =3D=3D -1)
 		err(EXIT_FAILURE, "socket");
 	if (connect(C->fd, &addr.sa, addrlen) =3D=3D -1)
 		err(EXIT_FAILURE, "connect");

 	/*
 	 * Accept the connetion.
 	 */
 	C->peer =3D accept(listener, NULL, NULL);
 	if (C->peer =3D=3D -1)
 		err(EXIT_FAILURE, "accept");

 	/*
 	 * Make the peer nonblocking so we can fill its buffer to the
 	 * brim.
 	 */
 	flags =3D fcntl(C->peer, F_GETFL);
 	if (flags =3D=3D -1)
 		err(EXIT_FAILURE, "fcntl(F_GETFL)");
 	if (fcntl(C->peer, F_SETFL, flags|O_NONBLOCK) =3D=3D -1)
 		err(EXIT_FAILURE, "fcntl(F_GETFL)");
 	for (;;) {
 		if (write(C->peer, buf, sizeof(buf)) =3D=3D -1) {
 			if (errno =3D=3D EAGAIN)
 				break;
 			err(EXIT_FAILURE, "write");
 		}
 	}

 	/*
 	 * Verify that peer is NOT writable right now.
 	 */
 	pfd.fd =3D C->peer;
 	pfd.events =3D POLLOUT|POLLWRNORM;
 	nfds =3D poll(&pfd, 1, 0);
 	if (nfds =3D=3D -1)
 		err(EXIT_FAILURE, "nfds");
 	if (pfd.revents & (POLLOUT|POLLWRNORM))
 		errx(EXIT_FAILURE, "writable but the buffer is full! [1]");

 	/*
 	 * Wait for the thread to shutdown writes.  After this point,
 	 * we cannot write any further, so there should be no reason
 	 * for poll to report a transition to writable.
 	 */
 	alarm(1);
 	(void)pthread_barrier_wait(&C->bar);
 	pfd.fd =3D C->peer;
 	pfd.events =3D POLLOUT|POLLWRNORM;
 	nfds =3D poll(&pfd, 1, 0);
 	if (nfds =3D=3D -1)
 		err(EXIT_FAILURE, "nfds");
 	if (pfd.revents & (POLLOUT|POLLWRNORM))
 		errx(EXIT_FAILURE, "writable but the buffer is full! [2]");

 	/*
 	 * Start a thread to close the socket.
 	 */
 	errno =3D pthread_create(&t, NULL, &start_thread, C);
 	if (errno)
 		err(EXIT_FAILURE, "pthread_create");

 	/*
 	 * Wait to start and see what poll says is ready.
 	 */
 	(void)pthread_barrier_wait(&C->bar);

 	/*
 	 * See what I/O is available.
 	 */
 	pfd.fd =3D C->peer;
 	pfd.events =3D POLLIN|POLLOUT|POLLRDNORM|POLLWRNORM;
 	nfds =3D poll(&pfd, 1, -1);
 	if (nfds =3D=3D -1)
 		err(EXIT_FAILURE, "poll");
 	if (nfds !=3D 1)
 		errx(EXIT_FAILURE, "poll returned nfds%d", nfds);
 	fprintf(stderr, "pfd.revents=3D0x%x\n", pfd.revents);
 	if (pfd.events & (POLLOUT|POLLWRNORM))
 		errx(EXIT_FAILURE, "writable but shutdown!");

 	errno =3D pthread_join(t, NULL);
 	if (errno)
 		err(EXIT_FAILURE, "pthread_join");
 	return 0;
 }

 --=_nsoQjY3CD7bBvFEdm/FiyAa+axP8majm--

From: Taylor R Campbell <riastradh@NetBSD.org>
To: Robert Elz <kre@munnari.OZ.AU>
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org
Subject: Re: kern/60786: poll reports socket writable after shutdown(SHUT_WR)
Date: Fri, 25 Sep 2026 01:24:10 +0000

 > Date: Fri, 25 Sep 2026 01:06:50 +0000
 > From: Taylor R Campbell <riastradh@NetBSD.org>
 > 
 > OK, in that case shutdown(SHUT_WR) should cause concurrent poll for
 > POLLOUT to wake, but it doesn't!

 Oops, disregard -- that was a bad test I drafted too quickly.

 Will revisit this to see whether there is still an issue here, once
 I'm done with the slew of other SIGIO/poll/fifo/socket/pipe issues a
 little more confidently.

From: Robert Elz <kre@munnari.OZ.AU>
To: Taylor R Campbell <riastradh@NetBSD.org>
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org
Subject: Re: kern/60786: poll reports socket writable after shutdown(SHUT_WR)
Date: Fri, 25 Sep 2026 17:23:55 +0700

     Date:        Fri, 25 Sep 2026 01:24:10 +0000
     From:        Taylor R Campbell <riastradh@NetBSD.org>
     Message-ID:  <20260925012412.01FC684D78@mail.netbsd.org>

   | Oops, disregard -- that was a bad test I drafted too quickly.

 No problem, and I can certainly believe there are cases where
 select/poll aren't woken when they should be, lots has been added
 to the kernel since they were first created, and it would be easy
 for bugs to have slipped in.

 The only reason I replied initially, is because it is too easy to
 fall into the rabbit hole of believing that if select/poll say
 that an fd can be read/written, it should mean successfully.
 It doesn't, and never did - they were just a mechanism (long before
 BSD had O_NONBLOCK support in any form) to avoid hanging, and allow
 a semblance of fake threading (before that existed either).

 kre

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.