diff options
Diffstat (limited to 'src/engine/pipe.cpp')
-rw-r--r-- | src/engine/pipe.cpp | 873 |
1 files changed, 873 insertions, 0 deletions
diff --git a/src/engine/pipe.cpp b/src/engine/pipe.cpp new file mode 100644 index 00000000..7ecc4859 --- /dev/null +++ b/src/engine/pipe.cpp | |||
@@ -0,0 +1,873 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | #include "precomp.h" | ||
4 | |||
5 | static const DWORD PIPE_64KB = 64 * 1024; | ||
6 | static const DWORD PIPE_WAIT_FOR_CONNECTION = 100; // wait a 10th of a second, | ||
7 | static const DWORD PIPE_RETRY_FOR_CONNECTION = 1800; // for up to 3 minutes. | ||
8 | |||
9 | static const LPCWSTR PIPE_NAME_FORMAT_STRING = L"\\\\.\\pipe\\%ls"; | ||
10 | static const LPCWSTR CACHE_PIPE_NAME_FORMAT_STRING = L"\\\\.\\pipe\\%ls.Cache"; | ||
11 | |||
12 | static HRESULT AllocatePipeMessage( | ||
13 | __in DWORD dwMessage, | ||
14 | __in_bcount_opt(cbData) LPVOID pvData, | ||
15 | __in DWORD cbData, | ||
16 | __out_bcount(cb) LPVOID* ppvMessage, | ||
17 | __out DWORD* cbMessage | ||
18 | ); | ||
19 | static void FreePipeMessage( | ||
20 | __in BURN_PIPE_MESSAGE *pMsg | ||
21 | ); | ||
22 | static HRESULT WritePipeMessage( | ||
23 | __in HANDLE hPipe, | ||
24 | __in DWORD dwMessage, | ||
25 | __in_bcount_opt(cbData) LPVOID pvData, | ||
26 | __in DWORD cbData | ||
27 | ); | ||
28 | static HRESULT GetPipeMessage( | ||
29 | __in HANDLE hPipe, | ||
30 | __in BURN_PIPE_MESSAGE* pMsg | ||
31 | ); | ||
32 | static HRESULT ChildPipeConnected( | ||
33 | __in HANDLE hPipe, | ||
34 | __in_z LPCWSTR wzSecret, | ||
35 | __inout DWORD* pdwProcessId | ||
36 | ); | ||
37 | |||
38 | |||
39 | |||
40 | /******************************************************************* | ||
41 | PipeConnectionInitialize - initialize pipe connection data. | ||
42 | |||
43 | *******************************************************************/ | ||
44 | void PipeConnectionInitialize( | ||
45 | __in BURN_PIPE_CONNECTION* pConnection | ||
46 | ) | ||
47 | { | ||
48 | memset(pConnection, 0, sizeof(BURN_PIPE_CONNECTION)); | ||
49 | pConnection->hPipe = INVALID_HANDLE_VALUE; | ||
50 | pConnection->hCachePipe = INVALID_HANDLE_VALUE; | ||
51 | } | ||
52 | |||
53 | /******************************************************************* | ||
54 | PipeConnectionUninitialize - free data in a pipe connection. | ||
55 | |||
56 | *******************************************************************/ | ||
57 | void PipeConnectionUninitialize( | ||
58 | __in BURN_PIPE_CONNECTION* pConnection | ||
59 | ) | ||
60 | { | ||
61 | ReleaseFileHandle(pConnection->hCachePipe); | ||
62 | ReleaseFileHandle(pConnection->hPipe); | ||
63 | ReleaseHandle(pConnection->hProcess); | ||
64 | ReleaseStr(pConnection->sczSecret); | ||
65 | ReleaseStr(pConnection->sczName); | ||
66 | |||
67 | memset(pConnection, 0, sizeof(BURN_PIPE_CONNECTION)); | ||
68 | pConnection->hPipe = INVALID_HANDLE_VALUE; | ||
69 | pConnection->hCachePipe = INVALID_HANDLE_VALUE; | ||
70 | } | ||
71 | |||
72 | /******************************************************************* | ||
73 | PipeSendMessage - | ||
74 | |||
75 | *******************************************************************/ | ||
76 | extern "C" HRESULT PipeSendMessage( | ||
77 | __in HANDLE hPipe, | ||
78 | __in DWORD dwMessage, | ||
79 | __in_bcount_opt(cbData) LPVOID pvData, | ||
80 | __in DWORD cbData, | ||
81 | __in_opt PFN_PIPE_MESSAGE_CALLBACK pfnCallback, | ||
82 | __in_opt LPVOID pvContext, | ||
83 | __out DWORD* pdwResult | ||
84 | ) | ||
85 | { | ||
86 | HRESULT hr = S_OK; | ||
87 | BURN_PIPE_RESULT result = { }; | ||
88 | |||
89 | hr = WritePipeMessage(hPipe, dwMessage, pvData, cbData); | ||
90 | ExitOnFailure(hr, "Failed to write send message to pipe."); | ||
91 | |||
92 | hr = PipePumpMessages(hPipe, pfnCallback, pvContext, &result); | ||
93 | ExitOnFailure(hr, "Failed to pump messages during send message to pipe."); | ||
94 | |||
95 | *pdwResult = result.dwResult; | ||
96 | |||
97 | LExit: | ||
98 | return hr; | ||
99 | } | ||
100 | |||
101 | /******************************************************************* | ||
102 | PipePumpMessages - | ||
103 | |||
104 | *******************************************************************/ | ||
105 | extern "C" HRESULT PipePumpMessages( | ||
106 | __in HANDLE hPipe, | ||
107 | __in_opt PFN_PIPE_MESSAGE_CALLBACK pfnCallback, | ||
108 | __in_opt LPVOID pvContext, | ||
109 | __in BURN_PIPE_RESULT* pResult | ||
110 | ) | ||
111 | { | ||
112 | HRESULT hr = S_OK; | ||
113 | BURN_PIPE_MESSAGE msg = { }; | ||
114 | SIZE_T iData = 0; | ||
115 | LPSTR sczMessage = NULL; | ||
116 | DWORD dwResult = 0; | ||
117 | |||
118 | // Pump messages from child process. | ||
119 | while (S_OK == (hr = GetPipeMessage(hPipe, &msg))) | ||
120 | { | ||
121 | switch (msg.dwMessage) | ||
122 | { | ||
123 | case BURN_PIPE_MESSAGE_TYPE_LOG: | ||
124 | iData = 0; | ||
125 | |||
126 | hr = BuffReadStringAnsi((BYTE*)msg.pvData, msg.cbData, &iData, &sczMessage); | ||
127 | ExitOnFailure(hr, "Failed to read log message."); | ||
128 | |||
129 | hr = LogStringWorkRaw(sczMessage); | ||
130 | ExitOnFailure(hr, "Failed to write log message:'%hs'.", sczMessage); | ||
131 | |||
132 | dwResult = static_cast<DWORD>(hr); | ||
133 | break; | ||
134 | |||
135 | case BURN_PIPE_MESSAGE_TYPE_COMPLETE: | ||
136 | if (!msg.pvData || sizeof(DWORD) != msg.cbData) | ||
137 | { | ||
138 | hr = E_INVALIDARG; | ||
139 | ExitOnRootFailure(hr, "No status returned to PipePumpMessages()"); | ||
140 | } | ||
141 | |||
142 | pResult->dwResult = *static_cast<DWORD*>(msg.pvData); | ||
143 | ExitFunction1(hr = S_OK); // exit loop. | ||
144 | |||
145 | case BURN_PIPE_MESSAGE_TYPE_TERMINATE: | ||
146 | iData = 0; | ||
147 | |||
148 | hr = BuffReadNumber(static_cast<BYTE*>(msg.pvData), msg.cbData, &iData, &pResult->dwResult); | ||
149 | ExitOnFailure(hr, "Failed to read returned result to PipePumpMessages()"); | ||
150 | |||
151 | if (sizeof(DWORD) * 2 == msg.cbData) | ||
152 | { | ||
153 | hr = BuffReadNumber(static_cast<BYTE*>(msg.pvData), msg.cbData, &iData, (DWORD*)&pResult->fRestart); | ||
154 | ExitOnFailure(hr, "Failed to read returned restart to PipePumpMessages()"); | ||
155 | } | ||
156 | |||
157 | ExitFunction1(hr = S_OK); // exit loop. | ||
158 | |||
159 | default: | ||
160 | if (pfnCallback) | ||
161 | { | ||
162 | hr = pfnCallback(&msg, pvContext, &dwResult); | ||
163 | } | ||
164 | else | ||
165 | { | ||
166 | hr = E_INVALIDARG; | ||
167 | } | ||
168 | ExitOnFailure(hr, "Failed to process message: %u", msg.dwMessage); | ||
169 | break; | ||
170 | } | ||
171 | |||
172 | // post result | ||
173 | hr = WritePipeMessage(hPipe, static_cast<DWORD>(BURN_PIPE_MESSAGE_TYPE_COMPLETE), &dwResult, sizeof(dwResult)); | ||
174 | ExitOnFailure(hr, "Failed to post result to child process."); | ||
175 | |||
176 | FreePipeMessage(&msg); | ||
177 | } | ||
178 | ExitOnFailure(hr, "Failed to get message over pipe"); | ||
179 | |||
180 | if (S_FALSE == hr) | ||
181 | { | ||
182 | hr = S_OK; | ||
183 | } | ||
184 | |||
185 | LExit: | ||
186 | ReleaseStr(sczMessage); | ||
187 | FreePipeMessage(&msg); | ||
188 | |||
189 | return hr; | ||
190 | } | ||
191 | |||
192 | /******************************************************************* | ||
193 | PipeCreateNameAndSecret - | ||
194 | |||
195 | *******************************************************************/ | ||
196 | extern "C" HRESULT PipeCreateNameAndSecret( | ||
197 | __out_z LPWSTR *psczConnectionName, | ||
198 | __out_z LPWSTR *psczSecret | ||
199 | ) | ||
200 | { | ||
201 | HRESULT hr = S_OK; | ||
202 | WCHAR wzGuid[GUID_STRING_LENGTH]; | ||
203 | LPWSTR sczConnectionName = NULL; | ||
204 | LPWSTR sczSecret = NULL; | ||
205 | |||
206 | // Create the unique pipe name. | ||
207 | hr = GuidFixedCreate(wzGuid); | ||
208 | ExitOnRootFailure(hr, "Failed to create pipe guid."); | ||
209 | |||
210 | hr = StrAllocFormatted(&sczConnectionName, L"BurnPipe.%s", wzGuid); | ||
211 | ExitOnFailure(hr, "Failed to allocate pipe name."); | ||
212 | |||
213 | // Create the unique client secret. | ||
214 | hr = GuidFixedCreate(wzGuid); | ||
215 | ExitOnRootFailure(hr, "Failed to create pipe secret."); | ||
216 | |||
217 | hr = StrAllocString(&sczSecret, wzGuid, 0); | ||
218 | ExitOnFailure(hr, "Failed to allocate pipe secret."); | ||
219 | |||
220 | *psczConnectionName = sczConnectionName; | ||
221 | sczConnectionName = NULL; | ||
222 | *psczSecret = sczSecret; | ||
223 | sczSecret = NULL; | ||
224 | |||
225 | LExit: | ||
226 | ReleaseStr(sczSecret); | ||
227 | ReleaseStr(sczConnectionName); | ||
228 | |||
229 | return hr; | ||
230 | } | ||
231 | |||
232 | /******************************************************************* | ||
233 | PipeCreatePipes - create the pipes and event to signal child process. | ||
234 | |||
235 | *******************************************************************/ | ||
236 | extern "C" HRESULT PipeCreatePipes( | ||
237 | __in BURN_PIPE_CONNECTION* pConnection, | ||
238 | __in BOOL fCreateCachePipe, | ||
239 | __out HANDLE* phEvent | ||
240 | ) | ||
241 | { | ||
242 | Assert(pConnection->sczName); | ||
243 | Assert(INVALID_HANDLE_VALUE == pConnection->hPipe); | ||
244 | Assert(INVALID_HANDLE_VALUE == pConnection->hCachePipe); | ||
245 | |||
246 | HRESULT hr = S_OK; | ||
247 | PSECURITY_DESCRIPTOR psd = NULL; | ||
248 | SECURITY_ATTRIBUTES sa = { }; | ||
249 | LPWSTR sczFullPipeName = NULL; | ||
250 | HANDLE hPipe = INVALID_HANDLE_VALUE; | ||
251 | HANDLE hCachePipe = INVALID_HANDLE_VALUE; | ||
252 | |||
253 | // Only the grant special rights when the pipe is being used for "embedded" | ||
254 | // scenarios (aka: there is no cache pipe). | ||
255 | if (!fCreateCachePipe) | ||
256 | { | ||
257 | // Create the security descriptor that grants read/write/sync access to Everyone. | ||
258 | // TODO: consider locking down "WD" to LogonIds (logon session) | ||
259 | LPCWSTR wzSddl = L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW0x00100000;;;WD)"; | ||
260 | if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(wzSddl, SDDL_REVISION_1, &psd, NULL)) | ||
261 | { | ||
262 | ExitWithLastError(hr, "Failed to create the security descriptor for the connection event and pipe."); | ||
263 | } | ||
264 | |||
265 | sa.nLength = sizeof(sa); | ||
266 | sa.lpSecurityDescriptor = psd; | ||
267 | sa.bInheritHandle = FALSE; | ||
268 | } | ||
269 | |||
270 | // Create the pipe. | ||
271 | hr = StrAllocFormatted(&sczFullPipeName, PIPE_NAME_FORMAT_STRING, pConnection->sczName); | ||
272 | ExitOnFailure(hr, "Failed to allocate full name of pipe: %ls", pConnection->sczName); | ||
273 | |||
274 | // TODO: consider using overlapped IO to do waits on the pipe and still be able to cancel and such. | ||
275 | hPipe = ::CreateNamedPipeW(sczFullPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, PIPE_64KB, PIPE_64KB, 1, psd ? &sa : NULL); | ||
276 | if (INVALID_HANDLE_VALUE == hPipe) | ||
277 | { | ||
278 | ExitWithLastError(hr, "Failed to create pipe: %ls", sczFullPipeName); | ||
279 | } | ||
280 | |||
281 | if (fCreateCachePipe) | ||
282 | { | ||
283 | // Create the cache pipe. | ||
284 | hr = StrAllocFormatted(&sczFullPipeName, CACHE_PIPE_NAME_FORMAT_STRING, pConnection->sczName); | ||
285 | ExitOnFailure(hr, "Failed to allocate full name of cache pipe: %ls", pConnection->sczName); | ||
286 | |||
287 | hCachePipe = ::CreateNamedPipeW(sczFullPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, PIPE_64KB, PIPE_64KB, 1, NULL); | ||
288 | if (INVALID_HANDLE_VALUE == hCachePipe) | ||
289 | { | ||
290 | ExitWithLastError(hr, "Failed to create pipe: %ls", sczFullPipeName); | ||
291 | } | ||
292 | } | ||
293 | |||
294 | pConnection->hCachePipe = hCachePipe; | ||
295 | hCachePipe = INVALID_HANDLE_VALUE; | ||
296 | |||
297 | pConnection->hPipe = hPipe; | ||
298 | hPipe = INVALID_HANDLE_VALUE; | ||
299 | |||
300 | // TODO: remove the following | ||
301 | *phEvent = NULL; | ||
302 | |||
303 | LExit: | ||
304 | ReleaseFileHandle(hCachePipe); | ||
305 | ReleaseFileHandle(hPipe); | ||
306 | ReleaseStr(sczFullPipeName); | ||
307 | |||
308 | if (psd) | ||
309 | { | ||
310 | ::LocalFree(psd); | ||
311 | } | ||
312 | |||
313 | return hr; | ||
314 | } | ||
315 | |||
316 | /******************************************************************* | ||
317 | PipeLaunchParentProcess - Called from the per-machine process to create | ||
318 | a per-user process and set up the | ||
319 | communication pipe. | ||
320 | |||
321 | *******************************************************************/ | ||
322 | const LPCWSTR BURN_COMMANDLINE_SWITCH_UNELEVATED = L"burn.unelevated"; | ||
323 | HRESULT PipeLaunchParentProcess( | ||
324 | __in_z LPCWSTR wzCommandLine, | ||
325 | __in int nCmdShow, | ||
326 | __in_z LPWSTR sczConnectionName, | ||
327 | __in_z LPWSTR sczSecret, | ||
328 | __in BOOL /*fDisableUnelevate*/ | ||
329 | ) | ||
330 | { | ||
331 | HRESULT hr = S_OK; | ||
332 | DWORD dwProcessId = 0; | ||
333 | LPWSTR sczBurnPath = NULL; | ||
334 | LPWSTR sczParameters = NULL; | ||
335 | HANDLE hProcess = NULL; | ||
336 | |||
337 | dwProcessId = ::GetCurrentProcessId(); | ||
338 | |||
339 | hr = PathForCurrentProcess(&sczBurnPath, NULL); | ||
340 | ExitOnFailure(hr, "Failed to get current process path."); | ||
341 | |||
342 | hr = StrAllocFormatted(&sczParameters, L"-%ls %ls %ls %u %ls", BURN_COMMANDLINE_SWITCH_UNELEVATED, sczConnectionName, sczSecret, dwProcessId, wzCommandLine); | ||
343 | ExitOnFailure(hr, "Failed to allocate parameters for unelevated process."); | ||
344 | |||
345 | #ifdef ENABLE_UNELEVATE | ||
346 | if (fDisableUnelevate) | ||
347 | { | ||
348 | hr = ProcExec(sczBurnPath, sczParameters, nCmdShow, &hProcess); | ||
349 | ExitOnFailure(hr, "Failed to launch parent process with unelevate disabled: %ls", sczBurnPath); | ||
350 | } | ||
351 | else | ||
352 | { | ||
353 | // Try to launch unelevated and if that fails for any reason, try launch our process normally (even though that may make it elevated). | ||
354 | hr = ProcExecuteAsInteractiveUser(sczBurnPath, sczParameters, &hProcess); | ||
355 | if (FAILED(hr)) | ||
356 | { | ||
357 | hr = ShelExecUnelevated(sczBurnPath, sczParameters, L"open", NULL, nCmdShow); | ||
358 | if (FAILED(hr)) | ||
359 | { | ||
360 | hr = ShelExec(sczBurnPath, sczParameters, L"open", NULL, nCmdShow, NULL, NULL); | ||
361 | ExitOnFailure(hr, "Failed to launch parent process: %ls", sczBurnPath); | ||
362 | } | ||
363 | } | ||
364 | } | ||
365 | #else | ||
366 | hr = ProcExec(sczBurnPath, sczParameters, nCmdShow, &hProcess); | ||
367 | ExitOnFailure(hr, "Failed to launch parent process with unelevate disabled: %ls", sczBurnPath); | ||
368 | #endif | ||
369 | |||
370 | LExit: | ||
371 | ReleaseHandle(hProcess); | ||
372 | ReleaseStr(sczParameters); | ||
373 | ReleaseStr(sczBurnPath); | ||
374 | |||
375 | return hr; | ||
376 | } | ||
377 | |||
378 | /******************************************************************* | ||
379 | PipeLaunchChildProcess - Called from the per-user process to create | ||
380 | the per-machine process and set up the | ||
381 | communication pipe. | ||
382 | |||
383 | *******************************************************************/ | ||
384 | extern "C" HRESULT PipeLaunchChildProcess( | ||
385 | __in_z LPCWSTR wzExecutablePath, | ||
386 | __in BURN_PIPE_CONNECTION* pConnection, | ||
387 | __in BOOL fElevate, | ||
388 | __in_opt HWND hwndParent | ||
389 | ) | ||
390 | { | ||
391 | HRESULT hr = S_OK; | ||
392 | DWORD dwCurrentProcessId = ::GetCurrentProcessId(); | ||
393 | LPWSTR sczParameters = NULL; | ||
394 | OS_VERSION osVersion = OS_VERSION_UNKNOWN; | ||
395 | DWORD dwServicePack = 0; | ||
396 | LPCWSTR wzVerb = NULL; | ||
397 | HANDLE hProcess = NULL; | ||
398 | |||
399 | hr = StrAllocFormatted(&sczParameters, L"-q -%ls %ls %ls %u", BURN_COMMANDLINE_SWITCH_ELEVATED, pConnection->sczName, pConnection->sczSecret, dwCurrentProcessId); | ||
400 | ExitOnFailure(hr, "Failed to allocate parameters for elevated process."); | ||
401 | |||
402 | OsGetVersion(&osVersion, &dwServicePack); | ||
403 | wzVerb = (OS_VERSION_VISTA > osVersion) || !fElevate ? L"open" : L"runas"; | ||
404 | |||
405 | // Since ShellExecuteEx doesn't support passing inherited handles, don't bother with CoreAppendFileHandleSelfToCommandLine. | ||
406 | // We could fallback to using ::DuplicateHandle to inject the file handle later if necessary. | ||
407 | hr = ShelExec(wzExecutablePath, sczParameters, wzVerb, NULL, SW_HIDE, hwndParent, &hProcess); | ||
408 | ExitOnFailure(hr, "Failed to launch elevated child process: %ls", wzExecutablePath); | ||
409 | |||
410 | pConnection->dwProcessId = ::GetProcessId(hProcess); | ||
411 | pConnection->hProcess = hProcess; | ||
412 | hProcess = NULL; | ||
413 | |||
414 | LExit: | ||
415 | ReleaseHandle(hProcess); | ||
416 | ReleaseStr(sczParameters); | ||
417 | |||
418 | return hr; | ||
419 | } | ||
420 | |||
421 | /******************************************************************* | ||
422 | PipeWaitForChildConnect - | ||
423 | |||
424 | *******************************************************************/ | ||
425 | extern "C" HRESULT PipeWaitForChildConnect( | ||
426 | __in BURN_PIPE_CONNECTION* pConnection | ||
427 | ) | ||
428 | { | ||
429 | HRESULT hr = S_OK; | ||
430 | HANDLE hPipes[2] = { pConnection->hPipe, pConnection->hCachePipe}; | ||
431 | LPCWSTR wzSecret = pConnection->sczSecret; | ||
432 | DWORD cbSecret = lstrlenW(wzSecret) * sizeof(WCHAR); | ||
433 | DWORD dwCurrentProcessId = ::GetCurrentProcessId(); | ||
434 | DWORD dwAck = 0; | ||
435 | DWORD cb = 0; | ||
436 | |||
437 | for (DWORD i = 0; i < countof(hPipes) && INVALID_HANDLE_VALUE != hPipes[i]; ++i) | ||
438 | { | ||
439 | HANDLE hPipe = hPipes[i]; | ||
440 | DWORD dwPipeState = PIPE_READMODE_BYTE | PIPE_NOWAIT; | ||
441 | |||
442 | // Temporarily make the pipe non-blocking so we will not get stuck in ::ConnectNamedPipe() forever | ||
443 | // if the child decides not to show up. | ||
444 | if (!::SetNamedPipeHandleState(hPipe, &dwPipeState, NULL, NULL)) | ||
445 | { | ||
446 | ExitWithLastError(hr, "Failed to set pipe to non-blocking."); | ||
447 | } | ||
448 | |||
449 | // Loop for a while waiting for a connection from child process. | ||
450 | DWORD cRetry = 0; | ||
451 | do | ||
452 | { | ||
453 | if (!::ConnectNamedPipe(hPipe, NULL)) | ||
454 | { | ||
455 | DWORD er = ::GetLastError(); | ||
456 | if (ERROR_PIPE_CONNECTED == er) | ||
457 | { | ||
458 | hr = S_OK; | ||
459 | break; | ||
460 | } | ||
461 | else if (ERROR_PIPE_LISTENING == er) | ||
462 | { | ||
463 | if (cRetry < PIPE_RETRY_FOR_CONNECTION) | ||
464 | { | ||
465 | hr = HRESULT_FROM_WIN32(er); | ||
466 | } | ||
467 | else | ||
468 | { | ||
469 | hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); | ||
470 | break; | ||
471 | } | ||
472 | |||
473 | ++cRetry; | ||
474 | ::Sleep(PIPE_WAIT_FOR_CONNECTION); | ||
475 | } | ||
476 | else | ||
477 | { | ||
478 | hr = HRESULT_FROM_WIN32(er); | ||
479 | break; | ||
480 | } | ||
481 | } | ||
482 | } while (HRESULT_FROM_WIN32(ERROR_PIPE_LISTENING) == hr); | ||
483 | ExitOnRootFailure(hr, "Failed to wait for child to connect to pipe."); | ||
484 | |||
485 | // Put the pipe back in blocking mode. | ||
486 | dwPipeState = PIPE_READMODE_BYTE | PIPE_WAIT; | ||
487 | if (!::SetNamedPipeHandleState(hPipe, &dwPipeState, NULL, NULL)) | ||
488 | { | ||
489 | ExitWithLastError(hr, "Failed to reset pipe to blocking."); | ||
490 | } | ||
491 | |||
492 | // Prove we are the one that created the elevated process by passing the secret. | ||
493 | if (!::WriteFile(hPipe, &cbSecret, sizeof(cbSecret), &cb, NULL)) | ||
494 | { | ||
495 | ExitWithLastError(hr, "Failed to write secret length to pipe."); | ||
496 | } | ||
497 | |||
498 | if (!::WriteFile(hPipe, wzSecret, cbSecret, &cb, NULL)) | ||
499 | { | ||
500 | ExitWithLastError(hr, "Failed to write secret to pipe."); | ||
501 | } | ||
502 | |||
503 | if (!::WriteFile(hPipe, &dwCurrentProcessId, sizeof(dwCurrentProcessId), &cb, NULL)) | ||
504 | { | ||
505 | ExitWithLastError(hr, "Failed to write our process id to pipe."); | ||
506 | } | ||
507 | |||
508 | // Wait until the elevated process responds that it is ready to go. | ||
509 | if (!::ReadFile(hPipe, &dwAck, sizeof(dwAck), &cb, NULL)) | ||
510 | { | ||
511 | ExitWithLastError(hr, "Failed to read ACK from pipe."); | ||
512 | } | ||
513 | |||
514 | // The ACK should match out expected child process id. | ||
515 | //if (pConnection->dwProcessId != dwAck) | ||
516 | //{ | ||
517 | // hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); | ||
518 | // ExitOnRootFailure(hr, "Incorrect ACK from elevated pipe: %u", dwAck); | ||
519 | //} | ||
520 | } | ||
521 | |||
522 | LExit: | ||
523 | return hr; | ||
524 | } | ||
525 | |||
526 | /******************************************************************* | ||
527 | PipeTerminateChildProcess - | ||
528 | |||
529 | *******************************************************************/ | ||
530 | extern "C" HRESULT PipeTerminateChildProcess( | ||
531 | __in BURN_PIPE_CONNECTION* pConnection, | ||
532 | __in DWORD dwParentExitCode, | ||
533 | __in BOOL fRestart | ||
534 | ) | ||
535 | { | ||
536 | HRESULT hr = S_OK; | ||
537 | BYTE* pbData = NULL; | ||
538 | SIZE_T cbData = 0; | ||
539 | |||
540 | // Prepare the exit message. | ||
541 | hr = BuffWriteNumber(&pbData, &cbData, dwParentExitCode); | ||
542 | ExitOnFailure(hr, "Failed to write exit code to message buffer."); | ||
543 | |||
544 | hr = BuffWriteNumber(&pbData, &cbData, fRestart); | ||
545 | ExitOnFailure(hr, "Failed to write restart to message buffer."); | ||
546 | |||
547 | // Send the messages. | ||
548 | if (INVALID_HANDLE_VALUE != pConnection->hCachePipe) | ||
549 | { | ||
550 | hr = WritePipeMessage(pConnection->hCachePipe, static_cast<DWORD>(BURN_PIPE_MESSAGE_TYPE_TERMINATE), pbData, cbData); | ||
551 | ExitOnFailure(hr, "Failed to post terminate message to child process cache thread."); | ||
552 | } | ||
553 | |||
554 | hr = WritePipeMessage(pConnection->hPipe, static_cast<DWORD>(BURN_PIPE_MESSAGE_TYPE_TERMINATE), pbData, cbData); | ||
555 | ExitOnFailure(hr, "Failed to post terminate message to child process."); | ||
556 | |||
557 | // If we were able to get a handle to the other process, wait for it to exit. | ||
558 | if (pConnection->hProcess) | ||
559 | { | ||
560 | if (WAIT_FAILED == ::WaitForSingleObject(pConnection->hProcess, PIPE_WAIT_FOR_CONNECTION * PIPE_RETRY_FOR_CONNECTION)) | ||
561 | { | ||
562 | ExitWithLastError(hr, "Failed to wait for child process exit."); | ||
563 | } | ||
564 | |||
565 | #ifdef DEBUG | ||
566 | DWORD dwChildExitCode = 0; | ||
567 | DWORD dwErrorCode = ERROR_SUCCESS; | ||
568 | BOOL fReturnedExitCode = ::GetExitCodeProcess(pConnection->hProcess, &dwChildExitCode); | ||
569 | if (!fReturnedExitCode) | ||
570 | { | ||
571 | dwErrorCode = ::GetLastError(); // if the other process is elevated and we are not, then we'll get ERROR_ACCESS_DENIED. | ||
572 | |||
573 | // The unit test use a thread instead of a process so try to get the exit code from | ||
574 | // the thread because we failed to get it from the process. | ||
575 | if (ERROR_INVALID_HANDLE == dwErrorCode) | ||
576 | { | ||
577 | fReturnedExitCode = ::GetExitCodeThread(pConnection->hProcess, &dwChildExitCode); | ||
578 | } | ||
579 | } | ||
580 | AssertSz((fReturnedExitCode && dwChildExitCode == dwParentExitCode) || | ||
581 | (!fReturnedExitCode && ERROR_ACCESS_DENIED == dwErrorCode), | ||
582 | "Child elevated process did not return matching exit code to parent process."); | ||
583 | #endif | ||
584 | } | ||
585 | |||
586 | LExit: | ||
587 | return hr; | ||
588 | } | ||
589 | |||
590 | /******************************************************************* | ||
591 | PipeChildConnect - Called from the child process to connect back | ||
592 | to the pipe provided by the parent process. | ||
593 | |||
594 | *******************************************************************/ | ||
595 | extern "C" HRESULT PipeChildConnect( | ||
596 | __in BURN_PIPE_CONNECTION* pConnection, | ||
597 | __in BOOL fConnectCachePipe | ||
598 | ) | ||
599 | { | ||
600 | Assert(pConnection->sczName); | ||
601 | Assert(pConnection->sczSecret); | ||
602 | Assert(!pConnection->hProcess); | ||
603 | Assert(INVALID_HANDLE_VALUE == pConnection->hPipe); | ||
604 | Assert(INVALID_HANDLE_VALUE == pConnection->hCachePipe); | ||
605 | |||
606 | HRESULT hr = S_OK; | ||
607 | LPWSTR sczPipeName = NULL; | ||
608 | |||
609 | // Try to connect to the parent. | ||
610 | hr = StrAllocFormatted(&sczPipeName, PIPE_NAME_FORMAT_STRING, pConnection->sczName); | ||
611 | ExitOnFailure(hr, "Failed to allocate name of parent pipe."); | ||
612 | |||
613 | hr = E_UNEXPECTED; | ||
614 | for (DWORD cRetry = 0; FAILED(hr) && cRetry < PIPE_RETRY_FOR_CONNECTION; ++cRetry) | ||
615 | { | ||
616 | pConnection->hPipe = ::CreateFileW(sczPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); | ||
617 | if (INVALID_HANDLE_VALUE == pConnection->hPipe) | ||
618 | { | ||
619 | hr = HRESULT_FROM_WIN32(::GetLastError()); | ||
620 | if (E_FILENOTFOUND == hr) // if the pipe isn't created, call it a timeout waiting on the parent. | ||
621 | { | ||
622 | hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); | ||
623 | } | ||
624 | |||
625 | ::Sleep(PIPE_WAIT_FOR_CONNECTION); | ||
626 | } | ||
627 | else // we have a connection, go with it. | ||
628 | { | ||
629 | hr = S_OK; | ||
630 | } | ||
631 | } | ||
632 | ExitOnRootFailure(hr, "Failed to open parent pipe: %ls", sczPipeName) | ||
633 | |||
634 | // Verify the parent and notify it that the child connected. | ||
635 | hr = ChildPipeConnected(pConnection->hPipe, pConnection->sczSecret, &pConnection->dwProcessId); | ||
636 | ExitOnFailure(hr, "Failed to verify parent pipe: %ls", sczPipeName); | ||
637 | |||
638 | if (fConnectCachePipe) | ||
639 | { | ||
640 | // Connect to the parent for the cache pipe. | ||
641 | hr = StrAllocFormatted(&sczPipeName, CACHE_PIPE_NAME_FORMAT_STRING, pConnection->sczName); | ||
642 | ExitOnFailure(hr, "Failed to allocate name of parent cache pipe."); | ||
643 | |||
644 | pConnection->hCachePipe = ::CreateFileW(sczPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); | ||
645 | if (INVALID_HANDLE_VALUE == pConnection->hCachePipe) | ||
646 | { | ||
647 | ExitWithLastError(hr, "Failed to open parent pipe: %ls", sczPipeName) | ||
648 | } | ||
649 | |||
650 | // Verify the parent and notify it that the child connected. | ||
651 | hr = ChildPipeConnected(pConnection->hCachePipe, pConnection->sczSecret, &pConnection->dwProcessId); | ||
652 | ExitOnFailure(hr, "Failed to verify parent pipe: %ls", sczPipeName); | ||
653 | } | ||
654 | |||
655 | pConnection->hProcess = ::OpenProcess(SYNCHRONIZE, FALSE, pConnection->dwProcessId); | ||
656 | ExitOnNullWithLastError(pConnection->hProcess, hr, "Failed to open companion process with PID: %u", pConnection->dwProcessId); | ||
657 | |||
658 | LExit: | ||
659 | ReleaseStr(sczPipeName); | ||
660 | |||
661 | return hr; | ||
662 | } | ||
663 | |||
664 | |||
665 | static HRESULT AllocatePipeMessage( | ||
666 | __in DWORD dwMessage, | ||
667 | __in_bcount_opt(cbData) LPVOID pvData, | ||
668 | __in DWORD cbData, | ||
669 | __out_bcount(cb) LPVOID* ppvMessage, | ||
670 | __out DWORD* cbMessage | ||
671 | ) | ||
672 | { | ||
673 | HRESULT hr = S_OK; | ||
674 | LPVOID pv = NULL; | ||
675 | DWORD cb = 0; | ||
676 | |||
677 | // If no data was provided, ensure the count of bytes is zero. | ||
678 | if (!pvData) | ||
679 | { | ||
680 | cbData = 0; | ||
681 | } | ||
682 | |||
683 | // Allocate the message. | ||
684 | cb = sizeof(dwMessage) + sizeof(cbData) + cbData; | ||
685 | pv = MemAlloc(cb, FALSE); | ||
686 | ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate memory for message."); | ||
687 | |||
688 | memcpy_s(pv, cb, &dwMessage, sizeof(dwMessage)); | ||
689 | memcpy_s(static_cast<BYTE*>(pv) + sizeof(dwMessage), cb - sizeof(dwMessage), &cbData, sizeof(cbData)); | ||
690 | if (cbData) | ||
691 | { | ||
692 | memcpy_s(static_cast<BYTE*>(pv) + sizeof(dwMessage) + sizeof(cbData), cb - sizeof(dwMessage) - sizeof(cbData), pvData, cbData); | ||
693 | } | ||
694 | |||
695 | *cbMessage = cb; | ||
696 | *ppvMessage = pv; | ||
697 | pv = NULL; | ||
698 | |||
699 | LExit: | ||
700 | ReleaseMem(pv); | ||
701 | return hr; | ||
702 | } | ||
703 | |||
704 | static void FreePipeMessage( | ||
705 | __in BURN_PIPE_MESSAGE *pMsg | ||
706 | ) | ||
707 | { | ||
708 | if (pMsg->fAllocatedData) | ||
709 | { | ||
710 | ReleaseNullMem(pMsg->pvData); | ||
711 | pMsg->fAllocatedData = FALSE; | ||
712 | } | ||
713 | } | ||
714 | |||
715 | static HRESULT WritePipeMessage( | ||
716 | __in HANDLE hPipe, | ||
717 | __in DWORD dwMessage, | ||
718 | __in_bcount_opt(cbData) LPVOID pvData, | ||
719 | __in DWORD cbData | ||
720 | ) | ||
721 | { | ||
722 | HRESULT hr = S_OK; | ||
723 | LPVOID pv = NULL; | ||
724 | DWORD cb = 0; | ||
725 | |||
726 | hr = AllocatePipeMessage(dwMessage, pvData, cbData, &pv, &cb); | ||
727 | ExitOnFailure(hr, "Failed to allocate message to write."); | ||
728 | |||
729 | // Write the message. | ||
730 | DWORD cbWrote = 0; | ||
731 | DWORD cbTotalWritten = 0; | ||
732 | while (cbTotalWritten < cb) | ||
733 | { | ||
734 | if (!::WriteFile(hPipe, pv, cb - cbTotalWritten, &cbWrote, NULL)) | ||
735 | { | ||
736 | ExitWithLastError(hr, "Failed to write message type to pipe."); | ||
737 | } | ||
738 | |||
739 | cbTotalWritten += cbWrote; | ||
740 | } | ||
741 | |||
742 | LExit: | ||
743 | ReleaseMem(pv); | ||
744 | return hr; | ||
745 | } | ||
746 | |||
747 | static HRESULT GetPipeMessage( | ||
748 | __in HANDLE hPipe, | ||
749 | __in BURN_PIPE_MESSAGE* pMsg | ||
750 | ) | ||
751 | { | ||
752 | HRESULT hr = S_OK; | ||
753 | DWORD rgdwMessageAndByteCount[2] = { }; | ||
754 | DWORD cb = 0; | ||
755 | DWORD cbRead = 0; | ||
756 | |||
757 | while (cbRead < sizeof(rgdwMessageAndByteCount)) | ||
758 | { | ||
759 | if (!::ReadFile(hPipe, reinterpret_cast<BYTE*>(rgdwMessageAndByteCount) + cbRead, sizeof(rgdwMessageAndByteCount) - cbRead, &cb, NULL)) | ||
760 | { | ||
761 | DWORD er = ::GetLastError(); | ||
762 | if (ERROR_MORE_DATA == er) | ||
763 | { | ||
764 | hr = S_OK; | ||
765 | } | ||
766 | else if (ERROR_BROKEN_PIPE == er) // parent process shut down, time to exit. | ||
767 | { | ||
768 | memset(rgdwMessageAndByteCount, 0, sizeof(rgdwMessageAndByteCount)); | ||
769 | hr = S_FALSE; | ||
770 | break; | ||
771 | } | ||
772 | else | ||
773 | { | ||
774 | hr = HRESULT_FROM_WIN32(er); | ||
775 | } | ||
776 | ExitOnRootFailure(hr, "Failed to read message from pipe."); | ||
777 | } | ||
778 | |||
779 | cbRead += cb; | ||
780 | } | ||
781 | |||
782 | pMsg->dwMessage = rgdwMessageAndByteCount[0]; | ||
783 | pMsg->cbData = rgdwMessageAndByteCount[1]; | ||
784 | if (pMsg->cbData) | ||
785 | { | ||
786 | pMsg->pvData = MemAlloc(pMsg->cbData, FALSE); | ||
787 | ExitOnNull(pMsg->pvData, hr, E_OUTOFMEMORY, "Failed to allocate data for message."); | ||
788 | |||
789 | if (!::ReadFile(hPipe, pMsg->pvData, pMsg->cbData, &cb, NULL)) | ||
790 | { | ||
791 | ExitWithLastError(hr, "Failed to read data for message."); | ||
792 | } | ||
793 | |||
794 | pMsg->fAllocatedData = TRUE; | ||
795 | } | ||
796 | |||
797 | LExit: | ||
798 | if (!pMsg->fAllocatedData && pMsg->pvData) | ||
799 | { | ||
800 | MemFree(pMsg->pvData); | ||
801 | } | ||
802 | |||
803 | return hr; | ||
804 | } | ||
805 | |||
806 | static HRESULT ChildPipeConnected( | ||
807 | __in HANDLE hPipe, | ||
808 | __in_z LPCWSTR wzSecret, | ||
809 | __inout DWORD* pdwProcessId | ||
810 | ) | ||
811 | { | ||
812 | HRESULT hr = S_OK; | ||
813 | LPWSTR sczVerificationSecret = NULL; | ||
814 | DWORD cbVerificationSecret = 0; | ||
815 | DWORD dwVerificationProcessId = 0; | ||
816 | DWORD dwRead = 0; | ||
817 | DWORD dwAck = ::GetCurrentProcessId(); // send our process id as the ACK. | ||
818 | DWORD cb = 0; | ||
819 | |||
820 | // Read the verification secret. | ||
821 | if (!::ReadFile(hPipe, &cbVerificationSecret, sizeof(cbVerificationSecret), &dwRead, NULL)) | ||
822 | { | ||
823 | ExitWithLastError(hr, "Failed to read size of verification secret from parent pipe."); | ||
824 | } | ||
825 | |||
826 | if (255 < cbVerificationSecret / sizeof(WCHAR)) | ||
827 | { | ||
828 | hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); | ||
829 | ExitOnRootFailure(hr, "Verification secret from parent is too big."); | ||
830 | } | ||
831 | |||
832 | hr = StrAlloc(&sczVerificationSecret, cbVerificationSecret / sizeof(WCHAR) + 1); | ||
833 | ExitOnFailure(hr, "Failed to allocate buffer for verification secret."); | ||
834 | |||
835 | if (!::ReadFile(hPipe, sczVerificationSecret, cbVerificationSecret, &dwRead, NULL)) | ||
836 | { | ||
837 | ExitWithLastError(hr, "Failed to read verification secret from parent pipe."); | ||
838 | } | ||
839 | |||
840 | // Verify the secrets match. | ||
841 | if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, 0, sczVerificationSecret, -1, wzSecret, -1)) | ||
842 | { | ||
843 | hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); | ||
844 | ExitOnRootFailure(hr, "Verification secret from parent does not match."); | ||
845 | } | ||
846 | |||
847 | // Read the verification process id. | ||
848 | if (!::ReadFile(hPipe, &dwVerificationProcessId, sizeof(dwVerificationProcessId), &dwRead, NULL)) | ||
849 | { | ||
850 | ExitWithLastError(hr, "Failed to read verification process id from parent pipe."); | ||
851 | } | ||
852 | |||
853 | // If a process id was not provided, we'll trust the process id from the parent. | ||
854 | if (*pdwProcessId == 0) | ||
855 | { | ||
856 | *pdwProcessId = dwVerificationProcessId; | ||
857 | } | ||
858 | else if (*pdwProcessId != dwVerificationProcessId) // verify the ids match. | ||
859 | { | ||
860 | hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); | ||
861 | ExitOnRootFailure(hr, "Verification process id from parent does not match."); | ||
862 | } | ||
863 | |||
864 | // All is well, tell the parent process. | ||
865 | if (!::WriteFile(hPipe, &dwAck, sizeof(dwAck), &cb, NULL)) | ||
866 | { | ||
867 | ExitWithLastError(hr, "Failed to inform parent process that child is running."); | ||
868 | } | ||
869 | |||
870 | LExit: | ||
871 | ReleaseStr(sczVerificationSecret); | ||
872 | return hr; | ||
873 | } | ||