NetBSD-Bugs archive

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

kern/60851: change in pipe kevent EVFILT_READ/WRITE on wrong end



>Number:         60851
>Category:       kern
>Synopsis:       change in pipe kevent EVFILT_READ/WRITE on wrong end
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Mon Oct 05 19:10:01 +0000 2026
>Originator:     Taylor R Campbell
>Release:        current
>Organization:
Acme Pipefitters, Inc.
>Environment:
>Description:

	Before my recent flurry of changes to the pipe(2)
	implementation, kevent EVFILT_READ on the write end of a pipe,
	and kevent EVFILT_WRITE on the read end of a pipe, would
	quietly be accepted.

	After the changes, specifically after
	https://mail-index.netbsd.org/source-changes/2026/10/03/msg164422.html,
	kevent would reject these with EINVAL.

   1208 	switch (kn->kn_filter) {
   1209 	case EVFILT_READ:
   1210 		if ((fp->f_flag & FREAD) == 0) {
   1211 			mutex_exit(lock);
   1212 			return (EINVAL);
   1213 		}
   1214 		kn->kn_fop = &pipe_rfiltops;
   1215 		break;
   1216 	case EVFILT_WRITE:
   1217 		if ((fp->f_flag & FWRITE) == 0) {
   1218 			mutex_exit(lock);
   1219 			return (EINVAL);
   1220 		}
   1221 		kn->kn_fop = &pipe_wfiltops;
   1222 		break;
   1223 	default:
   1224 		mutex_exit(lock);
   1225 		return (EINVAL);
   1226 	}

	https://nxr.netbsd.org/xref/src/sys/kern/sys_pipe.c?r=1.174#1208

	This is apparently in conflict with FreeBSD:

	/*
	 * If a filter is requested that is not supported by this file
	 * descriptor, don't return an error, but also don't ever generate an
	 * event.
	 */
	if ((kn->kn_filter == EVFILT_READ) && !(fp->f_flag & FREAD)) {
		kn->kn_fop = &pipe_nfiltops;
		return (0);
	}
	if ((kn->kn_filter == EVFILT_WRITE) && !(fp->f_flag & FWRITE)) {
		kn->kn_fop = &pipe_nfiltops;
		return (0);
	}

	https://cgit.freebsd.org/src/tree/sys/kern/sys_pipe.c?h=release/15.1.0-p4&id=e38a7085f3a8ecd317c947591fe42b5ca9ab317b#n1791

	The flurry of changes appear to have broken dovecot:

	https://mail-index.netbsd.org/current-users/2026/10/05/msg047953.html

	Review of a ktrace shows that SIGABRT happens shortly after
	kevent(2) fails with EINVAL, which is consistent with this code
	path showing that dovecot assumes EVFILT_READ on the write end
	of a pipe will work when just the (dovecot-internal) event
	condition IO_ERROR is requested:

	if ((io->io.condition & (IO_READ | IO_ERROR)) != 0) {
		MY_EV_SET(&ev, io->fd, EVFILT_READ, EV_ADD, 0, 0, io);
		if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0)
			i_panic("kevent(EV_ADD, READ, %d) failed: %m", io->fd);
	}
	if ((io->io.condition & IO_WRITE) != 0) {
		MY_EV_SET(&ev, io->fd, EVFILT_WRITE, EV_ADD, 0, 0, io);
		if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0)
			i_panic("kevent(EV_ADD, WRITE, %d) failed: %m", io->fd);
	}

	https://github.com/dovecot/core/blob/2.3.21.1/src/lib/ioloop-kqueue.c#L64-L73

		/* start listening errors for status fd, it means master died */
		service->io_status_error = io_add(MASTER_DEAD_FD, IO_ERROR,
						  master_status_error, service);

	https://github.com/dovecot/core/blob/2.3.21.1/src/lib-master/master-service.c#L688-L690

	It's not clear that this isn't a dovecot bug, but there was
	obviously an unexpected change in the NetBSD semantics which
	now disagrees with FreeBSD.

>How-To-Repeat:

	run mail/dovecot2

>Fix:

	Replace these error branches by a null filterops that never
	returns ready, like FreeBSD does -- and if we really want the
	EINVAL semantics (which I tossed in without thinking much about
	it just to detect what I assumed must be mistakes), that should
	be done in a spearate intentional commit with supporting
	documentation.




Home | Main Index | Thread Index | Old Index