[NTOSKRNL] ExRaiseHardError(): Protect strings copy to user-mode space inside a SEH...
[reactos.git] / ntoskrnl / ex / harderr.c
index a43bc24..63fa050 100644 (file)
@@ -85,30 +85,15 @@ ExpSystemErrorHandler(IN NTSTATUS ErrorStatus,
 
 /*++
  * @name ExpRaiseHardError
+ * @implemented
  *
- * For now it's a stub
- *
- * @param ErrorStatus
- *        FILLME
- *
- * @param NumberOfParameters
- *        FILLME
- *
- * @param UnicodeStringParameterMask
- *        FILLME
- *
- * @param Parameters
- *        FILLME
- *
- * @param ValidResponseOptions
- *        FILLME
+ * See ExRaiseHardError and NtRaiseHardError, same parameters.
  *
- * @param Response
- *        FILLME
- *
- * @return None
- *
- * @remarks None
+ * This function performs the central work for both ExRaiseHardError
+ * and NtRaiseHardError. ExRaiseHardError is the service for kernel-mode
+ * that copies the parameters to user-mode, and NtRaiseHardError is the
+ * service for both kernel-mode and user-mode that performs parameters
+ * validation and capture if necessary.
  *
  *--*/
 NTSTATUS
@@ -120,20 +105,31 @@ ExpRaiseHardError(IN NTSTATUS ErrorStatus,
                   IN ULONG ValidResponseOptions,
                   OUT PULONG Response)
 {
+    NTSTATUS Status;
     PEPROCESS Process = PsGetCurrentProcess();
     PETHREAD Thread = PsGetCurrentThread();
     UCHAR Buffer[PORT_MAXIMUM_MESSAGE_LENGTH];
     PHARDERROR_MSG Message = (PHARDERROR_MSG)Buffer;
-    NTSTATUS Status;
     HANDLE PortHandle;
     KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
+
     PAGED_CODE();
 
     /* Check if this error will shutdown the system */
     if (ValidResponseOptions == OptionShutdownSystem)
     {
-        /* Check for privilege */
-        if (!SeSinglePrivilegeCheck(SeShutdownPrivilege, PreviousMode))
+        /*
+         * Check if we have the privileges.
+         *
+         * NOTE: In addition to the Shutdown privilege we also check whether
+         * the caller has the Tcb privilege. The purpose is to allow only
+         * SYSTEM processes to "shutdown" the system on hard errors (BSOD)
+         * while forbidding regular processes to do so. This behaviour differs
+         * from Windows, where any user-mode process, as soon as it has the
+         * Shutdown privilege, can trigger a hard-error BSOD.
+         */
+        if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode) ||
+            !SeSinglePrivilegeCheck(SeShutdownPrivilege, PreviousMode))
         {
             /* No rights */
             *Response = ResponseNotHandled;
@@ -148,21 +144,23 @@ ExpRaiseHardError(IN NTSTATUS ErrorStatus,
     if (!Thread->HardErrorsAreDisabled)
     {
         /* Check if we can't do errors anymore, and this is serious */
-        if ((!ExReadyForErrors) && (NT_ERROR(ErrorStatus)))
+        if (!ExReadyForErrors && NT_ERROR(ErrorStatus))
         {
             /* Use the system handler */
             ExpSystemErrorHandler(ErrorStatus,
                                   NumberOfParameters,
                                   UnicodeStringParameterMask,
                                   Parameters,
-                                  (PreviousMode != KernelMode) ? TRUE: FALSE);
+                                  (PreviousMode != KernelMode) ? TRUE : FALSE);
         }
     }
 
-    /* Enable hard error processing if it is enabled for the process
-     * or if the exception status forces it */
-    if ((Process->DefaultHardErrorProcessing & 1) ||
-        (ErrorStatus & 0x10000000))
+    /*
+     * Enable hard error processing if it is enabled for the process
+     * or if the exception status forces it.
+     */
+    if ((Process->DefaultHardErrorProcessing & SEM_FAILCRITICALERRORS) ||
+        (ErrorStatus & HARDERROR_OVERRIDE_ERRORMODE))
     {
         /* Check if we have an exception port */
         if (Process->ExceptionPort)
@@ -185,81 +183,104 @@ ExpRaiseHardError(IN NTSTATUS ErrorStatus,
     /* If hard errors are disabled, do nothing */
     if (Thread->HardErrorsAreDisabled) PortHandle = NULL;
 
-    /* Now check if we have a port */
-    if (PortHandle)
+    /*
+     * If this is not the system thread, check whether hard errors are
+     * disabled for this thread on user-mode side, and if so, do nothing.
+     */
+    if (!Thread->SystemThread && (PortHandle != NULL))
     {
-        /* Check if this is the default process */
-        if (Process == ExpDefaultErrorPortProcess)
+        /* Check if we have a TEB */
+        PTEB Teb = PsGetCurrentThread()->Tcb.Teb;
+        if (Teb)
         {
-            /* We can't handle the error, check if this is critical */
-            if (NT_ERROR(ErrorStatus))
+            _SEH2_TRY
             {
-                /* It is, invoke the system handler */
-                ExpSystemErrorHandler(ErrorStatus,
-                                      NumberOfParameters,
-                                      UnicodeStringParameterMask,
-                                      Parameters,
-                                      (PreviousMode != KernelMode) ? TRUE: FALSE);
-
-                /* If we survived, return to caller */
-                *Response = ResponseReturnToCaller;
-                return STATUS_SUCCESS;
+                if (Teb->HardErrorMode & RTL_SEM_FAILCRITICALERRORS)
+                {
+                    PortHandle = NULL;
+                }
+            }
+            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+            {
+                NOTHING;
             }
+            _SEH2_END;
         }
+    }
 
-        /* Setup the LPC Message */
-        Message->h.u1.Length = (sizeof(HARDERROR_MSG) << 16) |
-                               (sizeof(HARDERROR_MSG) - sizeof(PORT_MESSAGE));
-        Message->h.u2.ZeroInit = 0;
-        Message->h.u2.s2.Type = LPC_ERROR_EVENT;
-        Message->Status = ErrorStatus &~ 0x10000000;
-        Message->ValidResponseOptions = ValidResponseOptions;
-        Message->UnicodeStringParameterMask = UnicodeStringParameterMask;
-        Message->NumberOfParameters = NumberOfParameters;
-        KeQuerySystemTime(&Message->ErrorTime);
-
-        /* Copy the parameters */
-        if (Parameters) RtlMoveMemory(&Message->Parameters,
-                                      Parameters,
-                                      sizeof(ULONG_PTR) * NumberOfParameters);
-
-        /* Send the LPC Message */
-        Status = LpcRequestWaitReplyPort(PortHandle,
-                                         (PVOID)Message,
-                                         (PVOID)Message);
-        if (NT_SUCCESS(Status))
+    /* Now check if we have a port */
+    if (PortHandle == NULL)
+    {
+        /* Just return to caller */
+        *Response = ResponseReturnToCaller;
+        return STATUS_SUCCESS;
+    }
+
+    /* Check if this is the default process */
+    if (Process == ExpDefaultErrorPortProcess)
+    {
+        /* We can't handle the error, check if this is critical */
+        if (NT_ERROR(ErrorStatus))
         {
-            /* Check what kind of response we got */
-            if ((Message->Response != ResponseReturnToCaller) &&
-                (Message->Response != ResponseNotHandled) &&
-                (Message->Response != ResponseAbort) &&
-                (Message->Response != ResponseCancel) &&
-                (Message->Response != ResponseIgnore) &&
-                (Message->Response != ResponseNo) &&
-                (Message->Response != ResponseOk) &&
-                (Message->Response != ResponseRetry) &&
-                (Message->Response != ResponseYes) &&
-                (Message->Response != ResponseTryAgain) &&
-                (Message->Response != ResponseContinue))
-            {
-                /* Reset to a default one */
-                Message->Response = ResponseReturnToCaller;
-            }
+            /* It is, invoke the system handler */
+            ExpSystemErrorHandler(ErrorStatus,
+                                  NumberOfParameters,
+                                  UnicodeStringParameterMask,
+                                  Parameters,
+                                  (PreviousMode != KernelMode) ? TRUE : FALSE);
 
-            /* Set the response */
-            *Response = Message->Response;
+            /* If we survived, return to caller */
+            *Response = ResponseReturnToCaller;
+            return STATUS_SUCCESS;
         }
-        else
+    }
+
+    /* Setup the LPC Message */
+    Message->h.u1.Length = (sizeof(HARDERROR_MSG) << 16) |
+                           (sizeof(HARDERROR_MSG) - sizeof(PORT_MESSAGE));
+    Message->h.u2.ZeroInit = 0;
+    Message->h.u2.s2.Type = LPC_ERROR_EVENT;
+    Message->Status = ErrorStatus & ~HARDERROR_OVERRIDE_ERRORMODE;
+    Message->ValidResponseOptions = ValidResponseOptions;
+    Message->UnicodeStringParameterMask = UnicodeStringParameterMask;
+    Message->NumberOfParameters = NumberOfParameters;
+    KeQuerySystemTime(&Message->ErrorTime);
+
+    /* Copy the parameters */
+    if (Parameters) RtlMoveMemory(&Message->Parameters,
+                                  Parameters,
+                                  sizeof(ULONG_PTR) * NumberOfParameters);
+
+    /* Send the LPC Message */
+    Status = LpcRequestWaitReplyPort(PortHandle,
+                                     (PPORT_MESSAGE)Message,
+                                     (PPORT_MESSAGE)Message);
+    if (NT_SUCCESS(Status))
+    {
+        /* Check what kind of response we got */
+        if ((Message->Response != ResponseReturnToCaller) &&
+            (Message->Response != ResponseNotHandled) &&
+            (Message->Response != ResponseAbort) &&
+            (Message->Response != ResponseCancel) &&
+            (Message->Response != ResponseIgnore) &&
+            (Message->Response != ResponseNo) &&
+            (Message->Response != ResponseOk) &&
+            (Message->Response != ResponseRetry) &&
+            (Message->Response != ResponseYes) &&
+            (Message->Response != ResponseTryAgain) &&
+            (Message->Response != ResponseContinue))
         {
-            /* Set the response */
-            *Response = ResponseReturnToCaller;
+            /* Reset to a default one */
+            Message->Response = ResponseReturnToCaller;
         }
+
+        /* Set the response */
+        *Response = Message->Response;
     }
     else
     {
-        /* Set defaults */
+        /* Set the response */
         *Response = ResponseReturnToCaller;
-        Status = STATUS_SUCCESS;
     }
 
     /* Return status */
@@ -334,7 +355,7 @@ ExSystemExceptionFilter(VOID)
  * @name ExRaiseHardError
  * @implemented
  *
- * See NtRaiseHardError
+ * See NtRaiseHardError and ExpRaiseHardError.
  *
  * @param ErrorStatus
  *        Error Code
@@ -354,9 +375,7 @@ ExSystemExceptionFilter(VOID)
  * @param Response
  *        Pointer to HARDERROR_RESPONSE enumeration
  *
- * @return None
- *
- * @remarks None
+ * @return Status
  *
  *--*/
 NTSTATUS
@@ -368,6 +387,7 @@ ExRaiseHardError(IN NTSTATUS ErrorStatus,
                  IN ULONG ValidResponseOptions,
                  OUT PULONG Response)
 {
+    NTSTATUS Status;
     SIZE_T Size;
     UNICODE_STRING CapturedParams[MAXIMUM_HARDERROR_PARAMETERS];
     ULONG i;
@@ -375,7 +395,7 @@ ExRaiseHardError(IN NTSTATUS ErrorStatus,
     PHARDERROR_USER_PARAMETERS UserParams;
     PWSTR BufferBase;
     ULONG SafeResponse;
-    NTSTATUS Status;
+
     PAGED_CODE();
 
     /* Check if we have parameters */
@@ -416,35 +436,46 @@ ExRaiseHardError(IN NTSTATUS ErrorStatus,
             UserParams = UserData;
             BufferBase = UserParams->Buffer;
 
-            /* Loop parameters again */
-            for (i = 0; i < NumberOfParameters; i++)
+            /* Enter SEH block as we are writing to user-mode space */
+            _SEH2_TRY
             {
-                /* Check if we're in the mask */
-                if (UnicodeStringParameterMask & (1 << i))
+                /* Loop parameters again */
+                for (i = 0; i < NumberOfParameters; i++)
                 {
-                    /* Update the base */
-                    UserParams->Parameters[i] = (ULONG_PTR)&UserParams->Strings[i];
+                    /* Check if we are in the mask */
+                    if (UnicodeStringParameterMask & (1 << i))
+                    {
+                        /* Update the base */
+                        UserParams->Parameters[i] = (ULONG_PTR)&UserParams->Strings[i];
 
-                    /* Copy the string buffer */
-                    RtlMoveMemory(BufferBase,
-                                  CapturedParams[i].Buffer,
-                                  CapturedParams[i].MaximumLength);
+                        /* Copy the string buffer */
+                        RtlMoveMemory(BufferBase,
+                                      CapturedParams[i].Buffer,
+                                      CapturedParams[i].MaximumLength);
 
-                    /* Set buffer */
-                    CapturedParams[i].Buffer = BufferBase;
+                        /* Set buffer */
+                        CapturedParams[i].Buffer = BufferBase;
 
-                    /* Copy the string structure */
-                    UserParams->Strings[i] = CapturedParams[i];
+                        /* Copy the string structure */
+                        UserParams->Strings[i] = CapturedParams[i];
 
-                    /* Update the pointer */
-                    BufferBase += CapturedParams[i].MaximumLength;
-                }
-                else
-                {
-                    /* No need to copy any strings */
-                    UserParams->Parameters[i] = Parameters[i];
+                        /* Update the pointer */
+                        BufferBase += CapturedParams[i].MaximumLength;
+                    }
+                    else
+                    {
+                        /* No need to copy any strings */
+                        UserParams->Parameters[i] = Parameters[i];
+                    }
                 }
             }
+            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+            {
+                /* Return the exception code */
+                Status = _SEH2_GetExceptionCode();
+                DPRINT1("ExRaiseHardError - Exception when writing data to user-mode, Status 0x%08lx\n", Status);
+            }
+            _SEH2_END;
         }
         else
         {
@@ -481,9 +512,9 @@ ExRaiseHardError(IN NTSTATUS ErrorStatus,
  * @name NtRaiseHardError
  * @implemented
  *
- * This function sends HARDERROR_MSG LPC message to listener
- * (typically CSRSS.EXE). See NtSetDefaultHardErrorPort for more information
- * See: http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Error/NtRaiseHardError.html
+ * This function sends HARDERROR_MSG LPC message to a hard-error listener,
+ * typically CSRSS.EXE. See NtSetDefaultHardErrorPort for more information.
+ * See also: http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Error/NtRaiseHardError.html
  *
  * @param ErrorStatus
  *        Error Code
@@ -505,8 +536,8 @@ ExRaiseHardError(IN NTSTATUS ErrorStatus,
  *
  * @return Status
  *
- * @remarks NtRaiseHardError is easy way to display message in GUI
- *          without loading Win32 API libraries
+ * @remarks NtRaiseHardError constitutes an easy way to display messages
+ *          in GUI without loading any Win32 API libraries.
  *
  *--*/
 NTSTATUS
@@ -554,6 +585,8 @@ NtRaiseHardError(IN NTSTATUS ErrorStatus,
             case OptionYesNo:
             case OptionYesNoCancel:
             case OptionShutdownSystem:
+            case OptionOkNoWait:
+            case OptionCancelTryContinue:
                 break;
 
             /* Anything else is invalid */
@@ -686,11 +719,11 @@ NtRaiseHardError(IN NTSTATUS ErrorStatus,
  * @name NtSetDefaultHardErrorPort
  * @implemented
  *
- * NtSetDefaultHardErrorPort is typically called only once. After call,
- * kernel set BOOLEAN flag named _ExReadyForErrors to TRUE, and all other
- * tries to change default port are broken with STATUS_UNSUCCESSFUL error code
- * See: http://www.windowsitlibrary.com/Content/356/08/2.html
- *      http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Error/NtSetDefaultHardErrorPort.html
+ * NtSetDefaultHardErrorPort is typically called only once. After the call,
+ * the kernel sets a BOOLEAN flag named ExReadyForErrors to TRUE, and all other
+ * attempts to change the default port fail with STATUS_UNSUCCESSFUL error code.
+ * See: http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Error/NtSetDefaultHardErrorPort.html
+ *      https://web.archive.org/web/20070716133753/http://www.windowsitlibrary.com/Content/356/08/2.html
  *
  * @param PortHandle
  *        Handle to named port object
@@ -707,7 +740,7 @@ NtSetDefaultHardErrorPort(IN HANDLE PortHandle)
     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
     NTSTATUS Status = STATUS_UNSUCCESSFUL;
 
-    /* Check if we have the Privilege */
+    /* Check if we have the privileges */
     if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode))
     {
         DPRINT1("NtSetDefaultHardErrorPort: Caller requires "
@@ -718,7 +751,7 @@ NtSetDefaultHardErrorPort(IN HANDLE PortHandle)
     /* Only called once during bootup, make sure we weren't called yet */
     if (!ExReadyForErrors)
     {
-        /* Reference the port */
+        /* Reference the hard-error port */
         Status = ObReferenceObjectByHandle(PortHandle,
                                            0,
                                            LpcPortObjectType,
@@ -727,9 +760,11 @@ NtSetDefaultHardErrorPort(IN HANDLE PortHandle)
                                            NULL);
         if (NT_SUCCESS(Status))
         {
-            /* Save the data */
+            /* Keep also a reference to the process handling the hard errors */
             ExpDefaultErrorPortProcess = PsGetCurrentProcess();
+            ObReferenceObject(ExpDefaultErrorPortProcess);
             ExReadyForErrors = TRUE;
+            Status = STATUS_SUCCESS;
         }
     }