reactos/ntoskrnl/ex/locale.c
George Bișoc 6438b85605
[NTOS:EX] Bail out on an invalid locale or language ID
NtSetDefaultLocale and ExpSetCurrentUserUILanguage do not probe the given locale or language ID,
and as a result of that these functions would happily take any given argument. This is problematic
because overwriting NLS data (specifically the Default registry key value as its gets set by the
NtSetDefaultLocale syscall itself) with garbage stuff, rendering the system completely unbootable.

In addition to that, these functions do not check the captured language/locale ID against pre-determined
locales or languages pre-installed in the system. This basically means an ID of 1, for example, is still
valid because it is not bogus albeit there is no such a locale of an ID of 1. That value would get passed
to the Default value key and that renders the system unbootable as well.

CORE-18100
2023-11-16 21:09:05 +01:00

720 lines
21 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/ex/locale.c
* PURPOSE: Locale (Language) Support for the Executive
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
* Eric Kohl
* Thomas Weidenmueller (w3seek@reactos.org
*/
/* INCLUDES ******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/* GLOBALS *******************************************************************/
/* System IDs: EN_US */
LCID PsDefaultSystemLocaleId = 0x00000409;
LANGID PsInstallUILanguageId = LANGIDFROMLCID(0x00000409);
/* UI/Thread IDs: Same as system */
LCID PsDefaultThreadLocaleId = 0x00000409;
LANGID PsDefaultUILanguageId = LANGIDFROMLCID(0x00000409);
/* DEFINES *******************************************************************/
#define BOGUS_LOCALE_ID 0xFFFF0000
/* PRIVATE FUNCTIONS *********************************************************/
/**
* @brief
* Validates the registry data of a NLS locale.
*
* @param[in] LocaleData
* A pointer to partial information that contains
* the NLS locale data.
*
* @return
* Returns TRUE if the following conditions are met,
* otherwise FALSE is returned.
*/
static
__inline
BOOLEAN
ExpValidateNlsLocaleData(
_In_ PKEY_VALUE_PARTIAL_INFORMATION LocaleData)
{
PWCHAR Data;
/* Is this a null-terminated string type? */
if (LocaleData->Type != REG_SZ)
{
return FALSE;
}
/* Does it have a consistent length? */
if (LocaleData->DataLength < sizeof(WCHAR))
{
return FALSE;
}
/* Is the locale set and null-terminated? */
Data = (PWSTR)LocaleData->Data;
if (Data[0] != L'1' || Data[1] != UNICODE_NULL)
{
return FALSE;
}
/* All of the conditions above are met */
return TRUE;
}
/**
* @brief
* Validates a NLS locale. Whether a locale is valid
* or not depends on the following conditions:
*
* - The locale must exist in the Locale key, otherwise
* in the Alternate Sorts key;
*
* - The locale must exist in the Language Groups key, and
* the queried value must be readable;
*
* - The locale registry data must be of REG_SIZE type,
* has a consistent length and the locale belongs to
* a supported language group that is set.
*
* @param[in] LocaleId
* A locale identifier that corresponds to a specific
* locale to be validated.
*
* @return
* Returns STATUS_SUCCESS if the function has successfully
* validated the locale and it is valid. STATUS_OBJECT_NAME_NOT_FOUND
* is returned if the following locale does not exist on the system.
* A failure NTSTATUS code is returned otherwise.
*/
static
NTSTATUS
ExpValidateNlsLocaleId(
_In_ LCID LocaleId)
{
NTSTATUS Status;
HANDLE NlsLocaleKey = NULL, AltSortKey = NULL, LangGroupKey = NULL;
OBJECT_ATTRIBUTES NlsLocalKeyAttrs, AltSortKeyAttrs, LangGroupKeyAttrs;
PKEY_VALUE_PARTIAL_INFORMATION BufferKey;
WCHAR ValueBuffer[20], LocaleIdBuffer[20];
ULONG ReturnedLength;
UNICODE_STRING LocaleIdString;
static UNICODE_STRING NlsLocaleKeyPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Locale");
static UNICODE_STRING AltSortKeyPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Locale\\Alternate Sorts");
static UNICODE_STRING LangGroupPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Language Groups");
/* Initialize the registry path attributes */
InitializeObjectAttributes(&NlsLocalKeyAttrs,
&NlsLocaleKeyPath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
InitializeObjectAttributes(&AltSortKeyAttrs,
&AltSortKeyPath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
InitializeObjectAttributes(&LangGroupKeyAttrs,
&LangGroupPath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
/* Copy the locale ID into a buffer */
swprintf(LocaleIdBuffer,
L"%08lx",
(ULONG)LocaleId);
/* And build the LCID string */
RtlInitUnicodeString(&LocaleIdString, LocaleIdBuffer);
/* Open the NLS locale key */
Status = ZwOpenKey(&NlsLocaleKey,
KEY_QUERY_VALUE,
&NlsLocalKeyAttrs);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to open %wZ (Status 0x%lx)\n", NlsLocaleKeyPath, Status);
return Status;
}
/* Open the NLS alternate sort locales key */
Status = ZwOpenKey(&AltSortKey,
KEY_QUERY_VALUE,
&AltSortKeyAttrs);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to open %wZ (Status 0x%lx)\n", AltSortKeyPath, Status);
goto Quit;
}
/* Open the NLS language groups key */
Status = ZwOpenKey(&LangGroupKey,
KEY_QUERY_VALUE,
&LangGroupKeyAttrs);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to open %wZ (Status 0x%lx)\n", LangGroupPath, Status);
goto Quit;
}
/* Check if the captured locale ID exists in the list of other locales */
BufferKey = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer;
Status = ZwQueryValueKey(NlsLocaleKey,
&LocaleIdString,
KeyValuePartialInformation,
BufferKey,
sizeof(ValueBuffer),
&ReturnedLength);
if (!NT_SUCCESS(Status))
{
/* We failed, retry by looking at the alternate sorts locales */
Status = ZwQueryValueKey(AltSortKey,
&LocaleIdString,
KeyValuePartialInformation,
BufferKey,
sizeof(ValueBuffer),
&ReturnedLength);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to query value from Alternate Sorts key (Status 0x%lx)\n", Status);
goto Quit;
}
}
/* Ensure the queried locale is of the right key type with a sane length */
if (BufferKey->Type != REG_SZ ||
BufferKey->DataLength < sizeof(WCHAR))
{
DPRINT1("The queried locale is of bad value type or length (Type %lu, DataLength %lu)\n",
BufferKey->Type, BufferKey->DataLength);
Status = STATUS_OBJECT_NAME_NOT_FOUND;
goto Quit;
}
/* We got what we need, now query the locale from the language groups */
RtlInitUnicodeString(&LocaleIdString, (PWSTR)BufferKey->Data);
Status = ZwQueryValueKey(LangGroupKey,
&LocaleIdString,
KeyValuePartialInformation,
BufferKey,
sizeof(ValueBuffer),
&ReturnedLength);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to query value from Language Groups key (Status 0x%lx)\n", Status);
goto Quit;
}
/*
* We have queried the locale with its data. However we are not finished here yet,
* because the locale data could be malformed or the locale itself was not set
* so ensure all of these conditions are met.
*/
if (!ExpValidateNlsLocaleData(BufferKey))
{
DPRINT1("The locale data is not valid!\n");
Status = STATUS_OBJECT_NAME_NOT_FOUND;
}
Quit:
if (LangGroupKey != NULL)
{
ZwClose(LangGroupKey);
}
if (AltSortKey != NULL)
{
ZwClose(AltSortKey);
}
if (NlsLocaleKey != NULL)
{
ZwClose(NlsLocaleKey);
}
return Status;
}
NTSTATUS
NTAPI
ExpGetCurrentUserUILanguage(IN PCWSTR MuiName,
OUT LANGID* LanguageId)
{
UCHAR ValueBuffer[256];
PKEY_VALUE_PARTIAL_INFORMATION ValueInfo;
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING KeyName =
RTL_CONSTANT_STRING(L"Control Panel\\Desktop");
UNICODE_STRING ValueName;
UNICODE_STRING ValueString;
ULONG ValueLength;
ULONG Value;
HANDLE UserKey;
HANDLE KeyHandle;
NTSTATUS Status;
PAGED_CODE();
/* Setup the key name */
RtlInitUnicodeString(&ValueName, MuiName);
/* Open the use key */
Status = RtlOpenCurrentUser(KEY_READ, &UserKey);
if (!NT_SUCCESS(Status)) return Status;
/* Initialize the attributes and open the key */
InitializeObjectAttributes(&ObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
UserKey,
NULL);
Status = ZwOpenKey(&KeyHandle, KEY_QUERY_VALUE,&ObjectAttributes);
if (NT_SUCCESS(Status))
{
/* Set buffer and query the current value */
ValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer;
Status = ZwQueryValueKey(KeyHandle,
&ValueName,
KeyValuePartialInformation,
ValueBuffer,
sizeof(ValueBuffer),
&ValueLength);
if (NT_SUCCESS(Status))
{
/* Success, is the value the right type? */
if (ValueInfo->Type == REG_SZ)
{
/* It is. Initialize the data and convert it */
RtlInitUnicodeString(&ValueString, (PWSTR)ValueInfo->Data);
Status = RtlUnicodeStringToInteger(&ValueString, 16, &Value);
if (NT_SUCCESS(Status))
{
/* Return the language */
*LanguageId = (USHORT)Value;
}
}
else
{
/* Fail */
Status = STATUS_UNSUCCESSFUL;
}
}
/* Close the key */
ZwClose(KeyHandle);
}
/* Close the user key and return */
ZwClose(UserKey);
return Status;
}
NTSTATUS
NTAPI
ExpSetCurrentUserUILanguage(IN PCWSTR MuiName,
IN LANGID LanguageId)
{
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"Control Panel\\Desktop");
UNICODE_STRING ValueName;
WCHAR ValueBuffer[8];
ULONG ValueLength;
HANDLE UserHandle;
HANDLE KeyHandle;
NTSTATUS Status;
PAGED_CODE();
/* Check that the passed language ID is not bogus */
if (LanguageId & BOGUS_LOCALE_ID)
{
return STATUS_INVALID_PARAMETER;
}
/* Setup the key name */
RtlInitUnicodeString(&ValueName, MuiName);
/* Open the use key */
Status = RtlOpenCurrentUser(KEY_WRITE, &UserHandle);
if (!NT_SUCCESS(Status)) return Status;
/* Initialize the attributes */
InitializeObjectAttributes(&ObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
UserHandle,
NULL);
/* Validate the language ID */
Status = ExpValidateNlsLocaleId(MAKELCID(LanguageId, SORT_DEFAULT));
if (NT_SUCCESS(Status))
{
/* Open the key */
Status = ZwOpenKey(&KeyHandle, KEY_SET_VALUE, &ObjectAttributes);
if (NT_SUCCESS(Status))
{
/* Setup the value name */
ValueLength = swprintf(ValueBuffer,
L"%04lX",
(ULONG)LanguageId);
/* Set the length for the call and set the value */
ValueLength = (ValueLength + 1) * sizeof(WCHAR);
Status = ZwSetValueKey(KeyHandle,
&ValueName,
0,
REG_SZ,
ValueBuffer,
ValueLength);
/* Close the handle for this key */
ZwClose(KeyHandle);
}
}
/* Close the user key and return status */
ZwClose(UserHandle);
return Status;
}
/* PUBLIC FUNCTIONS **********************************************************/
NTSTATUS
NTAPI
NtQueryDefaultLocale(IN BOOLEAN UserProfile,
OUT PLCID DefaultLocaleId)
{
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
/* Enter SEH for probing */
_SEH2_TRY
{
/* Check if we came from user mode */
if (KeGetPreviousMode() != KernelMode)
{
/* Probe the language ID */
ProbeForWriteLangId(DefaultLocaleId);
}
/* Check if we have a user profile */
if (UserProfile)
{
/* Return session wide thread locale */
*DefaultLocaleId = MmGetSessionLocaleId();
}
else
{
/* Return system locale */
*DefaultLocaleId = PsDefaultSystemLocaleId;
}
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Get exception code */
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
/* Return status */
return Status;
}
NTSTATUS
NTAPI
NtSetDefaultLocale(IN BOOLEAN UserProfile,
IN LCID DefaultLocaleId)
{
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING KeyName;
UNICODE_STRING ValueName;
UNICODE_STRING LocaleString;
HANDLE KeyHandle = NULL;
ULONG ValueLength;
WCHAR ValueBuffer[20];
HANDLE UserKey = NULL;
NTSTATUS Status;
UCHAR KeyValueBuffer[256];
PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation;
PAGED_CODE();
/* Check that the passed locale ID is not bogus */
if (DefaultLocaleId & BOGUS_LOCALE_ID)
{
return STATUS_INVALID_PARAMETER;
}
/* Check if we have a profile */
if (UserProfile)
{
/* Open the user's key */
Status = RtlOpenCurrentUser(KEY_WRITE, &UserKey);
if (!NT_SUCCESS(Status)) return Status;
/* Initialize the registry location */
RtlInitUnicodeString(&KeyName, L"Control Panel\\International");
RtlInitUnicodeString(&ValueName, L"Locale");
}
else
{
/* Initialize the system registry location */
RtlInitUnicodeString(&KeyName,
L"\\Registry\\Machine\\System\\CurrentControlSet"
L"\\Control\\Nls\\Language");
RtlInitUnicodeString(&ValueName, L"Default");
}
/* Initialize the object attributes */
InitializeObjectAttributes(&ObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
UserKey,
NULL);
/* Check if we don't have a default locale yet */
if (!DefaultLocaleId)
{
/* Open the key for reading */
Status = ZwOpenKey(&KeyHandle, KEY_QUERY_VALUE, &ObjectAttributes);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
/* Query the key value */
KeyValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION)KeyValueBuffer;
Status = ZwQueryValueKey(KeyHandle,
&ValueName,
KeyValuePartialInformation,
KeyValueInformation,
sizeof(KeyValueBuffer),
&ValueLength);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
/* Check if this is a REG_DWORD */
if ((KeyValueInformation->Type == REG_DWORD) &&
(KeyValueInformation->DataLength == sizeof(ULONG)))
{
/* It contains the LCID as a DWORD */
DefaultLocaleId = *((ULONG*)KeyValueInformation->Data);
}
/* Otherwise check for a REG_SZ */
else if (KeyValueInformation->Type == REG_SZ)
{
/* Initialize a unicode string from the value data */
LocaleString.Buffer = (PWCHAR)KeyValueInformation->Data;
LocaleString.Length = (USHORT)KeyValueInformation->DataLength;
LocaleString.MaximumLength = LocaleString.Length;
/* Convert the hex string to a number */
RtlUnicodeStringToInteger(&LocaleString, 16, &DefaultLocaleId);
}
else
{
Status = STATUS_UNSUCCESSFUL;
}
}
else
{
/* We have a locale, validate it */
Status = ExpValidateNlsLocaleId(DefaultLocaleId);
if (NT_SUCCESS(Status))
{
/* Open the key now */
Status = ZwOpenKey(&KeyHandle, KEY_SET_VALUE, &ObjectAttributes);
if (NT_SUCCESS(Status))
{
/* Check if we had a profile */
if (UserProfile)
{
/* Fill in the buffer */
ValueLength = swprintf(ValueBuffer,
L"%08lx",
(ULONG)DefaultLocaleId);
}
else
{
/* Fill in the buffer */
ValueLength = swprintf(ValueBuffer,
L"%04lx",
(ULONG)DefaultLocaleId & 0xFFFF);
}
/* Set the length for the registry call */
ValueLength = (ValueLength + 1) * sizeof(WCHAR);
/* Now write the actual value */
Status = ZwSetValueKey(KeyHandle,
&ValueName,
0,
REG_SZ,
ValueBuffer,
ValueLength);
}
}
}
Cleanup:
/* Close the locale key */
if (KeyHandle)
{
ObCloseHandle(KeyHandle, KernelMode);
}
/* Close the user key */
if (UserKey)
{
ObCloseHandle(UserKey, KernelMode);
}
/* Check for success */
if (NT_SUCCESS(Status))
{
/* Check if it was for a user */
if (UserProfile)
{
/* Set the session wide thread locale */
MmSetSessionLocaleId(DefaultLocaleId);
}
else
{
/* Set system locale */
PsDefaultSystemLocaleId = DefaultLocaleId;
}
}
/* Return status */
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtQueryInstallUILanguage(OUT LANGID* LanguageId)
{
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
/* Enter SEH for probing */
_SEH2_TRY
{
/* Check if we came from user mode */
if (KeGetPreviousMode() != KernelMode)
{
/* Probe the Language ID */
ProbeForWriteLangId(LanguageId);
}
/* Return it */
*LanguageId = PsInstallUILanguageId;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Get exception code */
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
/* Return status */
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtQueryDefaultUILanguage(OUT LANGID* LanguageId)
{
NTSTATUS Status;
LANGID SafeLanguageId;
PAGED_CODE();
/* Call the executive helper routine */
Status = ExpGetCurrentUserUILanguage(L"MultiUILanguageId", &SafeLanguageId);
/* Enter SEH for probing */
_SEH2_TRY
{
/* Check if we came from user mode */
if (KeGetPreviousMode() != KernelMode)
{
/* Probe the Language ID */
ProbeForWriteLangId(LanguageId);
}
if (NT_SUCCESS(Status))
{
/* Success, return the language */
*LanguageId = SafeLanguageId;
}
else
{
/* Failed, use fallback value */
// NOTE: Windows doesn't use PsDefaultUILanguageId.
*LanguageId = PsInstallUILanguageId;
}
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Return exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
/* Return success */
return STATUS_SUCCESS;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtSetDefaultUILanguage(IN LANGID LanguageId)
{
NTSTATUS Status;
PAGED_CODE();
/* Check if the caller specified a language id */
if (LanguageId)
{
/* Set the pending MUI language id */
Status = ExpSetCurrentUserUILanguage(L"MUILanguagePending", LanguageId);
}
else
{
/* Otherwise get the pending MUI language id */
Status = ExpGetCurrentUserUILanguage(L"MUILanguagePending", &LanguageId);
if (!NT_SUCCESS(Status))
{
return Status;
}
/* And apply it as actual */
Status = ExpSetCurrentUserUILanguage(L"MultiUILanguageId", LanguageId);
}
return Status;
}
/* EOF */