diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index 1db8db6711..a710750732 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -816,8 +816,16 @@ void QProcessPrivate::Channel::clear() Its members are: \list \li UnixProcessParameters::flags Flags, see QProcess::UnixProcessFlags + \li UnixProcessParameters::lowestFileDescriptorToClose The lowest file + descriptor to close. \endlist + When the QProcess::UnixProcessFlags::CloseFileDescriptors flag is set in + the \c flags field, QProcess closes the application's open file descriptors + before executing the child process. The descriptors 0, 1, and 2 (that is, + \c stdin, \c stdout, and \c stderr) are left alone, along with the ones + numbered lower than the value of the \c lowestFileDescriptorToClose field. + All of the settings above can also be manually achieved by calling the respective POSIX function from a handler set with QProcess::setChildProcessModifier(). This structure allows QProcess to deal @@ -835,10 +843,11 @@ void QProcessPrivate::Channel::clear() These flags can be used in the \c flags field of \l UnixProcessParameters. - \value CloseNonStandardFileDescriptors Close all file descriptors besides - \c stdin, \c stdout, and \c stderr, preventing any currently open - descriptor in the parent process from accidentally leaking to the - child. + \value CloseFileDescriptors Close all file descriptors above the threshold + defined by \c lowestFileDescriptorToClose, preventing any currently + open descriptor in the parent process from accidentally leaking to the + child. The \c stdin, \c stdout, and \c stderr file descriptors are + never closed. \value IgnoreSigPipe Always sets the \c SIGPIPE signal to ignored (\c SIG_IGN), even if the \c ResetSignalHandlers flag was set. By diff --git a/src/corelib/io/qprocess.h b/src/corelib/io/qprocess.h index b8923b032d..0cb041f324 100644 --- a/src/corelib/io/qprocess.h +++ b/src/corelib/io/qprocess.h @@ -179,15 +179,16 @@ public: ResetSignalHandlers = 0x0001, // like POSIX_SPAWN_SETSIGDEF IgnoreSigPipe = 0x0002, // some room if we want to add IgnoreSigHup or so - CloseNonStandardFileDescriptors = 0x0010, + CloseFileDescriptors = 0x0010, UseVFork = 0x0020, // like POSIX_SPAWN_USEVFORK }; Q_DECLARE_FLAGS(UnixProcessFlags, UnixProcessFlag) struct UnixProcessParameters { UnixProcessFlags flags = {}; + int lowestFileDescriptorToClose = 0; - quint32 _reserved[7] {}; + quint32 _reserved[6] {}; }; UnixProcessParameters unixProcessParameters() const noexcept; void setUnixProcessParameters(const UnixProcessParameters ¶ms); diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp index 7cd379f9fd..ea39962b9d 100644 --- a/src/corelib/io/qprocess_unix.cpp +++ b/src/corelib/io/qprocess_unix.cpp @@ -669,9 +669,9 @@ static void applyProcessParameters(const QProcess::UnixProcessParameters ¶ms // Close all file descriptors above stderr. // This isn't expected to fail, so we ignore close()'s return value. - if (params.flags.testFlag(QProcess::UnixProcessFlag::CloseNonStandardFileDescriptors)) { + if (params.flags.testFlag(QProcess::UnixProcessFlag::CloseFileDescriptors)) { int r = -1; - int fd = STDERR_FILENO + 1; + int fd = qMax(STDERR_FILENO + 1, params.lowestFileDescriptorToClose); #if QT_CONFIG(close_range) // On FreeBSD, this probably won't fail. // On Linux, this will fail with ENOSYS before kernel 5.9. diff --git a/tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp b/tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp index 9be1930667..96425eca2f 100644 --- a/tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp +++ b/tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp @@ -62,7 +62,7 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - if (cmd == "std-file-descriptors") { + if (cmd == "file-descriptors") { int fd = atoi(argv[2]); if (close(fd) < 0 && errno == EBADF) return EXIT_SUCCESS; @@ -70,6 +70,16 @@ int main(int argc, char **argv) return EXIT_FAILURE; } + if (cmd == "file-descriptors2") { + int fd1 = atoi(argv[2]); // should be open + int fd2 = atoi(argv[3]); // should be closed + if (close(fd1) < 0) + fprintf(stderr, "%d was not a valid file descriptor\n", fd1); + if (close(fd2) == 0 || errno != EBADF) + fprintf(stderr, "%d is a valid file descriptor\n", fd2); + return EXIT_SUCCESS; + } + fprintf(stderr, "Unknown command \"%s\"", cmd.data()); return EXIT_FAILURE; } diff --git a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp index 609a6e1113..cd55a72bf5 100644 --- a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp +++ b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp @@ -118,6 +118,7 @@ private slots: void unixProcessParameters_data(); void unixProcessParameters(); void unixProcessParametersAndChildModifier(); + void unixProcessParametersOtherFileDescriptors(); #endif void exitCodeTest(); void systemEnvironment(); @@ -1537,7 +1538,7 @@ void tst_QProcess::unixProcessParameters_data() using P = QProcess::UnixProcessFlag; addRow("reset-sighand", P::ResetSignalHandlers); addRow("ignore-sigpipe", P::IgnoreSigPipe); - addRow("std-file-descriptors", P::CloseNonStandardFileDescriptors); + addRow("file-descriptors", P::CloseFileDescriptors); } void tst_QProcess::unixProcessParameters() @@ -1618,11 +1619,11 @@ void tst_QProcess::unixProcessParametersAndChildModifier() write(pipes[1], message, strlen(message)); vforkControl.storeRelaxed(1); }); - auto flags = QProcess::UnixProcessFlag::CloseNonStandardFileDescriptors | + auto flags = QProcess::UnixProcessFlag::CloseFileDescriptors | QProcess::UnixProcessFlag::UseVFork; process.setUnixProcessParameters({ flags }); process.setProgram("testUnixProcessParameters/testUnixProcessParameters"); - process.setArguments({ "std-file-descriptors", QString::number(pipes[1]) }); + process.setArguments({ "file-descriptors", QString::number(pipes[1]) }); process.start(); QVERIFY2(process.waitForStarted(5000), qPrintable(process.errorString())); } // closes the writing end of the pipe @@ -1639,6 +1640,50 @@ void tst_QProcess::unixProcessParametersAndChildModifier() if (haveWorkingVFork) QVERIFY2(vforkControl.loadRelaxed(), "QProcess doesn't appear to have used vfork()"); } + +void tst_QProcess::unixProcessParametersOtherFileDescriptors() +{ + constexpr int TargetFileDescriptor = 3; + int pipes[2]; + int fd1 = open("/dev/null", O_RDONLY); + int devnull = fcntl(fd1, F_DUPFD, 100); // instead of F_DUPFD_CLOEXEC + pipe2(pipes, O_CLOEXEC); + close(fd1); + + auto closeFds = qScopeGuard([&] { + close(devnull); + close(pipes[0]); + // we'll close pipe[1] before any QCOMPARE + }); + + QProcess process; + QProcess::UnixProcessParameters params; + params.flags = QProcess::UnixProcessFlag::CloseFileDescriptors + | QProcess::UnixProcessFlag::UseVFork; + params.lowestFileDescriptorToClose = 4; + process.setUnixProcessParameters(params); + process.setChildProcessModifier([devnull, pipes]() { + if (dup2(devnull, TargetFileDescriptor) == TargetFileDescriptor) + return; + write(pipes[1], &errno, sizeof(errno)); + _exit(255); + }); + process.setProgram("testUnixProcessParameters/testUnixProcessParameters"); + process.setArguments({ "file-descriptors2", QString::number(TargetFileDescriptor), + QString::number(devnull) }); + process.start(); + close(pipes[1]); + + if (int duperror; read(pipes[0], &duperror, sizeof(duperror)) == sizeof(duperror)) + QFAIL(QByteArray("dup2 failed: ") + strerror(duperror)); + + QVERIFY2(process.waitForStarted(5000), qPrintable(process.errorString())); + QVERIFY(process.waitForFinished(5000)); + QCOMPARE(process.readAllStandardError(), QString()); + QCOMPARE(process.readAll(), QString()); + QCOMPARE(process.exitCode(), 0); + QCOMPARE(process.exitStatus(), QProcess::NormalExit); +} #endif void tst_QProcess::exitCodeTest()