main
cpp 547 lines 19.1 KB
Raw
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 LPCWSTR CACHE_PIPE_NAME_FORMAT_STRING = L"%ls.Cache";
6 static const LPCWSTR LOGGING_PIPE_NAME_FORMAT_STRING = L"%ls.Log";
7
8 static HRESULT ChildPipeConnected(
9 __in HANDLE hPipe,
10 __in_z LPCWSTR wzSecret,
11 __inout DWORD* pdwProcessId
12 );
13
14
15 /*******************************************************************
16 BurnPipeConnectionInitialize - initialize pipe connection data.
17
18 *******************************************************************/
19 void BurnPipeConnectionInitialize(
20 __in BURN_PIPE_CONNECTION* pConnection
21 )
22 {
23 memset(pConnection, 0, sizeof(BURN_PIPE_CONNECTION));
24 pConnection->hPipe = INVALID_HANDLE_VALUE;
25 pConnection->hCachePipe = INVALID_HANDLE_VALUE;
26 pConnection->hLoggingPipe = INVALID_HANDLE_VALUE;
27 }
28
29 /*******************************************************************
30 BurnPipeConnectionUninitialize - free data in a pipe connection.
31
32 *******************************************************************/
33 void BurnPipeConnectionUninitialize(
34 __in BURN_PIPE_CONNECTION* pConnection
35 )
36 {
37 ReleasePipeHandle(pConnection->hLoggingPipe);
38 ReleasePipeHandle(pConnection->hCachePipe);
39 ReleasePipeHandle(pConnection->hPipe);
40 ReleaseHandle(pConnection->hProcess);
41 ReleaseStr(pConnection->sczSecret);
42 ReleaseStr(pConnection->sczName);
43
44 BurnPipeConnectionInitialize(pConnection);
45 }
46
47 /*******************************************************************
48 BurnPipeSendMessage -
49
50 *******************************************************************/
51 extern "C" HRESULT BurnPipeSendMessage(
52 __in HANDLE hPipe,
53 __in DWORD dwMessage,
54 __in_bcount_opt(cbData) LPVOID pvData,
55 __in SIZE_T cbData,
56 __in_opt PFN_PIPE_MESSAGE_CALLBACK pfnCallback,
57 __in_opt LPVOID pvContext,
58 __out DWORD* pdwResult
59 )
60 {
61 HRESULT hr = S_OK;
62 BURN_PIPE_RESULT result = { };
63
64 hr = PipeWriteMessage(hPipe, dwMessage, pvData, cbData);
65 ExitOnFailure(hr, "Failed to write send message to pipe.");
66
67 hr = BurnPipePumpMessages(hPipe, pfnCallback, pvContext, &result);
68 ExitOnFailure(hr, "Failed to pump messages during send message to pipe.");
69
70 *pdwResult = result.dwResult;
71
72 LExit:
73 return hr;
74 }
75
76 /*******************************************************************
77 BurnPipePumpMessages -
78
79 *******************************************************************/
80 extern "C" HRESULT BurnPipePumpMessages(
81 __in HANDLE hPipe,
82 __in_opt PFN_PIPE_MESSAGE_CALLBACK pfnCallback,
83 __in_opt LPVOID pvContext,
84 __in BURN_PIPE_RESULT* pResult
85 )
86 {
87 HRESULT hr = S_OK;
88 PIPE_MESSAGE msg = { };
89 SIZE_T iData = 0;
90 LPSTR sczMessage = NULL;
91 DWORD dwResult = 0;
92
93 // Pump messages from child process.
94 while (S_OK == (hr = PipeReadMessage(hPipe, &msg)))
95 {
96 switch (msg.dwMessageType)
97 {
98 case BURN_PIPE_MESSAGE_TYPE_LOG:
99 iData = 0;
100
101 hr = BuffReadStringAnsi((BYTE*)msg.pvData, msg.cbData, &iData, &sczMessage);
102 ExitOnFailure(hr, "Failed to read log message.");
103
104 hr = LogStringWorkRaw(sczMessage);
105 ExitOnFailure(hr, "Failed to write log message:'%hs'.", sczMessage);
106
107 dwResult = static_cast<DWORD>(hr);
108 break;
109
110 case BURN_PIPE_MESSAGE_TYPE_COMPLETE:
111 if (!msg.pvData || sizeof(DWORD) != msg.cbData)
112 {
113 hr = E_INVALIDARG;
114 ExitOnRootFailure(hr, "No status returned to BurnPipePumpMessages()");
115 }
116
117 pResult->dwResult = *static_cast<DWORD*>(msg.pvData);
118 ExitFunction1(hr = S_OK); // exit loop.
119
120 case BURN_PIPE_MESSAGE_TYPE_TERMINATE:
121 iData = 0;
122
123 hr = BuffReadNumber(static_cast<BYTE*>(msg.pvData), msg.cbData, &iData, &pResult->dwResult);
124 ExitOnFailure(hr, "Failed to read returned result to BurnPipePumpMessages()");
125
126 if (sizeof(DWORD) * 2 == msg.cbData)
127 {
128 hr = BuffReadNumber(static_cast<BYTE*>(msg.pvData), msg.cbData, &iData, (DWORD*)&pResult->fRestart);
129 ExitOnFailure(hr, "Failed to read returned restart to BurnPipePumpMessages()");
130 }
131
132 ExitFunction1(hr = S_OK); // exit loop.
133
134 default:
135 if (pfnCallback)
136 {
137 hr = pfnCallback(&msg, pvContext, &dwResult);
138 }
139 else
140 {
141 hr = E_INVALIDARG;
142 }
143 ExitOnFailure(hr, "Failed to process message: %u", msg.dwMessageType);
144 break;
145 }
146
147 // post result
148 hr = PipeWriteMessage(hPipe, static_cast<DWORD>(BURN_PIPE_MESSAGE_TYPE_COMPLETE), &dwResult, sizeof(dwResult));
149 ExitOnFailure(hr, "Failed to post result to child process.");
150
151 ReleasePipeMessage(&msg);
152 }
153 ExitOnFailure(hr, "Failed to get message over pipe");
154
155 if (S_FALSE == hr)
156 {
157 hr = S_OK;
158 }
159
160 LExit:
161 ReleaseStr(sczMessage);
162 ReleasePipeMessage(&msg);
163
164 return hr;
165 }
166
167 /*******************************************************************
168 BurnPipeCreateNameAndSecret -
169
170 *******************************************************************/
171 extern "C" HRESULT BurnPipeCreateNameAndSecret(
172 __out_z LPWSTR *psczConnectionName,
173 __out_z LPWSTR *psczSecret
174 )
175 {
176 HRESULT hr = S_OK;
177 WCHAR wzGuid[GUID_STRING_LENGTH];
178 LPWSTR sczConnectionName = NULL;
179 LPWSTR sczSecret = NULL;
180
181 // Create the unique pipe name.
182 hr = GuidFixedCreate(wzGuid);
183 ExitOnRootFailure(hr, "Failed to create pipe guid.");
184
185 hr = StrAllocFormatted(&sczConnectionName, L"BurnPipe.%s", wzGuid);
186 ExitOnFailure(hr, "Failed to allocate pipe name.");
187
188 // Create the unique client secret.
189 hr = GuidFixedCreate(wzGuid);
190 ExitOnRootFailure(hr, "Failed to create pipe secret.");
191
192 hr = StrAllocString(&sczSecret, wzGuid, 0);
193 ExitOnFailure(hr, "Failed to allocate pipe secret.");
194
195 *psczConnectionName = sczConnectionName;
196 sczConnectionName = NULL;
197 *psczSecret = sczSecret;
198 sczSecret = NULL;
199
200 LExit:
201 ReleaseStr(sczSecret);
202 ReleaseStr(sczConnectionName);
203
204 return hr;
205 }
206
207 /*******************************************************************
208 BurnPipeCreatePipes - create the pipes and event to signal child process.
209
210 *******************************************************************/
211 extern "C" HRESULT BurnPipeCreatePipes(
212 __in BURN_PIPE_CONNECTION* pConnection,
213 __in BOOL fCompanion
214 )
215 {
216 Assert(pConnection->sczName);
217 Assert(INVALID_HANDLE_VALUE == pConnection->hPipe);
218 Assert(INVALID_HANDLE_VALUE == pConnection->hCachePipe);
219 Assert(INVALID_HANDLE_VALUE == pConnection->hLoggingPipe);
220
221 HRESULT hr = S_OK;
222 PSECURITY_DESCRIPTOR psd = NULL;
223 SECURITY_ATTRIBUTES sa = { };
224 LPWSTR sczPipeName = NULL;
225 HANDLE hPipe = INVALID_HANDLE_VALUE;
226 HANDLE hCachePipe = INVALID_HANDLE_VALUE;
227 HANDLE hLoggingPipe = INVALID_HANDLE_VALUE;
228
229 // Only grant special rights when the pipe is being used for "embedded" scenarios.
230 if (!fCompanion)
231 {
232 // Create the security descriptor that grants read/write/sync access to Everyone.
233 // TODO: consider locking down "WD" to LogonIds (logon session)
234 LPCWSTR wzSddl = L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW0x00100000;;;WD)";
235 if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(wzSddl, SDDL_REVISION_1, &psd, NULL))
236 {
237 ExitWithLastError(hr, "Failed to create the security descriptor for the connection event and pipe.");
238 }
239
240 sa.nLength = sizeof(sa);
241 sa.lpSecurityDescriptor = psd;
242 sa.bInheritHandle = FALSE;
243 }
244
245 // Create the pipe.
246 hr = PipeCreate(pConnection->sczName, psd ? &sa : NULL, &hPipe);
247 ExitOnFailure(hr, "Failed to create pipe: %ls", pConnection->sczName);
248
249 if (fCompanion)
250 {
251 // Create the cache pipe.
252 hr = StrAllocFormatted(&sczPipeName, CACHE_PIPE_NAME_FORMAT_STRING, pConnection->sczName);
253 ExitOnFailure(hr, "Failed to allocate full name of cache pipe: %ls", pConnection->sczName);
254
255 hr = PipeCreate(sczPipeName, NULL, &hCachePipe);
256 ExitOnFailure(hr, "Failed to create cache pipe: %ls", sczPipeName);
257
258 // Create the logging pipe.
259 hr = StrAllocFormatted(&sczPipeName, LOGGING_PIPE_NAME_FORMAT_STRING, pConnection->sczName);
260 ExitOnFailure(hr, "Failed to allocate full name of logging pipe: %ls", pConnection->sczName);
261
262 hr = PipeCreate(sczPipeName, NULL, &hLoggingPipe);
263 ExitOnFailure(hr, "Failed to create logging pipe: %ls", sczPipeName);
264 }
265
266 pConnection->hLoggingPipe = hLoggingPipe;
267 hLoggingPipe = INVALID_HANDLE_VALUE;
268
269 pConnection->hCachePipe = hCachePipe;
270 hCachePipe = INVALID_HANDLE_VALUE;
271
272 pConnection->hPipe = hPipe;
273 hPipe = INVALID_HANDLE_VALUE;
274
275 LExit:
276 ReleasePipeHandle(hLoggingPipe);
277 ReleasePipeHandle(hCachePipe);
278 ReleasePipeHandle(hPipe);
279 ReleaseStr(sczPipeName);
280
281 if (psd)
282 {
283 ::LocalFree(psd);
284 }
285
286 return hr;
287 }
288
289 /*******************************************************************
290 BurnPipeWaitForChildConnect -
291
292 *******************************************************************/
293 extern "C" HRESULT BurnPipeWaitForChildConnect(
294 __in BURN_PIPE_CONNECTION* pConnection
295 )
296 {
297 HRESULT hr = S_OK;
298 HANDLE hPipes[3] = { pConnection->hPipe, pConnection->hCachePipe, pConnection->hLoggingPipe};
299 LPCWSTR wzSecret = pConnection->sczSecret;
300 DWORD cbSecret = lstrlenW(wzSecret) * sizeof(WCHAR);
301 DWORD dwCurrentProcessId = ::GetCurrentProcessId();
302 DWORD dwAck = 0;
303
304 for (DWORD i = 0; i < countof(hPipes) && INVALID_HANDLE_VALUE != hPipes[i]; ++i)
305 {
306 HANDLE hPipe = hPipes[i];
307
308 hr = PipeServerWaitForClientConnect(pConnection->hProcess, hPipe);
309 ExitOnRootFailure(hr, "Failed to wait for child to connect to pipe.");
310
311 // Prove we are the one that created the elevated process by passing the secret.
312 hr = FileWriteHandle(hPipe, reinterpret_cast<LPCBYTE>(&cbSecret), sizeof(cbSecret));
313 ExitOnFailure(hr, "Failed to write secret length to pipe.");
314
315 hr = FileWriteHandle(hPipe, reinterpret_cast<LPCBYTE>(wzSecret), cbSecret);
316 ExitOnFailure(hr, "Failed to write secret to pipe.");
317
318 hr = FileWriteHandle(hPipe, reinterpret_cast<LPCBYTE>(&dwCurrentProcessId), sizeof(dwCurrentProcessId));
319 ExitOnFailure(hr, "Failed to write our process id to pipe.");
320
321 // Wait until the elevated process responds that it is ready to go.
322 hr = FileReadHandle(hPipe, reinterpret_cast<LPBYTE>(&dwAck), sizeof(dwAck));
323 ExitOnFailure(hr, "Failed to read ACK from pipe.");
324
325 // The ACK should match out expected child process id.
326 //if (pConnection->dwProcessId != dwAck)
327 //{
328 // hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
329 // ExitOnRootFailure(hr, "Incorrect ACK from elevated pipe: %u", dwAck);
330 //}
331 }
332
333 LExit:
334 return hr;
335 }
336
337 /*******************************************************************
338 BurnPipeTerminateLoggingPipe -
339
340 *******************************************************************/
341 extern "C" HRESULT BurnPipeTerminateLoggingPipe(
342 __in HANDLE hLoggingPipe,
343 __in DWORD dwParentExitCode
344 )
345 {
346 HRESULT hr = S_OK;
347 BYTE* pbData = NULL;
348 SIZE_T cbData = 0;
349
350 // Prepare the exit message.
351 hr = BuffWriteNumber(&pbData, &cbData, dwParentExitCode);
352 ExitOnFailure(hr, "Failed to write exit code to message buffer.");
353
354 hr = PipeWriteMessage(hLoggingPipe, static_cast<DWORD>(BURN_PIPE_MESSAGE_TYPE_COMPLETE), pbData, cbData);
355 ExitOnFailure(hr, "Failed to post complete message to logging pipe.");
356
357 LExit:
358 ReleaseMem(pbData);
359
360 return hr;
361 }
362
363 /*******************************************************************
364 BurnPipeTerminateChildProcess -
365
366 *******************************************************************/
367 extern "C" HRESULT BurnPipeTerminateChildProcess(
368 __in BURN_PIPE_CONNECTION* pConnection,
369 __in DWORD dwParentExitCode,
370 __in BOOL fRestart
371 )
372 {
373 HRESULT hr = S_OK;
374 BYTE* pbData = NULL;
375 SIZE_T cbData = 0;
376 BOOL fTimedOut = FALSE;
377
378 // Prepare the exit message.
379 hr = BuffWriteNumber(&pbData, &cbData, dwParentExitCode);
380 ExitOnFailure(hr, "Failed to write exit code to message buffer.");
381
382 hr = BuffWriteNumber(&pbData, &cbData, fRestart);
383 ExitOnFailure(hr, "Failed to write restart to message buffer.");
384
385 // Send the messages.
386 if (INVALID_HANDLE_VALUE != pConnection->hCachePipe)
387 {
388 hr = PipeWriteMessage(pConnection->hCachePipe, static_cast<DWORD>(BURN_PIPE_MESSAGE_TYPE_TERMINATE), pbData, cbData);
389 ExitOnFailure(hr, "Failed to post terminate message to child process cache thread.");
390 }
391
392 hr = PipeWriteMessage(pConnection->hPipe, static_cast<DWORD>(BURN_PIPE_MESSAGE_TYPE_TERMINATE), pbData, cbData);
393 ExitOnFailure(hr, "Failed to post terminate message to child process.");
394
395 // If we were able to get a handle to the other process, wait for it to exit.
396 if (pConnection->hProcess)
397 {
398 hr = AppWaitForSingleObject(pConnection->hProcess, PIPE_WAIT_FOR_CONNECTION * PIPE_RETRY_FOR_CONNECTION);
399 ExitOnWaitObjectFailure(hr, fTimedOut, "Failed to wait for child process exit.");
400
401 AssertSz(!fTimedOut, "Timed out while waiting for child process to exit.");
402 }
403
404 #ifdef DEBUG
405 if (pConnection->hProcess && !fTimedOut)
406 {
407 DWORD dwChildExitCode = 0;
408 HRESULT hrDebug = S_OK;
409
410 hrDebug = CoreWaitForProcCompletion(pConnection->hProcess, 0, &dwChildExitCode);
411 if (E_ACCESSDENIED != hrDebug && FAILED(hrDebug)) // if the other process is elevated and we are not, then we'll get ERROR_ACCESS_DENIED.
412 {
413 TraceError(hrDebug, "Failed to wait for child process completion.");
414 }
415
416 AssertSz(E_ACCESSDENIED == hrDebug || dwChildExitCode == dwParentExitCode,
417 "Child elevated process did not return matching exit code to parent process.");
418 }
419 #endif
420
421 LExit:
422 ReleaseMem(pbData);
423
424 return hr;
425 }
426
427 /*******************************************************************
428 BurnPipeChildConnect - Called from the child process to connect back
429 to the pipe provided by the parent process.
430
431 *******************************************************************/
432 extern "C" HRESULT BurnPipeChildConnect(
433 __in BURN_PIPE_CONNECTION* pConnection,
434 __in BOOL fCompanion
435 )
436 {
437 Assert(pConnection->sczName);
438 Assert(pConnection->sczSecret);
439 Assert(!pConnection->hProcess);
440 Assert(INVALID_HANDLE_VALUE == pConnection->hPipe);
441 Assert(INVALID_HANDLE_VALUE == pConnection->hCachePipe);
442 Assert(INVALID_HANDLE_VALUE == pConnection->hLoggingPipe);
443
444 HRESULT hr = S_OK;
445 LPWSTR sczPipeName = NULL;
446
447 // Try to connect to the parent.
448 hr = PipeClientConnect(pConnection->sczName, &pConnection->hPipe);
449 ExitOnRootFailure(hr, "Failed to open parent pipe: %ls", sczPipeName)
450
451 // Verify the parent and notify it that the child connected.
452 hr = ChildPipeConnected(pConnection->hPipe, pConnection->sczSecret, &pConnection->dwProcessId);
453 ExitOnFailure(hr, "Failed to verify parent pipe: %ls", sczPipeName);
454
455 if (fCompanion)
456 {
457 // Connect to the parent for the cache pipe.
458 hr = StrAllocFormatted(&sczPipeName, CACHE_PIPE_NAME_FORMAT_STRING, pConnection->sczName);
459 ExitOnFailure(hr, "Failed to allocate name of parent cache pipe.");
460
461 hr = PipeClientConnect(sczPipeName, &pConnection->hCachePipe);
462 ExitOnFailure(hr, "Failed to open parent cache pipe: %ls", sczPipeName)
463
464 // Verify the parent and notify it that the child connected.
465 hr = ChildPipeConnected(pConnection->hCachePipe, pConnection->sczSecret, &pConnection->dwProcessId);
466 ExitOnFailure(hr, "Failed to verify parent cache pipe: %ls", sczPipeName);
467
468 // Connect to the parent for the logging pipe.
469 hr = StrAllocFormatted(&sczPipeName, LOGGING_PIPE_NAME_FORMAT_STRING, pConnection->sczName);
470 ExitOnFailure(hr, "Failed to allocate name of parent logging pipe.");
471
472 hr = PipeClientConnect(sczPipeName, &pConnection->hLoggingPipe);
473 ExitOnFailure(hr, "Failed to open parent cache pipe: %ls", sczPipeName)
474
475 // Verify the parent and notify it that the child connected.
476 hr = ChildPipeConnected(pConnection->hLoggingPipe, pConnection->sczSecret, &pConnection->dwProcessId);
477 ExitOnFailure(hr, "Failed to verify parent logging pipe: %ls", sczPipeName);
478 }
479
480 pConnection->hProcess = ::OpenProcess(SYNCHRONIZE, FALSE, pConnection->dwProcessId);
481 ExitOnNullWithLastError(pConnection->hProcess, hr, "Failed to open companion process with PID: %u", pConnection->dwProcessId);
482
483 LExit:
484 ReleaseStr(sczPipeName);
485
486 return hr;
487 }
488
489 static HRESULT ChildPipeConnected(
490 __in HANDLE hPipe,
491 __in_z LPCWSTR wzSecret,
492 __inout DWORD* pdwProcessId
493 )
494 {
495 HRESULT hr = S_OK;
496 LPWSTR sczVerificationSecret = NULL;
497 DWORD cbVerificationSecret = 0;
498 DWORD dwVerificationProcessId = 0;
499 DWORD dwAck = ::GetCurrentProcessId(); // send our process id as the ACK.
500
501 // Read the verification secret.
502 hr = FileReadHandle(hPipe, reinterpret_cast<LPBYTE>(&cbVerificationSecret), sizeof(cbVerificationSecret));
503 ExitOnFailure(hr, "Failed to read size of verification secret from parent pipe.");
504
505 if (255 < cbVerificationSecret / sizeof(WCHAR))
506 {
507 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
508 ExitOnRootFailure(hr, "Verification secret from parent is too big.");
509 }
510
511 hr = StrAlloc(&sczVerificationSecret, cbVerificationSecret / sizeof(WCHAR) + 1);
512 ExitOnFailure(hr, "Failed to allocate buffer for verification secret.");
513
514 FileReadHandle(hPipe, reinterpret_cast<LPBYTE>(sczVerificationSecret), cbVerificationSecret);
515 ExitOnFailure(hr, "Failed to read verification secret from parent pipe.");
516
517 // Verify the secrets match.
518 if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, 0, sczVerificationSecret, -1, wzSecret, -1))
519 {
520 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
521 ExitOnRootFailure(hr, "Verification secret from parent does not match.");
522 }
523
524 // Read the verification process id.
525 hr = FileReadHandle(hPipe, reinterpret_cast<LPBYTE>(&dwVerificationProcessId), sizeof(dwVerificationProcessId));
526 ExitOnFailure(hr, "Failed to read verification process id from parent pipe.");
527
528 // If a process id was not provided, we'll trust the process id from the parent.
529 if (*pdwProcessId == 0)
530 {
531 *pdwProcessId = dwVerificationProcessId;
532 }
533 else if (*pdwProcessId != dwVerificationProcessId) // verify the ids match.
534 {
535 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
536 ExitOnRootFailure(hr, "Verification process id from parent does not match.");
537 }
538
539 // All is well, tell the parent process.
540 // TODO: consider sending BURN_PROTOCOL_VERSION as a way to verify compatibility.
541 hr = FileWriteHandle(hPipe, reinterpret_cast<LPCBYTE>(&dwAck), sizeof(dwAck));
542 ExitOnFailure(hr, "Failed to inform parent process that child is running.");
543
544 LExit:
545 ReleaseStr(sczVerificationSecret);
546 return hr;
547 }