#include "common/ipc.h" #include "common/windows_pipe.h" #include #include #include #ifdef Q_OS_WIN #ifndef NOMINMAX #define NOMINMAX #endif #include #endif using namespace docview; class WindowsPipeTest : public QObject { Q_OBJECT private slots: void invalidHandlesAreRejected() { WindowsPipeDevice device; QString error; QVERIFY(!device.adopt(0, 0, &error)); QVERIFY(!device.isOpen()); QVERIFY(!error.isEmpty()); #ifndef Q_OS_WIN QVERIFY(!createWindowsPipePair(&error)); QVERIFY(error.startsWith("E_SANDBOX_UNAVAILABLE")); #endif } void duplexFramingAndPartialTransfers() { #ifdef Q_OS_WIN QString error; auto pair = createWindowsPipePair(&error); QVERIFY2(pair.has_value(), qPrintable(error)); WindowsPipeDevice broker, worker; QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle(), &error)); QVERIFY(worker.adopt(pair->worker.takeReadHandle(), pair->worker.takeWriteHandle(), &error)); IpcChannel parent(&broker), child(&worker); QSignalSpy childReceived(&child, &IpcChannel::messageReceived); QSignalSpy parentReceived(&parent, &IpcChannel::messageReceived); QSignalSpy parentErrors(&parent, &IpcChannel::protocolError); QSignalSpy childErrors(&child, &IpcChannel::protocolError); const QCborMap request{{"protocolVersion", 1}, {"sessionId", "native-pipe"}, {"requestId", 1}, {"generation", 1}, {"operation", "render"}, { "payload", QCborMap { {"page", 0} } }}; QVERIFY(parent.send(request)); QTRY_COMPARE_WITH_TIMEOUT(childReceived.size(), 1, 5000); QCOMPARE(qvariant_cast(childReceived[0][0]), request); auto reply = request; reply.remove(QStringLiteral("payload")); const QByteArray pixels(6 * 1024 * 1024, char(0xa5)); reply.insert(QStringLiteral("result"), QCborMap{{ "data", pixels }}); QVERIFY(child.send(reply)); QTRY_COMPARE_WITH_TIMEOUT(parentReceived.size(), 1, 15000); QCOMPARE(qvariant_cast(parentReceived[0][0]), reply); QTRY_COMPARE(worker.bytesToWrite(), qint64(0)); QCOMPARE(parentErrors.size(), 0); QCOMPARE(childErrors.size(), 0); QCOMPARE(broker.bytesAvailable(), qint64(0)); #else QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass"); #endif } void boundedWriteDoesNotWaitForReader() { #ifdef Q_OS_WIN auto pair = createWindowsPipePair(); QVERIFY(pair.has_value()); WindowsPipeDevice broker; QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle())); // Keep the other endpoint open without issuing a read. A blocking pipe // write would hang here after its kernel buffer fills. const QByteArray bytes(WindowsPipeDevice::MaxWriteBuffer, 'x'); QElapsedTimer elapsed; elapsed.start(); QCOMPARE(broker.write(bytes), qint64(bytes.size())); QVERIFY2(elapsed.elapsed() < 500, "The GUI write path waited for a pipe reader"); QCOMPARE(broker.bytesToWrite(), WindowsPipeDevice::MaxWriteBuffer); QCOMPARE(broker.write("x", 1), qint64(-1)); QVERIFY(broker.bytesToWrite() <= WindowsPipeDevice::MaxWriteBuffer); WindowsPipeDevice worker; QVERIFY(worker.adopt(pair->worker.takeReadHandle(), pair->worker.takeWriteHandle())); qint64 received = 0; connect(&worker, &QIODevice::readyRead, this, [&] { received += worker.readAll().size(); }); QTRY_COMPARE_WITH_TIMEOUT(received, qint64(bytes.size()), 15000); QTRY_COMPARE(broker.bytesToWrite(), qint64(0)); #else QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass"); #endif } void workerPipeCannotAcquireBrokerImpersonationToken() { #ifdef Q_OS_WIN auto pair = createWindowsPipePair(); QVERIFY(pair.has_value()); const auto workerRead = reinterpret_cast(pair->worker.readHandle()); WindowsPipeDevice broker, worker; QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle())); QVERIFY(worker.adopt(pair->worker.takeReadHandle(), pair->worker.takeWriteHandle())); QCOMPARE(broker.write("probe", 5), qint64(5)); QTRY_COMPARE_WITH_TIMEOUT(worker.bytesAvailable(), qint64(5), 5000); QCOMPARE(worker.readAll(), QByteArray("probe")); // Exercise the server end actually handed to a worker. The test runs // outside LPAC to test the pipe's own SQOS boundary independently of // any additional token restrictions imposed by the OS sandbox. const bool impersonated = ImpersonateNamedPipeClient(workerRead); HANDLE token = nullptr; const bool opened = impersonated && OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token); const DWORD tokenError = GetLastError(); if (token) CloseHandle(token); const bool reverted = !impersonated || RevertToSelf(); QVERIFY(reverted); QVERIFY(impersonated); QVERIFY(!opened); QCOMPARE(tokenError, DWORD(ERROR_CANT_OPEN_ANONYMOUS)); #else QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass"); #endif } void closeCancelsPendingIoWithoutStaleSignals() { #ifdef Q_OS_WIN DWORD before = 0; QVERIFY(GetProcessHandleCount(GetCurrentProcess(), &before)); for (int iteration = 0; iteration < 16; ++iteration) { auto pair = createWindowsPipePair(); QVERIFY(pair.has_value()); WindowsPipeDevice broker; QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle())); QSignalSpy reads(&broker, &QIODevice::readyRead); QSignalSpy writes(&broker, &QIODevice::bytesWritten); QSignalSpy errors(&broker, &WindowsPipeDevice::transportError); QCoreApplication::processEvents(); // Arm an overlapped read. QCOMPARE(broker.write(QByteArray(1024 * 1024, 'x')), qint64(1024 * 1024)); QElapsedTimer elapsed; elapsed.start(); broker.close(); QVERIFY2(elapsed.elapsed() < 500, "close waited on a pipe operation on the GUI thread"); pair->worker.close(); QVERIFY(!broker.isOpen()); QCOMPARE(broker.bytesToWrite(), qint64(0)); QTest::qWait(5); QCOMPARE(reads.size(), 0); QCOMPARE(writes.size(), 0); QCOMPARE(errors.size(), 0); } auto handleCount = [] { QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); DWORD count = 0; return GetProcessHandleCount(GetCurrentProcess(), &count) ? count : DWORD(-1); }; // Qt may initialize process-wide wait infrastructure on its first use. QTRY_VERIFY_WITH_TIMEOUT(handleCount() <= before + 8, 5000); #else QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass"); #endif } void disconnectFailsOnce() { #ifdef Q_OS_WIN auto pair = createWindowsPipePair(); QVERIFY(pair.has_value()); WindowsPipeDevice broker; QVERIFY(broker.adopt(pair->broker.takeReadHandle(), pair->broker.takeWriteHandle())); QSignalSpy errors(&broker, &WindowsPipeDevice::transportError); QSignalSpy disconnected(&broker, &WindowsPipeDevice::disconnected); pair->worker.close(); QTRY_COMPARE_WITH_TIMEOUT(errors.size(), 1, 5000); QCOMPARE(disconnected.size(), 1); QVERIFY(!broker.isOpen()); broker.close(); QTest::qWait(20); QCOMPARE(errors.size(), 1); QCOMPARE(disconnected.size(), 1); #else QSKIP("Native Windows pipe execution requires Windows; Linux stub is not a native pass"); #endif } }; QTEST_GUILESS_MAIN(WindowsPipeTest) #include "test_windows_pipe.moc"