[WINSRV] Implement sending the hard error balloon package to explorer
[reactos.git] / win32ss / user / winsrv / usersrv / harderror.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS User API Server DLL
4 * FILE: win32ss/user/winsrv/usersrv/harderror.c
5 * PURPOSE: Hard errors
6 * PROGRAMMERS: Dmitry Philippov (shedon@mail.ru)
7 * Timo Kreuzer (timo.kreuzer@reactos.org)
8 */
9
10 /* INCLUDES *******************************************************************/
11
12 #include "usersrv.h"
13
14 #include <ndk/mmfuncs.h>
15 #include <pseh/pseh2.h>
16 #include <strsafe.h>
17
18 #define NDEBUG
19 #include <debug.h>
20
21 #define IDTRYAGAIN 10
22 #define IDCONTINUE 11
23
24 /* FUNCTIONS ******************************************************************/
25
26 static
27 NTSTATUS
28 UserpGetClientFileName(
29 OUT PUNICODE_STRING ClientFileNameU,
30 HANDLE hProcess)
31 {
32 PLIST_ENTRY ModuleListHead;
33 PLIST_ENTRY Entry;
34 PLDR_DATA_TABLE_ENTRY Module;
35 PPEB_LDR_DATA Ldr;
36 PROCESS_BASIC_INFORMATION ClientBasicInfo;
37 LDR_DATA_TABLE_ENTRY ModuleData;
38 PVOID ClientDllBase;
39 NTSTATUS Status;
40 PPEB Peb;
41
42 /* Initialize string */
43 ClientFileNameU->MaximumLength = 0;
44 ClientFileNameU->Length = 0;
45 ClientFileNameU->Buffer = NULL;
46
47 /* Query process information */
48 Status = NtQueryInformationProcess(hProcess,
49 ProcessBasicInformation,
50 &ClientBasicInfo,
51 sizeof(ClientBasicInfo),
52 NULL);
53 if (!NT_SUCCESS(Status)) return Status;
54
55 Peb = ClientBasicInfo.PebBaseAddress;
56 if (!Peb) return STATUS_UNSUCCESSFUL;
57
58 Status = NtReadVirtualMemory(hProcess, &Peb->Ldr, &Ldr, sizeof(Ldr), NULL);
59 if (!NT_SUCCESS(Status)) return Status;
60
61 ModuleListHead = &Ldr->InLoadOrderModuleList;
62 Status = NtReadVirtualMemory(hProcess,
63 &ModuleListHead->Flink,
64 &Entry,
65 sizeof(Entry),
66 NULL);
67 if (!NT_SUCCESS(Status)) return Status;
68
69 if (Entry == ModuleListHead) return STATUS_UNSUCCESSFUL;
70
71 Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
72
73 Status = NtReadVirtualMemory(hProcess,
74 Module,
75 &ModuleData,
76 sizeof(ModuleData),
77 NULL);
78 if (!NT_SUCCESS(Status)) return Status;
79
80 Status = NtReadVirtualMemory(hProcess,
81 &Peb->ImageBaseAddress,
82 &ClientDllBase,
83 sizeof(ClientDllBase),
84 NULL);
85 if (!NT_SUCCESS(Status)) return Status;
86
87 if (ClientDllBase != ModuleData.DllBase) return STATUS_UNSUCCESSFUL;
88
89 ClientFileNameU->MaximumLength = ModuleData.BaseDllName.MaximumLength;
90 ClientFileNameU->Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
91 HEAP_ZERO_MEMORY,
92 ClientFileNameU->MaximumLength);
93
94 Status = NtReadVirtualMemory(hProcess,
95 ModuleData.BaseDllName.Buffer,
96 ClientFileNameU->Buffer,
97 ClientFileNameU->MaximumLength,
98 NULL);
99 if (!NT_SUCCESS(Status))
100 {
101 RtlFreeHeap(RtlGetProcessHeap(), 0, ClientFileNameU->Buffer);
102 ClientFileNameU->Buffer = NULL;
103 ClientFileNameU->MaximumLength = 0;
104 return Status;
105 }
106
107 ClientFileNameU->Length = wcslen(ClientFileNameU->Buffer) * sizeof(WCHAR);
108 DPRINT("ClientFileNameU=\'%wZ\'\n", &ClientFileNameU);
109
110 return STATUS_SUCCESS;
111 }
112
113 static
114 VOID
115 UserpFreeStringParameters(
116 IN OUT PULONG_PTR Parameters,
117 IN PHARDERROR_MSG HardErrorMessage)
118 {
119 ULONG nParam;
120
121 /* Loop all parameters */
122 for (nParam = 0; nParam < HardErrorMessage->NumberOfParameters; nParam++)
123 {
124 /* Check if the current parameter is a string */
125 if (HardErrorMessage->UnicodeStringParameterMask & (1 << nParam) && Parameters[nParam])
126 {
127 /* Free the string buffer */
128 RtlFreeHeap(RtlGetProcessHeap(), 0, (PVOID)Parameters[nParam]);
129 }
130 }
131 }
132
133 static
134 NTSTATUS
135 UserpCaptureStringParameters(
136 OUT PULONG_PTR Parameters,
137 OUT PULONG SizeOfAllUnicodeStrings,
138 IN PHARDERROR_MSG HardErrorMessage,
139 HANDLE hProcess)
140 {
141 ULONG nParam, Size = 0;
142 NTSTATUS Status = STATUS_SUCCESS;
143 UNICODE_STRING TempStringU, ParamStringU;
144 ANSI_STRING TempStringA;
145
146 if (SizeOfAllUnicodeStrings)
147 *SizeOfAllUnicodeStrings = 0;
148
149 /* Read all strings from client space */
150 for (nParam = 0; nParam < HardErrorMessage->NumberOfParameters; nParam++)
151 {
152 Parameters[nParam] = 0;
153
154 /* Check if the current parameter is a unicode string */
155 if (HardErrorMessage->UnicodeStringParameterMask & (1 << nParam))
156 {
157 /* Read the UNICODE_STRING from the process memory */
158 Status = NtReadVirtualMemory(hProcess,
159 (PVOID)HardErrorMessage->Parameters[nParam],
160 &ParamStringU,
161 sizeof(ParamStringU),
162 NULL);
163 if (!NT_SUCCESS(Status))
164 break;
165
166 /* Allocate a buffer for the string */
167 TempStringU.MaximumLength = ParamStringU.Length;
168 TempStringU.Length = ParamStringU.Length;
169 TempStringU.Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
170 HEAP_ZERO_MEMORY,
171 TempStringU.MaximumLength);
172
173 if (!TempStringU.Buffer)
174 {
175 DPRINT1("Cannot allocate memory %u\n", TempStringU.MaximumLength);
176 Status = STATUS_NO_MEMORY;
177 }
178
179 /* Read the string buffer from the process memory */
180 Status = NtReadVirtualMemory(hProcess,
181 ParamStringU.Buffer,
182 TempStringU.Buffer,
183 ParamStringU.Length,
184 NULL);
185 if (!NT_SUCCESS(Status))
186 {
187 DPRINT1("NtReadVirtualMemory failed with code: %lx\n", Status);
188 RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringU.Buffer);
189 break;
190 }
191
192 DPRINT("ParamString=\'%wZ\'\n", &TempStringU);
193
194 /* Allocate a buffer for converted to ANSI string */
195 TempStringA.MaximumLength = RtlUnicodeStringToAnsiSize(&TempStringU);
196 TempStringA.Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
197 HEAP_ZERO_MEMORY,
198 TempStringA.MaximumLength);
199
200 if (!TempStringA.Buffer)
201 {
202 DPRINT1("Cannot allocate memory %u\n", TempStringA.MaximumLength);
203 RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringU.Buffer);
204 Status = STATUS_NO_MEMORY;
205 break;
206 }
207
208 /* Convert string to ANSI and free temporary buffer */
209 Status = RtlUnicodeStringToAnsiString(&TempStringA, &TempStringU, FALSE);
210 RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringU.Buffer);
211 if (!NT_SUCCESS(Status))
212 {
213 RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringA.Buffer);
214 break;
215 }
216
217 /* Note: RtlUnicodeStringToAnsiString returns NULL terminated string */
218 Parameters[nParam] = (ULONG_PTR)TempStringA.Buffer;
219 Size += TempStringU.Length;
220 }
221 else
222 {
223 /* It's not a unicode string */
224 Parameters[nParam] = HardErrorMessage->Parameters[nParam];
225 }
226 }
227
228 if (!NT_SUCCESS(Status))
229 {
230 UserpFreeStringParameters(Parameters, HardErrorMessage);
231 return Status;
232 }
233
234 if (SizeOfAllUnicodeStrings)
235 *SizeOfAllUnicodeStrings = Size;
236
237 return Status;
238 }
239
240 static
241 NTSTATUS
242 UserpFormatMessages(
243 OUT PUNICODE_STRING TextStringU,
244 OUT PUNICODE_STRING CaptionStringU,
245 IN PULONG_PTR Parameters,
246 IN ULONG SizeOfStrings,
247 IN PHARDERROR_MSG Message,
248 IN HANDLE hProcess)
249 {
250 NTSTATUS Status;
251 UNICODE_STRING FileNameU, TempStringU, FormatU;
252 ANSI_STRING FormatA;
253 PMESSAGE_RESOURCE_ENTRY MessageResource;
254 PWSTR FormatString;
255 ULONG Size, ExceptionCode;
256
257 /* Get the file name of the client process */
258 UserpGetClientFileName(&FileNameU, hProcess);
259
260 /* Check if we have a file name */
261 if (!FileNameU.Buffer)
262 {
263 /* No, use system */
264 RtlInitUnicodeString(&FileNameU, L"System");
265 }
266
267 /* Get text string of the error code */
268 Status = RtlFindMessage(GetModuleHandleW(L"ntdll"),
269 (ULONG_PTR)RT_MESSAGETABLE,
270 LANG_NEUTRAL,
271 Message->Status,
272 &MessageResource);
273 if (NT_SUCCESS(Status))
274 {
275 if (MessageResource->Flags)
276 {
277 RtlInitUnicodeString(&FormatU, (PWSTR)MessageResource->Text);
278 FormatA.Buffer = NULL;
279 }
280 else
281 {
282 RtlInitAnsiString(&FormatA, (PCHAR)MessageResource->Text);
283 RtlAnsiStringToUnicodeString(&FormatU, &FormatA, TRUE);
284 }
285 }
286 else
287 {
288 /* Fall back to hardcoded value */
289 RtlInitUnicodeString(&FormatU, L"Unknown Hard Error");
290 FormatA.Buffer = NULL;
291 }
292
293 FormatString = FormatU.Buffer;
294
295 /* Check whether a caption exists */
296 if (FormatString[0] == L'{')
297 {
298 /* Set caption start */
299 TempStringU.Buffer = ++FormatString;
300
301 /* Get size of the caption */
302 for (Size = 0; *FormatString != 0 && *FormatString != L'}'; Size++)
303 FormatString++;
304
305 /* Skip '}', '\r', '\n' */
306 FormatString += 3;
307
308 TempStringU.Length = Size * sizeof(WCHAR);
309 TempStringU.MaximumLength = TempStringU.Length;
310 }
311 else
312 {
313 /* FIXME: Set string based on severity */
314 RtlInitUnicodeString(&TempStringU, L"Application Error");
315 }
316
317 /* Calculate buffer length for the caption */
318 CaptionStringU->MaximumLength = FileNameU.Length + TempStringU.Length +
319 4 * sizeof(WCHAR);
320
321 /* Allocate a buffer for the caption */
322 CaptionStringU->Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
323 HEAP_ZERO_MEMORY,
324 CaptionStringU->MaximumLength);
325
326 /* Append the file name, seperator and the caption text */
327 CaptionStringU->Length = 0;
328 RtlAppendUnicodeStringToString(CaptionStringU, &FileNameU);
329 RtlAppendUnicodeToString(CaptionStringU, L" - ");
330 RtlAppendUnicodeStringToString(CaptionStringU, &TempStringU);
331
332 /* Zero terminate the buffer */
333 CaptionStringU->Buffer[CaptionStringU->Length / sizeof(WCHAR)] = 0;
334
335 /* Free the file name buffer */
336 RtlFreeUnicodeString(&FileNameU);
337
338 /* Check if this is an exception message */
339 if (Message->Status == STATUS_UNHANDLED_EXCEPTION)
340 {
341 ExceptionCode = Parameters[0];
342
343 /* Handle special cases */
344 if (ExceptionCode == STATUS_ACCESS_VIOLATION)
345 {
346 Parameters[0] = Parameters[1];
347 Parameters[1] = Parameters[3];
348 if (Parameters[2])
349 Parameters[2] = (ULONG_PTR)L"written";
350 else
351 Parameters[2] = (ULONG_PTR)L"read";
352 MessageResource = NULL;
353 }
354 else if (ExceptionCode == STATUS_IN_PAGE_ERROR)
355 {
356 Parameters[0] = Parameters[1];
357 Parameters[1] = Parameters[3];
358 MessageResource = NULL;
359 }
360 else
361 {
362 /* Fall back to hardcoded value */
363 Parameters[2] = Parameters[1];
364 Parameters[1] = Parameters[0];
365 Parameters[0] = (ULONG_PTR)L"unknown software exception";
366 }
367
368 if (!MessageResource)
369 {
370 /* Get text string of the exception code */
371 Status = RtlFindMessage(GetModuleHandleW(L"ntdll"),
372 (ULONG_PTR)RT_MESSAGETABLE,
373 LANG_NEUTRAL,
374 ExceptionCode,
375 &MessageResource);
376 if (NT_SUCCESS(Status))
377 {
378 if (FormatA.Buffer) RtlFreeUnicodeString(&FormatU);
379
380 if (MessageResource->Flags)
381 {
382 RtlInitUnicodeString(&FormatU, (PWSTR)MessageResource->Text);
383 FormatA.Buffer = NULL;
384 }
385 else
386 {
387 RtlInitAnsiString(&FormatA, (PCHAR)MessageResource->Text);
388 RtlAnsiStringToUnicodeString(&FormatU, &FormatA, TRUE);
389 }
390 FormatString = FormatU.Buffer;
391 }
392 else
393 {
394 /* Fall back to hardcoded value */
395 Parameters[2] = Parameters[1];
396 Parameters[1] = Parameters[0];
397 Parameters[0] = (ULONG_PTR)L"unknown software exception";
398 }
399 }
400 }
401
402 /* Calculate length of text buffer */
403 TextStringU->MaximumLength = FormatU.Length + SizeOfStrings + 42 * sizeof(WCHAR);
404
405 /* Allocate a buffer for the text */
406 TextStringU->Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
407 HEAP_ZERO_MEMORY,
408 TextStringU->MaximumLength);
409
410 /* Wrap in SEH to protect from invalid string parameters */
411 _SEH2_TRY
412 {
413 /* Print the string into the buffer */
414 StringCbPrintfW(TextStringU->Buffer,
415 TextStringU->MaximumLength,
416 FormatString,
417 Parameters[0],
418 Parameters[1],
419 Parameters[2],
420 Parameters[3]);
421 Status = STATUS_SUCCESS;
422 }
423 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
424 {
425 /* Set error and free buffers */
426 Status = _SEH2_GetExceptionCode();
427 RtlFreeHeap(RtlGetProcessHeap(), 0, TextStringU->Buffer);
428 RtlFreeHeap(RtlGetProcessHeap(), 0, CaptionStringU->Buffer);
429 }
430 _SEH2_END
431
432 if (NT_SUCCESS(Status))
433 {
434 TextStringU->Length = wcslen(TextStringU->Buffer) * sizeof(WCHAR);
435 }
436
437 if (FormatA.Buffer) RtlFreeUnicodeString(&FormatU);
438
439 return Status;
440 }
441
442 static BOOL
443 UserpShowInformationBalloon(PWSTR Text,
444 PWSTR Caption,
445 PHARDERROR_MSG Message)
446 {
447 HWND hwnd;
448 COPYDATASTRUCT CopyData;
449 PBALLOON_HARD_ERROR_DATA pdata;
450 DWORD dwSize, cchText, cchCaption;
451 PWCHAR pText, pCaption;
452 DWORD ret, dwResult;
453
454 hwnd = GetTaskmanWindow();
455 if (!hwnd)
456 {
457 DPRINT1("Failed to find Shell_TrayWnd\n");
458 return FALSE;
459 }
460
461 cchText = wcslen(Text);
462 cchCaption = wcslen(Caption);
463
464 dwSize = sizeof(BALLOON_HARD_ERROR_DATA);
465 dwSize += (cchText + 1) * sizeof(WCHAR);
466 dwSize += (cchCaption + 1) * sizeof(WCHAR);
467
468 pdata = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
469 if (!pdata)
470 {
471 DPRINT1("Failed to allocate balloon package\n");
472 return FALSE;
473 }
474
475 pdata->cbHeaderSize = sizeof(BALLOON_HARD_ERROR_DATA);
476 pdata->Status = Message->Status;
477 if (NT_SUCCESS(Message->Status))
478 pdata->dwType = MB_OK;
479 else if (Message->Status == STATUS_SERVICE_NOTIFICATION)
480 pdata->dwType = Message->Parameters[2];
481 else
482 pdata->dwType = MB_ICONINFORMATION;
483 pdata->TitleOffset = pdata->cbHeaderSize;
484 pdata->MessageOffset = pdata->TitleOffset;
485 pdata->MessageOffset += (cchCaption + 1) * sizeof(WCHAR);
486 pCaption = (PWCHAR)((ULONG_PTR)pdata + pdata->TitleOffset);
487 pText = (PWCHAR)((ULONG_PTR)pdata + pdata->MessageOffset);
488 wcscpy(pCaption, Caption);
489 wcscpy(pText, Text);
490
491 CopyData.dwData = RegisterWindowMessageW(L"HardError");
492 CopyData.cbData = dwSize;
493 CopyData.lpData = pdata;
494
495 dwResult = FALSE;
496
497 ret = SendMessageTimeoutW(hwnd, WM_COPYDATA, 0, (LPARAM)&CopyData,
498 SMTO_NORMAL | SMTO_ABORTIFHUNG, 3000, &dwResult);
499
500 RtlFreeHeap(RtlGetProcessHeap(), 0, pdata);
501
502 return (ret && dwResult) ? TRUE : FALSE;
503 }
504
505 static
506 ULONG
507 UserpMessageBox(
508 PWSTR Text,
509 PWSTR Caption,
510 ULONG ValidResponseOptions,
511 ULONG Severity)
512 {
513 ULONG Type, MessageBoxResponse;
514
515 /* Set the message box type */
516 switch (ValidResponseOptions)
517 {
518 case OptionAbortRetryIgnore:
519 Type = MB_ABORTRETRYIGNORE;
520 break;
521 case OptionOk:
522 Type = MB_OK;
523 break;
524 case OptionOkCancel:
525 Type = MB_OKCANCEL;
526 break;
527 case OptionRetryCancel:
528 Type = MB_RETRYCANCEL;
529 break;
530 case OptionYesNo:
531 Type = MB_YESNO;
532 break;
533 case OptionYesNoCancel:
534 Type = MB_YESNOCANCEL;
535 break;
536 case OptionShutdownSystem:
537 Type = MB_RETRYCANCEL; // FIXME???
538 break;
539 case OptionOkNoWait:
540 /*
541 * At that point showing the balloon failed. Is that correct?
542 */
543 Type = MB_OK; // FIXME!
544 break;
545 case OptionCancelTryContinue:
546 Type = MB_CANCELTRYCONTINUE;
547 break;
548
549 /* Anything else is invalid */
550 default:
551 {
552 DPRINT1("Unknown ValidResponseOptions = %d\n", ValidResponseOptions);
553 return ResponseNotHandled;
554 }
555 }
556
557 /* Set severity */
558 if (Severity == STATUS_SEVERITY_INFORMATIONAL) Type |= MB_ICONINFORMATION;
559 else if (Severity == STATUS_SEVERITY_WARNING) Type |= MB_ICONWARNING;
560 else if (Severity == STATUS_SEVERITY_ERROR) Type |= MB_ICONERROR;
561
562 Type |= MB_SYSTEMMODAL | MB_SETFOREGROUND;
563
564 DPRINT("Text = '%S', Caption = '%S', Severity = %d, Type = 0x%lx\n",
565 Text, Caption, Severity, Type);
566
567 /* Display a message box */
568 MessageBoxResponse = MessageBoxW(NULL, Text, Caption, Type);
569
570 /* Return response value */
571 switch (MessageBoxResponse)
572 {
573 case IDOK: return ResponseOk;
574 case IDCANCEL: return ResponseCancel;
575 case IDYES: return ResponseYes;
576 case IDNO: return ResponseNo;
577 case IDABORT: return ResponseAbort;
578 case IDIGNORE: return ResponseIgnore;
579 case IDRETRY: return ResponseRetry;
580 case IDTRYAGAIN: return ResponseTryAgain;
581 case IDCONTINUE: return ResponseContinue;
582 }
583
584 return ResponseNotHandled;
585 }
586
587 VOID
588 NTAPI
589 UserServerHardError(
590 IN PCSR_THREAD ThreadData,
591 IN PHARDERROR_MSG Message)
592 {
593 ULONG_PTR Parameters[MAXIMUM_HARDERROR_PARAMETERS];
594 OBJECT_ATTRIBUTES ObjectAttributes;
595 UNICODE_STRING TextU, CaptionU;
596 NTSTATUS Status;
597 HANDLE hProcess;
598 ULONG Size;
599
600 ASSERT(ThreadData->Process != NULL);
601
602 /* Default to not handled */
603 Message->Response = ResponseNotHandled;
604
605 /* Make sure we don't have too many parameters */
606 if (Message->NumberOfParameters > MAXIMUM_HARDERROR_PARAMETERS)
607 Message->NumberOfParameters = MAXIMUM_HARDERROR_PARAMETERS;
608
609 /* Initialize object attributes */
610 InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
611
612 /* Open client process */
613 Status = NtOpenProcess(&hProcess,
614 PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,
615 &ObjectAttributes,
616 &Message->h.ClientId);
617 if (!NT_SUCCESS(Status))
618 {
619 DPRINT1("NtOpenProcess failed with code: %lx\n", Status);
620 return;
621 }
622
623 /* Capture all string parameters from the process memory */
624 Status = UserpCaptureStringParameters(Parameters, &Size, Message, hProcess);
625 if (!NT_SUCCESS(Status))
626 {
627 NtClose(hProcess);
628 return;
629 }
630
631 /* Format the caption and message box text */
632 Status = UserpFormatMessages(&TextU,
633 &CaptionU,
634 Parameters,
635 Size,
636 Message,
637 hProcess);
638
639 /* Cleanup */
640 UserpFreeStringParameters(Parameters, Message);
641 NtClose(hProcess);
642
643 if (!NT_SUCCESS(Status))
644 {
645 return;
646 }
647
648 if (Message->ValidResponseOptions == OptionOkNoWait)
649 {
650 /* Display the balloon */
651 if (UserpShowInformationBalloon(TextU.Buffer,
652 CaptionU.Buffer,
653 Message))
654 {
655 Message->Response = ResponseOk;
656 RtlFreeUnicodeString(&TextU);
657 RtlFreeUnicodeString(&CaptionU);
658 return;
659 }
660 }
661
662 /* Display the message box */
663 Message->Response = UserpMessageBox(TextU.Buffer,
664 CaptionU.Buffer,
665 Message->ValidResponseOptions,
666 (ULONG)Message->Status >> 30);
667
668 RtlFreeUnicodeString(&TextU);
669 RtlFreeUnicodeString(&CaptionU);
670 return;
671 }
672
673 /* EOF */