diff --git a/src/utils/windowsprocessdevice.cpp b/src/utils/windowsprocessdevice.cpp index 98a05e01d..307d6597e 100644 --- a/src/utils/windowsprocessdevice.cpp +++ b/src/utils/windowsprocessdevice.cpp @@ -1,409 +1,459 @@ /* utils/windowsprocessdevice.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2019 g 10code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #ifdef WIN32 #include "windowsprocessdevice.h" #include "kleopatra_debug.h" #include #include #include #include /* This is the amount of data GPGME reads at once */ #define PIPEBUF_SIZE 16384 using namespace Kleo; static void CloseHandleX(HANDLE &h) { if (h && h != INVALID_HANDLE_VALUE) { if (!CloseHandle(h)) { qCWarning(KLEOPATRA_LOG) << "CloseHandle failed!"; } h = nullptr; } } class WindowsProcessDevice::Private { public: ~Private(); Private(const QString &path, const QStringList &args, const QString &wd): mPath(path), mArgs(args), mWorkingDirectory(wd), mStdInRd(nullptr), mStdInWr(nullptr), mStdOutRd(nullptr), mStdOutWr(nullptr), mStdErrRd(nullptr), mStdErrWr(nullptr), mProc(nullptr), mThread(nullptr), mEnded(false) { } bool start (QIODevice::OpenMode mode); qint64 write(const char *data, qint64 size) { if (size < 0 || (size >> 32)) { qCDebug (KLEOPATRA_LOG) << "Invalid write"; return -1; } if (!mStdInWr) { qCDebug (KLEOPATRA_LOG) << "Write to closed or read only device"; return -1; } DWORD dwWritten; if (!WriteFile(mStdInWr, data, (DWORD) size, &dwWritten, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to write"; return -1; } if (dwWritten != size) { qCDebug(KLEOPATRA_LOG) << "Failed to write everything"; return -1; } return size; } qint64 read(char *data, qint64 maxSize) { if (!mStdOutRd) { qCDebug (KLEOPATRA_LOG) << "Read of closed or write only device"; return -1; } if (!maxSize) { return 0; } DWORD exitCode = 0; if (GetExitCodeProcess (mProc, &exitCode)) { if (exitCode != STILL_ACTIVE) { if (exitCode) { qCDebug(KLEOPATRA_LOG) << "Non zero exit code"; mError = readAllStdErr(); return -1; } mEnded = true; qCDebug(KLEOPATRA_LOG) << "Process finished with code " << exitCode; } } else { qCDebug(KLEOPATRA_LOG) << "GetExitCodeProcess Failed"; } if (mEnded) { DWORD avail = 0; if (!PeekNamedPipe(mStdOutRd, nullptr, 0, nullptr, &avail, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; return -1; } if (!avail) { qCDebug(KLEOPATRA_LOG) << "Process ended and nothing more in pipe"; return 0; } } DWORD dwRead; if (!ReadFile(mStdOutRd, data, (DWORD) maxSize, &dwRead, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to read"; return -1; } return dwRead; } QString readAllStdErr() { QString ret; if (!mStdErrRd) { qCDebug (KLEOPATRA_LOG) << "Read of closed stderr"; } DWORD dwRead = 0; do { char buf[4096]; DWORD avail; if (!PeekNamedPipe(mStdErrRd, nullptr, 0, nullptr, &avail, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; return ret; } if (!avail) { return ret; } ReadFile(mStdErrRd, buf, 4096, &dwRead, nullptr); if (dwRead) { QByteArray ba (buf, dwRead); ret += QString::fromLocal8Bit (ba); } } while (dwRead); return ret; } void close() { if (mProc && mProc != INVALID_HANDLE_VALUE) { TerminateProcess(mProc, 0xf291); CloseHandleX(mProc); } } QString errorString() { return mError; } void closeWriteChannel() { CloseHandleX(mStdInWr); } private: QString mPath; QStringList mArgs; QString mWorkingDirectory; QString mError; HANDLE mStdInRd; HANDLE mStdInWr; HANDLE mStdOutRd; HANDLE mStdOutWr; HANDLE mStdErrRd; HANDLE mStdErrWr; HANDLE mProc; HANDLE mThread; bool mEnded; }; WindowsProcessDevice::WindowsProcessDevice(const QString &path, const QStringList &args, const QString &wd): d(new Private(path, args, wd)) { } bool WindowsProcessDevice::open(QIODevice::OpenMode mode) { bool ret = d->start(mode); if (ret) { setOpenMode(mode); } return ret; } qint64 WindowsProcessDevice::readData(char *data, qint64 maxSize) { return d->read(data, maxSize); } qint64 WindowsProcessDevice::writeData(const char *data, qint64 maxSize) { return d->write(data, maxSize); } bool WindowsProcessDevice::isSequential() const { return true; } void WindowsProcessDevice::closeWriteChannel() { d->closeWriteChannel(); } void WindowsProcessDevice::close() { d->close(); QIODevice::close(); } QString getLastErrorString() { wchar_t *lpMsgBuf = nullptr; DWORD dw = GetLastError(); FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (wchar_t *) &lpMsgBuf, 0, NULL); QString ret = QString::fromWCharArray(lpMsgBuf); LocalFree(lpMsgBuf); return ret; } WindowsProcessDevice::Private::~Private() { if (mProc && mProc != INVALID_HANDLE_VALUE) { close(); } CloseHandleX (mThread); CloseHandleX (mStdInRd); CloseHandleX (mStdInWr); CloseHandleX (mStdOutRd); CloseHandleX (mStdOutWr); CloseHandleX (mStdErrRd); CloseHandleX (mStdErrWr); } static QString qt_create_commandline(const QString &program, const QStringList &arguments, const QString &nativeArguments) { QString args; if (!program.isEmpty()) { QString programName = program; if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' '))) programName = QLatin1Char('\"') + programName + QLatin1Char('\"'); programName.replace(QLatin1Char('/'), QLatin1Char('\\')); // add the prgram as the first arg ... it works better args = programName + QLatin1Char(' '); } for (int i=0; i 0 && tmp.at(i - 1) == QLatin1Char('\\')) --i; tmp.insert(i, QLatin1Char('"')); tmp.prepend(QLatin1Char('"')); } args += QLatin1Char(' ') + tmp; } if (!nativeArguments.isEmpty()) { if (!args.isEmpty()) args += QLatin1Char(' '); args += nativeArguments; } return args; } bool WindowsProcessDevice::Private::start(QIODevice::OpenMode mode) { if (mode != QIODevice::ReadOnly && mode != QIODevice::WriteOnly && mode != QIODevice::ReadWrite) { qCDebug(KLEOPATRA_LOG) << "Unsupported open mode " << mode; return false; } SECURITY_ATTRIBUTES saAttr; ZeroMemory (&saAttr, sizeof (SECURITY_ATTRIBUTES)); saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Create the pipes if (!CreatePipe(&mStdOutRd, &mStdOutWr, &saAttr, PIPEBUF_SIZE) || !CreatePipe(&mStdErrRd, &mStdErrWr, &saAttr, 0) || !CreatePipe(&mStdInRd, &mStdInWr, &saAttr, PIPEBUF_SIZE)) { qCDebug(KLEOPATRA_LOG) << "Failed to create pipes"; mError = getLastErrorString(); return false; } // Ensure only the proper handles are inherited if (!SetHandleInformation(mStdOutRd, HANDLE_FLAG_INHERIT, 0) || !SetHandleInformation(mStdErrRd, HANDLE_FLAG_INHERIT, 0) || !SetHandleInformation(mStdInWr, HANDLE_FLAG_INHERIT, 0)) { qCDebug(KLEOPATRA_LOG) << "Failed to set inherit flag"; mError = getLastErrorString(); return false; } PROCESS_INFORMATION piProcInfo; ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); STARTUPINFO siStartInfo; ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = mStdErrWr; siStartInfo.hStdOutput = mStdOutWr; siStartInfo.hStdInput = mStdInRd; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; const auto args = qt_create_commandline(mPath, mArgs, QString()); wchar_t *cmdLine = wcsdup (reinterpret_cast(args.utf16())); const wchar_t *proc = reinterpret_cast(mPath.utf16()); const QString nativeWorkingDirectory = QDir::toNativeSeparators(mWorkingDirectory); const wchar_t *wd = reinterpret_cast(nativeWorkingDirectory.utf16()); - qCDebug(KLEOPATRA_LOG) << "Spawning:" << args; + /* Filter out the handles to inherit only the three handles which we want to + * inherit. As a Qt Application we have a multitude of open handles which + * may or may not be inheritable. Testing has shown that this reduced about + * thirty handles. Open File handles in our child application can also cause the + * read pipe not to be closed correcly on exit. */ + SIZE_T size; + bool suc = InitializeProcThreadAttributeList(NULL, 1, 0, &size) || + GetLastError() == ERROR_INSUFFICIENT_BUFFER; + if (!suc) { + qCDebug(KLEOPATRA_LOG) << "Failed to get Attribute List size"; + mError = getLastErrorString(); + return false; + } + LPPROC_THREAD_ATTRIBUTE_LIST attributeList = reinterpret_cast (HeapAlloc(GetProcessHeap(), 0, size)); + if (!attributeList) { + qCDebug(KLEOPATRA_LOG) << "Failed to Allocate Attribute List"; + return false; + } + suc = InitializeProcThreadAttributeList(attributeList, 1, 0, &size); + if (!suc) { + qCDebug(KLEOPATRA_LOG) << "Failed to Initalize Attribute List"; + mError = getLastErrorString(); + return false; + } + + HANDLE handles[3]; + handles[0] = mStdOutWr; + handles[1] = mStdErrWr; + handles[2] = mStdInRd; + suc = UpdateProcThreadAttribute(attributeList, + 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + handles, + 3 * sizeof(HANDLE), + NULL, + NULL); + if (!suc) { + qCDebug(KLEOPATRA_LOG) << "Failed to Update Attribute List"; + mError = getLastErrorString(); + return false; + + } + STARTUPINFOEX info; + ZeroMemory(&info, sizeof(info)); + info.StartupInfo = siStartInfo; + info.StartupInfo.cb = sizeof(info); // You have to know this,.. + info.lpAttributeList = attributeList; + // Now lets start - bool suc = CreateProcessW(proc, - cmdLine, // command line - NULL, // process security attributes - NULL, // primary thread security attributes - TRUE, // handles are inherited - CREATE_NO_WINDOW,// creation flags - NULL, // use parent's environment - wd, // use parent's current directory - &siStartInfo, // STARTUPINFO pointer - &piProcInfo); // receives PROCESS_INFORMATION + qCDebug(KLEOPATRA_LOG) << "Spawning:" << args; + suc = CreateProcessW(proc, + cmdLine, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT,// creation flags + NULL, // use parent's environment + wd, // use parent's current directory + &info.StartupInfo, // STARTUPINFO pointer + &piProcInfo); // receives PROCESS_INFORMATION + DeleteProcThreadAttributeList(attributeList); + HeapFree(GetProcessHeap(), 0, attributeList); free(cmdLine); if (!suc) { qCDebug(KLEOPATRA_LOG) << "Failed to create process"; mError = getLastErrorString(); return false; } mProc = piProcInfo.hProcess; mThread = piProcInfo.hThread; if (mode == QIODevice::WriteOnly) { CloseHandleX (mStdInRd); } if (mode == QIODevice::ReadOnly) { CloseHandleX (mStdInWr); } return true; } QString WindowsProcessDevice::errorString() { return d->errorString(); } #include "windowsprocessdevice.moc" #endif