Bug 297293 - fget_procdesc(): EBADF -> EINVAL change breaks Qt's forkfd
Summary: fget_procdesc(): EBADF -> EINVAL change breaks Qt's forkfd
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: 16.0-CURRENT
Hardware: Any Any
: --- Affects Only Me
Assignee: Konstantin Belousov
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2026-08-05 07:21 UTC by Li-Wen Hsu
Modified: 2026-08-16 16:28 UTC (History)
2 users (show)

See Also:
linimon: mfc-stable14?


Attachments
qrepro.cpp (1.22 KB, text/plain)
2026-08-05 07:21 UTC, Li-Wen Hsu
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Li-Wen Hsu freebsd_committer freebsd_triage 2026-08-05 07:21:59 UTC
Created attachment 273472 [details]
qrepro.cpp

Commit e18844223d1e ("fget_procdesc(): change error for non-procdesc type from EBADF to EINVAL", 2026-07-16) changes the error value of pdgetpid(2), pdkill(2), pddupfd(2) and pdwait(2) when the fd is open but is not a process descriptor.

Qt's bundled forkfd uses that EBADF to find out that the fd is its own pipe.  Since this commit, Qt loses the exit status of its child process and reports a normal exit as a crash.  konsole(1) now prints when a shell exits:

	Warning: Program '/usr/local/bin/zsh' crashed.

zsh exits with status 0, there is no signal and no core file.  /bin/sh and bash give the same message.

This is the same problem as in September 2025: fd9e09cb2ab0 made the same change for pdgetpid(2), and a85525a5c8b2 ("pdgetpid(2): switch back returning EBADF for non-procdesc fd") partially reverted it.  e18844223d1e brings EINVAL back through the new fget_procdesc() helper.

## The changed error value

	/*
	 * cc -o repro repro.c
	 *
	 * When Qt starts a child with fork() instead of pdfork(), forkfd waits on its
	 * own pipe.  Before forkfd reads that pipe, it calls pdgetpid() on the fd and
	 * looks at errno.  This is that call.
	 */
	#include <sys/procdesc.h>
	#include <err.h>
	#include <errno.h>
	#include <unistd.h>

	int
	main(void)
	{
		pid_t pid;
		int p[2];

		if (pipe(p) != 0)
			err(1, "pipe");
		if (pdgetpid(p[0], &pid) != -1)
			errx(1, "pdgetpid() on a pipe should not succeed");
		warn("pdgetpid(pipe fd) failed with errno %d", errno);
		return (0);
	}

On 16.0-CURRENT (kernel built from 5c533d39e75c):

	repro: pdgetpid(pipe fd) failed with errno 22: Invalid argument

Before e18844223d1e this was errno 9 (EBADF).

## What Qt does with it

qtbase/src/3rdparty/forkfd/forkfd.c:

	int forkfd_wait4(int ffd, struct forkfd_info *info, int options, struct rusage *rusage)
	{
	    ...
	    if (system_has_forkfd()) {
	        /* if this is one of our pipes, not a procdesc/pidfd, we'll get an EBADF */
	        ret = system_forkfd_wait(ffd, info, options, rusage);
	        if (disable_fork_fallback() || ret != -1 || errno != EBADF)
	            return ret;
	    }
	    ret = read(ffd, &payload, sizeof(payload));

forkfd does not remember which kind of fd it has, so it calls pdgetpid() first and uses errno to find out.  Without a child process modifier Qt uses vfork() and forkfd uses pdfork(), so ffd is a process descriptor.  With a child process modifier forkfd uses fork(), and ffd is the pipe above.  konsole always hits the second case, because KPtyProcess always sets a child process modifier.

With EINVAL, forkfd_wait() returns -1 and the pipe is never read.

qtbase/src/corelib/io/qprocess_unix.cpp then keeps its zeroed struct:

	forkfd_info info = {};
	QT_EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr));
	exitCode = info.status;
	exitStatus = info.code == CLD_EXITED ? QProcess::NormalExit : QProcess::CrashExit;

info.code is 0, which is not CLD_EXITED, so Qt reports a crash.  ktrace shows the correct payload (CLD_EXITED, status 0) is written to the pipe, and only never read.

qrepro.cpp (attached) shows it with Qt alone, no pty involved.  The child is "/bin/sh -c 'exit 0'" in both runs, the only difference is the child process modifier:

	plain QProcess:                Qt says: NormalExit exitCode=0  OK
	same as konsole does:          Qt says: CrashExit  exitCode=0  WRONG (the child exited 0)

## Notes

- lib/libsys/pdfork.2 still documents EBADF for pddupfd(), which now goes through fget_procdesc().
- On Linux, pidfd_send_signal(2) and waitid(P_PIDFD) return EBADF when the fd is not a pidfd, which is probably why forkfd checks for EBADF.
- stable/15 still returns EBADF (07debe52b30a).  e18844223d1e has "MFC after: 1 week", and after the MFC Qt programs there will have the same problem.

If the new error value is intended, I am happy to report this to Qt and to other projects that use these interfaces.  In that case, please do not MFC it for now
Comment 1 Konstantin Belousov freebsd_committer freebsd_triage 2026-08-05 07:48:37 UTC
The commit you referenced was done because some test caught the changed error value.
I believe it was pdwait::einval.

I can try to make fget_procdesc() return different errors for different callers.
Comment 2 Konstantin Belousov freebsd_committer freebsd_triage 2026-08-05 08:09:31 UTC
https://reviews.freebsd.org/D58666
Comment 3 commit-hook freebsd_committer freebsd_triage 2026-08-05 18:17:41 UTC
A commit in branch main references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=e8b9b6b9f31c463137b4104550bfb3286a43703a

commit e8b9b6b9f31c463137b4104550bfb3286a43703a
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-08-05 08:04:49 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-08-05 18:15:54 +0000

    pdkill(2), pdgetpid(2): return EBADF if the file type is not procdesc

    For pdwait(2) and pddupfd(2), the returned error is kept EINVAL.

    PR:     297293
    Reviewed by:    lwhsu, markj
    Sponsored by:   The FreeBSD Foundation
    MFC after:      1 week
    Differential revision:  https://reviews.freebsd.org/D58666

 sys/kern/kern_exit.c    |  3 ++-
 sys/kern/kern_sig.c     |  3 ++-
 sys/kern/sys_procdesc.c | 10 ++++++----
 sys/sys/procdesc.h      |  4 ++--
 4 files changed, 12 insertions(+), 8 deletions(-)
Comment 4 commit-hook freebsd_committer freebsd_triage 2026-08-16 02:48:07 UTC
A commit in branch stable/15 references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=83fa3c3ad84405df0ccbe5e65491df63b24782a0

commit 83fa3c3ad84405df0ccbe5e65491df63b24782a0
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-08-05 08:04:49 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-08-16 02:41:39 +0000

    pdkill(2), pdgetpid(2): return EBADF if the file type is not procdesc

    PR:     297293

    (cherry picked from commit e8b9b6b9f31c463137b4104550bfb3286a43703a)

 sys/kern/kern_exit.c    |  3 ++-
 sys/kern/kern_sig.c     |  3 ++-
 sys/kern/sys_procdesc.c | 10 ++++++----
 sys/sys/procdesc.h      |  4 ++--
 4 files changed, 12 insertions(+), 8 deletions(-)