8
8
// Version history
9
9
// 2022-08: Initial contribution (Steve Dower)
10
10
11
+ // clinic/_wmimodule.cpp.h uses internal pycore_modsupport.h API
12
+ #ifndef Py_BUILD_CORE_BUILTIN
13
+ # define Py_BUILD_CORE_MODULE 1
14
+ #endif
15
+
11
16
#define _WIN32_DCOM
12
17
#include < Windows.h>
13
18
#include < comdef.h>
@@ -39,6 +44,8 @@ struct _query_data {
39
44
LPCWSTR query;
40
45
HANDLE writePipe;
41
46
HANDLE readPipe;
47
+ HANDLE initEvent;
48
+ HANDLE connectEvent;
42
49
};
43
50
44
51
@@ -75,12 +82,18 @@ _query_thread(LPVOID param)
75
82
IID_IWbemLocator, (LPVOID *)&locator
76
83
);
77
84
}
85
+ if (SUCCEEDED (hr) && !SetEvent (data->initEvent )) {
86
+ hr = HRESULT_FROM_WIN32 (GetLastError ());
87
+ }
78
88
if (SUCCEEDED (hr)) {
79
89
hr = locator->ConnectServer (
80
90
bstr_t (L" ROOT\\ CIMV2" ),
81
91
NULL , NULL , 0 , NULL , 0 , 0 , &services
82
92
);
83
93
}
94
+ if (SUCCEEDED (hr) && !SetEvent (data->connectEvent )) {
95
+ hr = HRESULT_FROM_WIN32 (GetLastError ());
96
+ }
84
97
if (SUCCEEDED (hr)) {
85
98
hr = CoSetProxyBlanket (
86
99
services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL ,
@@ -184,6 +197,24 @@ _query_thread(LPVOID param)
184
197
}
185
198
186
199
200
+ static DWORD
201
+ wait_event (HANDLE event, DWORD timeout)
202
+ {
203
+ DWORD err = 0 ;
204
+ switch (WaitForSingleObject (event, timeout)) {
205
+ case WAIT_OBJECT_0:
206
+ break ;
207
+ case WAIT_TIMEOUT:
208
+ err = WAIT_TIMEOUT;
209
+ break ;
210
+ default :
211
+ err = GetLastError ();
212
+ break ;
213
+ }
214
+ return err;
215
+ }
216
+
217
+
187
218
/* [clinic input]
188
219
_wmi.exec_query
189
220
@@ -226,7 +257,11 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query)
226
257
227
258
Py_BEGIN_ALLOW_THREADS
228
259
229
- if (!CreatePipe (&data.readPipe , &data.writePipe , NULL , 0 )) {
260
+ data.initEvent = CreateEvent (NULL , TRUE , FALSE , NULL );
261
+ data.connectEvent = CreateEvent (NULL , TRUE , FALSE , NULL );
262
+ if (!data.initEvent || !data.connectEvent ||
263
+ !CreatePipe (&data.readPipe , &data.writePipe , NULL , 0 ))
264
+ {
230
265
err = GetLastError ();
231
266
} else {
232
267
hThread = CreateThread (NULL , 0 , _query_thread, (LPVOID*)&data, 0 , NULL );
@@ -238,6 +273,19 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query)
238
273
}
239
274
}
240
275
276
+ // gh-112278: If current user doesn't have permission to query the WMI, the
277
+ // function IWbemLocator::ConnectServer will hang for 5 seconds, and there
278
+ // is no way to specify the timeout. So we use an Event object to simulate
279
+ // a timeout. The initEvent will be set after COM initialization, it will
280
+ // take a longer time when first initialized. The connectEvent will be set
281
+ // after connected to WMI.
282
+ if (!err) {
283
+ err = wait_event (data.initEvent , 1000 );
284
+ if (!err) {
285
+ err = wait_event (data.connectEvent , 100 );
286
+ }
287
+ }
288
+
241
289
while (!err) {
242
290
if (ReadFile (
243
291
data.readPipe ,
@@ -259,28 +307,35 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query)
259
307
CloseHandle (data.readPipe );
260
308
}
261
309
262
- // Allow the thread some time to clean up
263
- switch (WaitForSingleObject (hThread, 1000 )) {
264
- case WAIT_OBJECT_0:
265
- // Thread ended cleanly
266
- if (!GetExitCodeThread (hThread, (LPDWORD)&err)) {
267
- err = GetLastError ();
268
- }
269
- break ;
270
- case WAIT_TIMEOUT:
271
- // Probably stuck - there's not much we can do, unfortunately
272
- if (err == 0 || err == ERROR_BROKEN_PIPE) {
273
- err = WAIT_TIMEOUT;
310
+ if (hThread) {
311
+ // Allow the thread some time to clean up
312
+ int thread_err;
313
+ switch (WaitForSingleObject (hThread, 100 )) {
314
+ case WAIT_OBJECT_0:
315
+ // Thread ended cleanly
316
+ if (!GetExitCodeThread (hThread, (LPDWORD)&thread_err)) {
317
+ thread_err = GetLastError ();
318
+ }
319
+ break ;
320
+ case WAIT_TIMEOUT:
321
+ // Probably stuck - there's not much we can do, unfortunately
322
+ thread_err = WAIT_TIMEOUT;
323
+ break ;
324
+ default :
325
+ thread_err = GetLastError ();
326
+ break ;
274
327
}
275
- break ;
276
- default :
328
+ // An error on our side is more likely to be relevant than one from
329
+ // the thread, but if we don't have one on our side we'll take theirs.
277
330
if (err == 0 || err == ERROR_BROKEN_PIPE) {
278
- err = GetLastError () ;
331
+ err = thread_err ;
279
332
}
280
- break ;
333
+
334
+ CloseHandle (hThread);
281
335
}
282
336
283
- CloseHandle (hThread);
337
+ CloseHandle (data.initEvent );
338
+ CloseHandle (data.connectEvent );
284
339
hThread = NULL ;
285
340
286
341
Py_END_ALLOW_THREADS
0 commit comments