[KERNEL32][CONSRV]
authorHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Fri, 8 Aug 2014 17:06:28 +0000 (17:06 +0000)
committerHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Fri, 8 Aug 2014 17:06:28 +0000 (17:06 +0000)
For WriteConsoleOutput API only!! Diagnosed with ApiMonitor from Rohitab.

If one tries to write a too large buffer (>= 80*198 CHAR_INFO cells), the CsrAllocateCaptureBuffer API helper fails, because it uses the non-growable CSR port heap of fixed size 64kB.
Since many applications use the API with large buffers (for example, Far Manager <= 1.70, the Windows XTree app http://textmode.netne.net/Extreme.html , ...) and maybe NTVDM,
Windows (and hence ReactOS for compatibility reasons) allocates a buffer in the process' heap (and not in the CSR port heap via the CSR capture API), so that the big buffer allocation
should work. Then, to be able to access it, CSR needs to call NtReadVirtualMemory for capturing the buffer.

CORE-5006 CORE-6397 CORE-8424 #resolve

svn path=/branches/condrv_restructure/; revision=63841

dll/win32/kernel32/client/console/readwrite.c
include/reactos/subsys/win/conmsg.h
win32ss/user/winsrv/consrv/conoutput.c

index 9db3ab5..f1818a0 100644 (file)
@@ -881,6 +881,7 @@ IntWriteConsoleOutput(IN HANDLE hConsoleOutput,
     {
         WriteOutputRequest->CharInfo = &WriteOutputRequest->StaticBuffer;
         // CaptureBuffer = NULL;
+        WriteOutputRequest->UseVirtualMemory = FALSE;
     }
     else
     {
@@ -888,17 +889,36 @@ IntWriteConsoleOutput(IN HANDLE hConsoleOutput,
 
         /* Allocate a Capture Buffer */
         CaptureBuffer = CsrAllocateCaptureBuffer(1, Size);
-        if (CaptureBuffer == NULL)
+        if (CaptureBuffer)
         {
-            DPRINT1("CsrAllocateCaptureBuffer failed with size %ld!\n", Size);
-            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
-            return FALSE;
+            /* Allocate space in the Buffer */
+            CsrAllocateMessagePointer(CaptureBuffer,
+                                      Size,
+                                      (PVOID*)&WriteOutputRequest->CharInfo);
+            WriteOutputRequest->UseVirtualMemory = FALSE;
+        }
+        else
+        {
+            /*
+             * CsrAllocateCaptureBuffer failed because we tried to allocate
+             * a too large (>= 64 kB, size of the CSR heap) data buffer.
+             * To circumvent this, Windows uses a trick (that we reproduce for
+             * compatibility reasons): we allocate a heap buffer in the process'
+             * memory, and CSR will read it via NtReadVirtualMemory.
+             */
+            DPRINT1("CsrAllocateCaptureBuffer failed with size %ld, let's use local heap buffer...\n", Size);
+
+            WriteOutputRequest->CharInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, Size);
+            WriteOutputRequest->UseVirtualMemory = TRUE;
+
+            /* Bail out if we still cannot allocate memory */
+            if (WriteOutputRequest->CharInfo == NULL)
+            {
+                DPRINT1("Failed to allocate heap buffer with size %ld!\n", Size);
+                SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+                return FALSE;
+            }
         }
-
-        /* Allocate space in the Buffer */
-        CsrAllocateMessagePointer(CaptureBuffer,
-                                  Size,
-                                  (PVOID*)&WriteOutputRequest->CharInfo);
     }
 
     /* Capture the user buffer contents */
@@ -945,7 +965,16 @@ IntWriteConsoleOutput(IN HANDLE hConsoleOutput,
     Success = NT_SUCCESS(ApiMessage.Status);
 
     /* Release the capture buffer if needed */
-    if (CaptureBuffer) CsrFreeCaptureBuffer(CaptureBuffer);
+    if (CaptureBuffer)
+    {
+        CsrFreeCaptureBuffer(CaptureBuffer);
+    }
+    else
+    {
+        /* If we used a heap buffer, free it */
+        if (WriteOutputRequest->UseVirtualMemory)
+            RtlFreeHeap(RtlGetProcessHeap(), 0, WriteOutputRequest->CharInfo);
+    }
 
     /* Retrieve the results */
     _SEH2_TRY
index a11d0bf..0685357 100644 (file)
@@ -544,7 +544,12 @@ typedef struct
     SMALL_RECT WriteRegion;
     BOOLEAN Unicode;
 
-    ULONG Unknown;
+    /*
+     * If we are going to write too large (>= 64 kB, size of the CSR heap)
+     * data buffers, we allocate a heap buffer in the process' memory, and
+     * CSR will read it via NtReadVirtualMemory.
+     */
+    BOOLEAN UseVirtualMemory;
 } CONSOLE_WRITEOUTPUT, *PCONSOLE_WRITEOUTPUT;
 
 typedef struct
index 833b3cd..12a4d4d 100644 (file)
@@ -463,15 +463,15 @@ CSR_API(SrvReadConsoleOutput)
 
     DPRINT("SrvReadConsoleOutput\n");
 
+    NumCells = (ReadOutputRequest->ReadRegion.Right - ReadOutputRequest->ReadRegion.Left + 1) *
+               (ReadOutputRequest->ReadRegion.Bottom - ReadOutputRequest->ReadRegion.Top + 1);
+
     /*
      * For optimization purposes, Windows (and hence ReactOS, too, for
      * compatibility reasons) uses a static buffer if no more than one
      * cell is read. Otherwise a new buffer is used.
      * The client-side expects that we know this behaviour.
      */
-    NumCells = (ReadOutputRequest->ReadRegion.Right - ReadOutputRequest->ReadRegion.Left + 1) *
-               (ReadOutputRequest->ReadRegion.Bottom - ReadOutputRequest->ReadRegion.Top + 1);
-
     if (NumCells <= 1)
     {
         /*
@@ -520,54 +520,95 @@ CSR_API(SrvWriteConsoleOutput)
     NTSTATUS Status;
     PCONSOLE_WRITEOUTPUT WriteOutputRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.WriteOutputRequest;
     PTEXTMODE_SCREEN_BUFFER Buffer;
+    PCSR_PROCESS Process = CsrGetClientThread()->Process;
 
     ULONG NumCells;
     PCHAR_INFO CharInfo;
 
     DPRINT("SrvWriteConsoleOutput\n");
 
-    /*
-     * For optimization purposes, Windows (and hence ReactOS, too, for
-     * compatibility reasons) uses a static buffer if no more than one
-     * cell is written. Otherwise a new buffer is used.
-     * The client-side expects that we know this behaviour.
-     */
     NumCells = (WriteOutputRequest->WriteRegion.Right - WriteOutputRequest->WriteRegion.Left + 1) *
                (WriteOutputRequest->WriteRegion.Bottom - WriteOutputRequest->WriteRegion.Top + 1);
 
-    if (NumCells <= 1)
+    Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(Process),
+                                     WriteOutputRequest->OutputHandle,
+                                     &Buffer, GENERIC_WRITE, TRUE);
+    if (!NT_SUCCESS(Status)) return Status;
+
+    /*
+     * Validate the message buffer if we do not use a process' heap buffer
+     * (CsrAllocateCaptureBuffer succeeded because we haven't allocated
+     * a too large (>= 64 kB, size of the CSR heap) data buffer).
+     */
+    if (!WriteOutputRequest->UseVirtualMemory)
     {
         /*
-         * Adjust the internal pointer, because its old value points to
-         * the static buffer in the original ApiMessage structure.
+         * For optimization purposes, Windows (and hence ReactOS, too, for
+         * compatibility reasons) uses a static buffer if no more than one
+         * cell is written. Otherwise a new buffer is used.
+         * The client-side expects that we know this behaviour.
          */
-        // WriteOutputRequest->CharInfo = &WriteOutputRequest->StaticBuffer;
-        CharInfo = &WriteOutputRequest->StaticBuffer;
+        if (NumCells <= 1)
+        {
+            /*
+             * Adjust the internal pointer, because its old value points to
+             * the static buffer in the original ApiMessage structure.
+             */
+            // WriteOutputRequest->CharInfo = &WriteOutputRequest->StaticBuffer;
+            CharInfo = &WriteOutputRequest->StaticBuffer;
+        }
+        else
+        {
+            if (!CsrValidateMessageBuffer(ApiMessage,
+                                          (PVOID*)&WriteOutputRequest->CharInfo,
+                                          NumCells,
+                                          sizeof(CHAR_INFO)))
+            {
+                Status = STATUS_INVALID_PARAMETER;
+                goto Quit;
+            }
+
+            CharInfo = WriteOutputRequest->CharInfo;
+        }
     }
     else
     {
-        if (!CsrValidateMessageBuffer(ApiMessage,
-                                      (PVOID*)&WriteOutputRequest->CharInfo,
-                                      NumCells,
-                                      sizeof(CHAR_INFO)))
+        /*
+         * This was not the case: we use a heap buffer. Retrieve its contents.
+         */
+        ULONG Size = NumCells * sizeof(CHAR_INFO);
+
+        CharInfo = ConsoleAllocHeap(HEAP_ZERO_MEMORY, Size);
+        if (CharInfo == NULL)
         {
-            return STATUS_INVALID_PARAMETER;
+            Status = STATUS_NO_MEMORY;
+            goto Quit;
         }
 
-        CharInfo = WriteOutputRequest->CharInfo;
+        Status = NtReadVirtualMemory(Process->ProcessHandle,
+                                     WriteOutputRequest->CharInfo,
+                                     CharInfo,
+                                     Size,
+                                     NULL);
+        if (!NT_SUCCESS(Status))
+        {
+            ConsoleFreeHeap(CharInfo);
+            // Status = STATUS_NO_MEMORY;
+            goto Quit;
+        }
     }
 
-    Status = ConSrvGetTextModeBuffer(ConsoleGetPerProcessData(CsrGetClientThread()->Process),
-                                     WriteOutputRequest->OutputHandle,
-                                     &Buffer, GENERIC_WRITE, TRUE);
-    if (!NT_SUCCESS(Status)) return Status;
-
     Status = ConDrvWriteConsoleOutput(Buffer->Header.Console,
                                       Buffer,
                                       WriteOutputRequest->Unicode,
                                       CharInfo,
                                       &WriteOutputRequest->WriteRegion);
 
+    /* Free the temporary buffer if we used the process' heap buffer */
+    if (WriteOutputRequest->UseVirtualMemory && CharInfo)
+        ConsoleFreeHeap(CharInfo);
+
+Quit:
     ConSrvReleaseScreenBuffer(Buffer, TRUE);
     return Status;
 }