Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qlocalserver_win.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qlocalserver.h"
7#include "qlocalsocket.h"
8#include <QtCore/private/qsystemerror_p.h>
9
10#include <qdebug.h>
11
12#include <aclapi.h>
13#include <accctrl.h>
14#include <sddl.h>
15
16#include <memory>
17
18// The buffer size need to be 0 otherwise data could be
19// lost if the socket that has written data closes the connection
20// before it is read. Pipewriter is used for write buffering.
21#define BUFSIZE 0
22
24
25using namespace Qt::StringLiterals;
26
27bool QLocalServerPrivate::addListener()
28{
29 // The object must not change its address once the
30 // contained OVERLAPPED struct is passed to Windows.
31 listeners.push_back(std::make_unique<Listener>());
32 auto &listener = listeners.back();
33
34 SECURITY_ATTRIBUTES sa;
35 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
36 sa.bInheritHandle = FALSE; //non inheritable handle, same as default
37 sa.lpSecurityDescriptor = 0; //default security descriptor
38
39 std::unique_ptr<SECURITY_DESCRIPTOR> pSD;
40 PSID worldSID = 0;
41 QByteArray aclBuffer;
42 QByteArray tokenUserBuffer;
43 QByteArray tokenGroupBuffer;
44
45 // create security descriptor if access options were specified
46 if ((socketOptions.value() & QLocalServer::WorldAccessOption)) {
47 pSD.reset(new SECURITY_DESCRIPTOR);
48 if (!InitializeSecurityDescriptor(pSD.get(), SECURITY_DESCRIPTOR_REVISION)) {
49 setError("QLocalServerPrivate::addListener"_L1);
50 return false;
51 }
52 HANDLE hToken = NULL;
53 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
54 return false;
55 DWORD dwBufferSize = 0;
56 GetTokenInformation(hToken, TokenUser, 0, 0, &dwBufferSize);
57 tokenUserBuffer.fill(0, dwBufferSize);
58 auto pTokenUser = reinterpret_cast<PTOKEN_USER>(tokenUserBuffer.data());
59 if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize)) {
60 setError("QLocalServerPrivate::addListener"_L1);
61 CloseHandle(hToken);
62 return false;
63 }
64
65 dwBufferSize = 0;
66 GetTokenInformation(hToken, TokenPrimaryGroup, 0, 0, &dwBufferSize);
67 tokenGroupBuffer.fill(0, dwBufferSize);
68 auto pTokenGroup = reinterpret_cast<PTOKEN_PRIMARY_GROUP>(tokenGroupBuffer.data());
69 if (!GetTokenInformation(hToken, TokenPrimaryGroup, pTokenGroup, dwBufferSize, &dwBufferSize)) {
70 setError("QLocalServerPrivate::addListener"_L1);
71 CloseHandle(hToken);
72 return false;
73 }
74 CloseHandle(hToken);
75
76#ifdef QLOCALSERVER_DEBUG
77 DWORD groupNameSize;
78 DWORD domainNameSize;
79 SID_NAME_USE groupNameUse;
80 LPWSTR groupNameSid;
81 LookupAccountSid(0, pTokenGroup->PrimaryGroup, 0, &groupNameSize, 0, &domainNameSize, &groupNameUse);
82 auto groupName = std::unique_ptr<wchar_t[]>(new wchar_t[groupNameSize]);
83 auto domainName = std::unique_ptr<wchar_t[]>(new wchar_t[domainNameSize]);
84 const bool lookup = LookupAccountSid(0, pTokenGroup->PrimaryGroup, groupName.get(),
85 &groupNameSize, domainName.get(), &domainNameSize,
86 &groupNameUse);
87 if (lookup) {
88 qDebug() << "primary group" << QString::fromWCharArray(domainName.get()) << "\\"
89 << QString::fromWCharArray(groupName.get()) << "type=" << groupNameUse;
90 }
91 if (ConvertSidToStringSid(pTokenGroup->PrimaryGroup, &groupNameSid)) {
92 qDebug() << "primary group SID" << QString::fromWCharArray(groupNameSid) << "valid" << IsValidSid(pTokenGroup->PrimaryGroup);
93 LocalFree(groupNameSid);
94 }
95#endif
96
97 SID_IDENTIFIER_AUTHORITY WorldAuth = { SECURITY_WORLD_SID_AUTHORITY };
98 if (!AllocateAndInitializeSid(&WorldAuth, 1, SECURITY_WORLD_RID,
99 0, 0, 0, 0, 0, 0, 0,
100 &worldSID)) {
101 setError("QLocalServerPrivate::addListener"_L1);
102 return false;
103 }
104
105 //calculate size of ACL buffer
106 DWORD aclSize = sizeof(ACL) + ((sizeof(ACCESS_ALLOWED_ACE)) * 3);
107 aclSize += GetLengthSid(pTokenUser->User.Sid) - sizeof(DWORD);
108 aclSize += GetLengthSid(pTokenGroup->PrimaryGroup) - sizeof(DWORD);
109 aclSize += GetLengthSid(worldSID) - sizeof(DWORD);
110 aclSize = (aclSize + (sizeof(DWORD) - 1)) & 0xfffffffc;
111
112 aclBuffer.fill(0, aclSize);
113 auto acl = reinterpret_cast<PACL>(aclBuffer.data());
114 InitializeAcl(acl, aclSize, ACL_REVISION_DS);
115
116 if (socketOptions.value() & QLocalServer::UserAccessOption) {
117 if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, pTokenUser->User.Sid)) {
118 setError("QLocalServerPrivate::addListener"_L1);
119 FreeSid(worldSID);
120 return false;
121 }
122 }
123 if (socketOptions.value() & QLocalServer::GroupAccessOption) {
124 if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, pTokenGroup->PrimaryGroup)) {
125 setError("QLocalServerPrivate::addListener"_L1);
126 FreeSid(worldSID);
127 return false;
128 }
129 }
130 if (socketOptions.value() & QLocalServer::OtherAccessOption) {
131 if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, worldSID)) {
132 setError("QLocalServerPrivate::addListener"_L1);
133 FreeSid(worldSID);
134 return false;
135 }
136 }
137 SetSecurityDescriptorOwner(pSD.get(), pTokenUser->User.Sid, FALSE);
138 SetSecurityDescriptorGroup(pSD.get(), pTokenGroup->PrimaryGroup, FALSE);
139 if (!SetSecurityDescriptorDacl(pSD.get(), TRUE, acl, FALSE)) {
140 setError("QLocalServerPrivate::addListener"_L1);
141 FreeSid(worldSID);
142 return false;
143 }
144
145 sa.lpSecurityDescriptor = pSD.get();
146 }
147
148 listener->handle = CreateNamedPipe(
149 reinterpret_cast<const wchar_t *>(fullServerName.utf16()), // pipe name
150 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, // read/write access
151 PIPE_TYPE_BYTE | // byte type pipe
152 PIPE_READMODE_BYTE | // byte-read mode
153 PIPE_WAIT | // blocking mode
154 PIPE_REJECT_REMOTE_CLIENTS, // because this is a local server
155 PIPE_UNLIMITED_INSTANCES, // max. instances
156 BUFSIZE, // output buffer size
157 BUFSIZE, // input buffer size
158 3000, // client time-out
159 &sa);
160
161 if (listener->handle == INVALID_HANDLE_VALUE) {
162 setError("QLocalServerPrivate::addListener"_L1);
163 listeners.pop_back();
164 return false;
165 }
166
167 if (worldSID)
168 FreeSid(worldSID);
169
170 memset(&listener->overlapped, 0, sizeof(OVERLAPPED));
171 listener->overlapped.hEvent = eventHandle;
172
173 // Beware! ConnectNamedPipe will reset the eventHandle to non-signaled.
174 // Callers of addListener must check all listeners for connections.
175 if (!ConnectNamedPipe(listener->handle, &listener->overlapped)) {
176 switch (GetLastError()) {
177 case ERROR_IO_PENDING:
178 listener->connected = false;
179 break;
180 case ERROR_PIPE_CONNECTED:
181 listener->connected = true;
182 break;
183 default:
184 CloseHandle(listener->handle);
185 setError("QLocalServerPrivate::addListener"_L1);
186 listeners.pop_back();
187 return false;
188 }
189 } else {
190 Q_ASSERT_X(false, "QLocalServerPrivate::addListener", "The impossible happened");
191 SetEvent(eventHandle);
192 }
193 return true;
194}
195
196void QLocalServerPrivate::setError(const QString &function)
197{
198 int windowsError = GetLastError();
199 errorString = QString::fromLatin1("%1: %2").arg(function, qt_error_string(windowsError));
200 error = QAbstractSocket::UnknownSocketError;
201}
202
203void QLocalServerPrivate::init()
204{
205}
206
207bool QLocalServerPrivate::removeServer(const QString &name)
208{
209 Q_UNUSED(name);
210 return true;
211}
212
213bool QLocalServerPrivate::listen(const QString &name)
214{
215 Q_Q(QLocalServer);
216
217 const auto pipePath = "\\\\.\\pipe\\"_L1;
218 if (name.startsWith(pipePath))
219 fullServerName = name;
220 else
221 fullServerName = pipePath + name;
222
223 // Use only one event for all listeners of one socket.
224 // The idea is that listener events are rare, so polling all listeners once in a while is
225 // cheap compared to waiting for N additional events in each iteration of the main loop.
226 eventHandle = CreateEvent(NULL, TRUE, FALSE, NULL); // If the function fails, the return value is NULL
227 connectionEventNotifier = new QWinEventNotifier(eventHandle , q);
228 q->connect(connectionEventNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_onNewConnection()));
229
230 for (int i = 0; i < listenBacklog; ++i)
231 if (!addListener())
232 return false;
233
234 _q_onNewConnection();
235 return true;
236}
237
238bool QLocalServerPrivate::listen(qintptr)
239{
240 qWarning("QLocalServer::listen(qintptr) is not supported on Windows QTBUG-24230");
241 return false;
242}
243
244void QLocalServerPrivate::_q_onNewConnection()
245{
246 Q_Q(QLocalServer);
247 DWORD dummy;
248 bool tryAgain;
249 do {
250 tryAgain = false;
251
252 // Reset first, otherwise we could reset an event which was asserted
253 // immediately after we checked the conn status.
254 ResetEvent(eventHandle);
255
256 // Testing shows that there is indeed absolutely no guarantee which listener gets
257 // a client connection first, so there is no way around polling all of them.
258 for (size_t i = 0; i < listeners.size(); ) {
259 HANDLE handle = listeners[i]->handle;
260 if (listeners[i]->connected
261 || GetOverlappedResult(handle, &listeners[i]->overlapped, &dummy, FALSE))
262 {
263 listeners.erase(listeners.begin() + i);
264
265 addListener();
266
267 if (pendingConnections.size() > maxPendingConnections)
268 connectionEventNotifier->setEnabled(false);
269 else
270 tryAgain = true;
271
272 // Make this the last thing so connected slots can wreak the least havoc
273 q->incomingConnection(reinterpret_cast<quintptr>(handle));
274 } else {
275 if (GetLastError() != ERROR_IO_INCOMPLETE) {
276 q->close();
277 setError("QLocalServerPrivate::_q_onNewConnection"_L1);
278 return;
279 }
280
281 ++i;
282 }
283 }
284 } while (tryAgain);
285}
286
287void QLocalServerPrivate::closeServer()
288{
289 connectionEventNotifier->setEnabled(false); // Otherwise, closed handle is checked before deleter runs
290 connectionEventNotifier->deleteLater();
291 connectionEventNotifier = 0;
292 CloseHandle(eventHandle);
293 eventHandle = nullptr;
294 for (size_t i = 0; i < listeners.size(); ++i)
295 CloseHandle(listeners[i]->handle);
296 listeners.clear();
297}
298
299void QLocalServerPrivate::waitForNewConnection(int msecs, bool *timedOut)
300{
301 Q_Q(QLocalServer);
302 if (!pendingConnections.isEmpty() || !q->isListening())
303 return;
304
305 DWORD result = WaitForSingleObject(eventHandle, (msecs == -1) ? INFINITE : msecs);
306 if (result == WAIT_TIMEOUT) {
307 if (timedOut)
308 *timedOut = true;
309 } else {
310 _q_onNewConnection();
311 }
312}
313
314QT_END_NAMESPACE
Combined button and popup list for selecting options.
#define BUFSIZE