[Bug 297293] fget_procdesc(): EBADF -> EINVAL change breaks Qt's forkfd
[email protected] Wed, 05 Aug 2026 07:21:59 +0000
| Newsgroups | gmane.os.freebsd.bugs |
|---|---|
| Message-ID | <[email protected]/bugzilla/> |
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=3D297293
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: [email protected]
Reporter: [email protected]
Attachment #273472 text/plain
mime type:
Created attachment 273472
--> https://bugs.freebsd.org/bugzilla/attachment.cgi?id=3D273472&action=
=3Dedit
qrepro.cpp
Commit e18844223d1e ("fget_procdesc(): change error for non-procdesc type f=
rom
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=
.=20
Since this commit, Qt loses the exit status of its child process and report=
s 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 returni=
ng
EBADF for non-procdesc fd") partially reverted it. e18844223d1e brings EIN=
VAL
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 w=
aits
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) !=3D 0)
err(1, "pipe");
if (pdgetpid(p[0], &pid) !=3D -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, st=
ruct
rusage *rusage)
{
...
if (system_has_forkfd()) {
/* if this is one of our pipes, not a procdesc/pidfd, we'll=
get
an EBADF */
ret =3D system_forkfd_wait(ffd, info, options, rusage);
if (disable_fork_fallback() || ret !=3D -1 || errno !=3D EB=
ADF)
return ret;
}
ret =3D read(ffd, &payload, sizeof(payload));
forkfd does not remember which kind of fd it has, so it calls pdgetpid() fi=
rst
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 pro=
cess
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 =3D {};
QT_EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr));
exitCode =3D info.status;
exitStatus =3D info.code =3D=3D CLD_EXITED ? QProcess::NormalExit :
QProcess::CrashExit;
info.code is 0, which is not CLD_EXITED, so Qt reports a crash. ktrace sho=
ws
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=3D0 OK
same as konsole does: Qt says: CrashExit exitCode=3D0 WR=
ONG
(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 afte=
r: 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
--=20
You are receiving this mail because:
You are the assignee for the bug.=