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
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.
https://reviews.freebsd.org/D58666
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(-)
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(-)