[Bug 297293] fget_procdesc(): EBADF -> EINVAL change breaks Qt's forkfd
- Reply: bugzilla-noreply_a_freebsd.org: "[Bug 297293] fget_procdesc(): EBADF -> EINVAL change breaks Qt's forkfd"
- Reply: bugzilla-noreply_a_freebsd.org: "[Bug 297293] fget_procdesc(): EBADF -> EINVAL change breaks Qt's forkfd"
- Reply: bugzilla-noreply_a_freebsd.org: "[Bug 297293] fget_procdesc(): EBADF -> EINVAL change breaks Qt's forkfd"
- Reply: bugzilla-noreply_a_freebsd.org: "[Bug 297293] fget_procdesc(): EBADF -> EINVAL change breaks Qt's forkfd"
- Reply: bugzilla-noreply_a_freebsd.org: "[Bug 297293] fget_procdesc(): EBADF -> EINVAL change breaks Qt's forkfd"
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 05 Aug 2026 07:21:59 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=297293
Bug ID: 297293
Summary: fget_procdesc(): EBADF -> EINVAL change breaks Qt's
forkfd
Product: Base System
Version: 16.0-CURRENT
Hardware: Any
OS: Any
Status: New
Severity: Affects Only Me
Priority: ---
Component: kern
Assignee: bugs@FreeBSD.org
Reporter: lwhsu@FreeBSD.org
Attachment #273472 text/plain
mime type:
Created attachment 273472
--> https://bugs.freebsd.org/bugzilla/attachment.cgi?id=273472&action=edit
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
--
You are receiving this mail because:
You are the assignee for the bug.