diff --git a/dll/win32/kernel32/client/console/init.c b/dll/win32/kernel32/client/console/init.c index eb97ac73ded..a4fc400d0eb 100644 --- a/dll/win32/kernel32/client/console/init.c +++ b/dll/win32/kernel32/client/console/init.c @@ -24,6 +24,7 @@ RTL_CRITICAL_SECTION ConsoleLock; BOOLEAN ConsoleInitialized = FALSE; extern HANDLE InputWaitHandle; +static volatile LONG g_bConsoleIMEStartingUp = FALSE; // We use interlock, so LONG static const PWSTR DefaultConsoleTitle = L"ReactOS Console"; @@ -332,6 +333,217 @@ ConnectConsole(IN PWSTR SessionDir, return TRUE; } +/* Query registry value */ +static NTSTATUS +IntRegQueryValue( + _In_ HANDLE hKey, + _In_ PCWSTR pszValueName, + _Out_ PVOID pvValue, + _In_ ULONG cbValue) +{ + HANDLE hProcessHeap; + ULONG cbInfo, cbResult; + PKEY_VALUE_PARTIAL_INFORMATION pInfo; + UNICODE_STRING valueName; + NTSTATUS status; + + hProcessHeap = GetProcessHeap(); + cbInfo = FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data) + cbValue; + pInfo = HeapAlloc(hProcessHeap, 0, cbInfo); + if (!pInfo) + return STATUS_NO_MEMORY; + + RtlInitUnicodeString(&valueName, pszValueName); + + status = NtQueryValueKey(hKey, &valueName, KeyValuePartialInformation, + pInfo, cbInfo, &cbResult); + if (NT_SUCCESS(status)) + { + const ULONG cbCopy = min(pInfo->DataLength, cbValue); + RtlCopyMemory(pvValue, pInfo->Data, cbCopy); + + /* SECURITY: Avoid buffer overrun */ + if (pInfo->Type == REG_SZ && cbValue >= sizeof(UNICODE_NULL)) + ((PWCHAR)pvValue)[cbValue / sizeof(WCHAR) - 1] = UNICODE_NULL; + } + + HeapFree(hProcessHeap, 0, pInfo); + return status; +} + +/* Quote a path string if necessary */ +static NTSTATUS +IntPathQuoteSpacesW( + _Inout_updates_z_(cchPathMax) PWSTR pszPath, + _In_ UINT cchPathMax) +{ + size_t cchLen; + + if (!wcschr(pszPath, L' ')) + return STATUS_SUCCESS; + + cchLen = wcslen(pszPath) + 1; + if (cchLen + 2 > cchPathMax) /* for 2 quotes */ + return STATUS_BUFFER_TOO_SMALL; + + RtlMoveMemory(pszPath + 1, pszPath, cchLen * sizeof(WCHAR)); + pszPath[0] = L'"'; + pszPath[cchLen] = L'"'; + pszPath[cchLen + 1] = UNICODE_NULL; + return STATUS_SUCCESS; +} + +/* Reject bad relative paths */ +static inline BOOL IntIsSafeRelativePath(_Inout_z_ PWSTR pszPath) +{ + /* Replace '/' with '\\' to detect the bad paths easily */ + INT ich; + for (ich = 0; pszPath[ich]; ++ich) + { + if (pszPath[ich] == L'/') + pszPath[ich] = L'\\'; + } + + /* Avoid path traversal */ + return (memcmp(pszPath, L"..\\", 3 * sizeof(WCHAR)) && !wcsstr(pszPath, L"\\..\\")); +} + +/* Build the conime.exe command line */ +static VOID +GetConsoleIMECommandLine( + _Out_ PWSTR pszBuffer, + _In_ UINT cchBuffer) +{ + UINT cchSysDir; + HANDLE hKey; + UNICODE_STRING keyName; + OBJECT_ATTRIBUTES attr; + NTSTATUS status; + WCHAR szValue[MAX_PATH]; + static const PCWSTR ConsoleKey = + L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Console"; + + cchSysDir = GetSystemDirectoryW(pszBuffer, cchBuffer); + if (cchSysDir > 0 && cchSysDir < cchBuffer - 1) + { + RtlStringCchCatW(pszBuffer, cchBuffer, L"\\"); + cchSysDir = (UINT)wcslen(pszBuffer); + } + else + { + *pszBuffer = UNICODE_NULL; + cchSysDir = 0; + } + + /* Open registry key */ + RtlInitUnicodeString(&keyName, ConsoleKey); + InitializeObjectAttributes(&attr, &keyName, OBJ_CASE_INSENSITIVE, NULL, NULL); + status = NtOpenKey(&hKey, KEY_QUERY_VALUE, &attr); + if (NT_SUCCESS(status)) + { + /* Query "ConsoleIME" value */ + status = IntRegQueryValue(hKey, L"ConsoleIME", szValue, sizeof(szValue)); + NtClose(hKey); + if (NT_SUCCESS(status)) + { + /* Malicious relative paths should be rejected (ReactOS-only) */ + if (szValue[0] && IntIsSafeRelativePath(szValue)) + { + /* Append value to pszBuffer */ + status = RtlStringCchCatW(pszBuffer, cchBuffer, szValue); + if (NT_SUCCESS(status)) + { + /* Quote the path if necessary (ReactOS-only). Avoid path traversal */ + status = IntPathQuoteSpacesW(pszBuffer, cchBuffer); + if (NT_SUCCESS(status)) + { + DPRINT("ConsoleIME: '%S'\n", pszBuffer); + return; /* Success */ + } + } + /* It failed. Let's try the default path */ + pszBuffer[cchSysDir] = UNICODE_NULL; + } + } + } + else + { + DPRINT1("Opening registry failed: 0x%08X\n", status); + } + + RtlStringCchCatW(pszBuffer, cchBuffer, L"conime.exe"); + + /* Quote the path if necessary (ReactOS-only). Avoid path traversal */ + status = IntPathQuoteSpacesW(pszBuffer, cchBuffer); + if (!NT_SUCCESS(status)) + RtlStringCchCopyW(pszBuffer, cchBuffer, L"conime.exe"); /* Use filename only */ +} + +/** + * @brief + * This function is called from winsrv.dll, in the context of the console application, + * to support Console IME on East Asian console. + */ +DWORD +WINAPI +ConsoleIMERoutine(_In_ PVOID unused) +{ + HANDLE hStartUpEvent; + DWORD dwError; + WCHAR szCommandLine[2 * MAX_PATH]; + STARTUPINFOW si; + PROCESS_INFORMATION pi; + DWORD dwCreationFlags; + + UNREFERENCED_PARAMETER(unused); + + if (InterlockedCompareExchange(&g_bConsoleIMEStartingUp, TRUE, FALSE) != FALSE) + return STATUS_UNSUCCESSFUL; /* NOTE: There's confusion between error codes and NTSTATUS */ + + hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, L"ConsoleIME_StartUp_Event"); + dwError = GetLastError(); + if (dwError == ERROR_ALREADY_EXISTS) + { + CloseHandle(hStartUpEvent); + hStartUpEvent = NULL; + } + if (!hStartUpEvent) + { + InterlockedExchange(&g_bConsoleIMEStartingUp, FALSE); + return ERROR_SUCCESS; + } + + RtlZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.lpDesktop = NtCurrentPeb()->ProcessParameters->DesktopInfo.Buffer; + si.dwFlags = STARTF_FORCEONFEEDBACK; + + /* Let's create a conime.exe process */ + GetConsoleIMECommandLine(szCommandLine, _countof(szCommandLine)); + dwCreationFlags = CREATE_DEFAULT_ERROR_MODE | CREATE_BREAKAWAY_FROM_JOB | + CREATE_NEW_PROCESS_GROUP | NORMAL_PRIORITY_CLASS; + if (CreateProcessW(NULL, szCommandLine, NULL, NULL, FALSE, dwCreationFlags, + NULL, NULL, &si, &pi)) + { + /* Wait 10 seconds for conime.exe to start up */ + const DWORD dwWait = WaitForSingleObject(hStartUpEvent, 10 * 1000); + if (dwWait == WAIT_TIMEOUT) + TerminateProcess(pi.hProcess, 0); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + dwError = ERROR_SUCCESS; + } + else + { + dwError = GetLastError(); + DPRINT1("'%S' startup failed: 0x%08X\n", szCommandLine, dwError); + } + + CloseHandle(hStartUpEvent); + + InterlockedExchange(&g_bConsoleIMEStartingUp, FALSE); + return dwError; +} BOOLEAN WINAPI @@ -442,7 +654,7 @@ ConDllInitialize(IN ULONG Reason, /* Initialize the console dispatchers */ ConnectInfo.CtrlRoutine = ConsoleControlDispatcher; ConnectInfo.PropRoutine = PropDialogHandler; - // ConnectInfo.ImeRoutine = ImeRoutine; + ConnectInfo.ImeRoutine = ConsoleIMERoutine; /* Set up the console properties */ if (ConnectInfo.IsConsoleApp && Parameters->ConsoleHandle == NULL) diff --git a/dll/win32/kernel32/kernel32.spec b/dll/win32/kernel32/kernel32.spec index 5be33f9c9ff..ecd51f8447e 100644 --- a/dll/win32/kernel32/kernel32.spec +++ b/dll/win32/kernel32/kernel32.spec @@ -90,7 +90,7 @@ @ stdcall -version=0x600+ CompareStringOrdinal(wstr long wstr long long) @ stdcall CompareStringW(long long wstr long wstr long) @ stdcall ConnectNamedPipe(long ptr) -;@ stdcall -arch=x86_64 ConsoleIMERoutine() +@ stdcall -version=0x502-0x600 -arch=x86_64 ConsoleIMERoutine(ptr) @ stdcall ConsoleMenuControl(long long long) @ stdcall ContinueDebugEvent(long long long) @ stdcall -stub -version=0x600+ ConvertCalDateTimeToSystemTime(ptr ptr) diff --git a/sdk/include/reactos/wincon_undoc.h b/sdk/include/reactos/wincon_undoc.h index 4b9068de88e..9c813c33b3b 100644 --- a/sdk/include/reactos/wincon_undoc.h +++ b/sdk/include/reactos/wincon_undoc.h @@ -587,6 +587,16 @@ WINBASEAPI BOOL WINAPI UnregisterConsoleIME(VOID); + +#ifdef _M_AMD64 +#if (_WIN32_WINNT >= _WIN32_WINNT_WS03) && (_WIN32_WINNT <= _WIN32_WINNT_VISTA) +WINBASEAPI +DWORD +WINAPI +ConsoleIMERoutine( + _In_ PVOID unused); +#endif +#endif #endif // FE_IME #ifdef UNICODE diff --git a/win32ss/user/winsrv/consrv/consrv.h b/win32ss/user/winsrv/consrv/consrv.h index 2608e8cf005..10c1418d279 100644 --- a/win32ss/user/winsrv/consrv/consrv.h +++ b/win32ss/user/winsrv/consrv/consrv.h @@ -52,7 +52,7 @@ typedef struct _CONSOLE_PROCESS_DATA LPTHREAD_START_ROUTINE CtrlRoutine; LPTHREAD_START_ROUTINE PropRoutine; // We hold the property dialog handler there, till all the GUI thingie moves out from CSRSS. - // LPTHREAD_START_ROUTINE ImeRoutine; + LPTHREAD_START_ROUTINE ImeRoutine; } CONSOLE_PROCESS_DATA, *PCONSOLE_PROCESS_DATA; #include "include/conio.h" diff --git a/win32ss/user/winsrv/consrv/init.c b/win32ss/user/winsrv/consrv/init.c index 4332ae531a4..c729b377b8d 100644 --- a/win32ss/user/winsrv/consrv/init.c +++ b/win32ss/user/winsrv/consrv/init.c @@ -523,8 +523,9 @@ ConSrvConnect(IN PCSR_PROCESS CsrProcess, } } - /* Set the Property-Dialog handler */ + /* Set the Property-Dialog and the IME handlers */ ProcessData->PropRoutine = ConnectInfo->PropRoutine; + ProcessData->ImeRoutine = ConnectInfo->ImeRoutine; return STATUS_SUCCESS; }