88// Version history
99// 2022-08: Initial contribution (Steve Dower)
1010
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+
1116#define _WIN32_DCOM
1217#include < Windows.h>
1318#include < comdef.h>
@@ -39,6 +44,8 @@ struct _query_data {
3944 LPCWSTR query;
4045 HANDLE writePipe;
4146 HANDLE readPipe;
47+ HANDLE initEvent;
48+ HANDLE connectEvent;
4249};
4350
4451
@@ -75,12 +82,18 @@ _query_thread(LPVOID param)
7582 IID_IWbemLocator, (LPVOID *)&locator
7683 );
7784 }
85+ if (SUCCEEDED (hr) && !SetEvent (data->initEvent )) {
86+ hr = HRESULT_FROM_WIN32 (GetLastError ());
87+ }
7888 if (SUCCEEDED (hr)) {
7989 hr = locator->ConnectServer (
8090 bstr_t (L" ROOT\\ CIMV2" ),
8191 NULL , NULL , 0 , NULL , 0 , 0 , &services
8292 );
8393 }
94+ if (SUCCEEDED (hr) && !SetEvent (data->connectEvent )) {
95+ hr = HRESULT_FROM_WIN32 (GetLastError ());
96+ }
8497 if (SUCCEEDED (hr)) {
8598 hr = CoSetProxyBlanket (
8699 services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL ,
@@ -184,6 +197,24 @@ _query_thread(LPVOID param)
184197}
185198
186199
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+
187218/* [clinic input]
188219_wmi.exec_query
189220
@@ -226,7 +257,11 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query)
226257
227258 Py_BEGIN_ALLOW_THREADS
228259
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+ {
230265 err = GetLastError ();
231266 } else {
232267 hThread = CreateThread (NULL , 0 , _query_thread, (LPVOID*)&data, 0 , NULL );
@@ -238,6 +273,19 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query)
238273 }
239274 }
240275
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+
241289 while (!err) {
242290 if (ReadFile (
243291 data.readPipe ,
@@ -259,28 +307,35 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query)
259307 CloseHandle (data.readPipe );
260308 }
261309
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 ;
274327 }
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.
277330 if (err == 0 || err == ERROR_BROKEN_PIPE) {
278- err = GetLastError () ;
331+ err = thread_err ;
279332 }
280- break ;
333+
334+ CloseHandle (hThread);
281335 }
282336
283- CloseHandle (hThread);
337+ CloseHandle (data.initEvent );
338+ CloseHandle (data.connectEvent );
284339 hThread = NULL ;
285340
286341 Py_END_ALLOW_THREADS
0 commit comments