From 222ace7c6c014741217b8e4ff73ad4bbdf7a3139 Mon Sep 17 00:00:00 2001 From: Pierre Schweitzer Date: Thu, 20 Jun 2019 09:00:07 +0200 Subject: [PATCH] [BASESRV] Implement LUID mapped drive arrival/removal notification CORE-16114 --- subsystems/win/basesrv/basesrv.h | 2 + subsystems/win/basesrv/dosdev.c | 362 ++++++++++++++++++++++++++++++- subsystems/win/basesrv/init.c | 10 +- 3 files changed, 367 insertions(+), 7 deletions(-) diff --git a/subsystems/win/basesrv/basesrv.h b/subsystems/win/basesrv/basesrv.h index 67b73a6da69..c758ab435c5 100644 --- a/subsystems/win/basesrv/basesrv.h +++ b/subsystems/win/basesrv/basesrv.h @@ -16,6 +16,7 @@ #define COM_NO_WINDOWS_H #include #include +#include #define NTOS_MODE_USER #include #include @@ -67,6 +68,7 @@ extern HANDLE BaseSrvSharedHeap; extern PBASE_STATIC_SERVER_DATA BaseStaticServerData; extern ULONG SessionId; extern ULONG ProtectionMode; +extern RTL_CRITICAL_SECTION BaseSrvDDDBSMCritSec; #define SM_REG_KEY \ L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Session Manager" diff --git a/subsystems/win/basesrv/dosdev.c b/subsystems/win/basesrv/dosdev.c index d13edb4ac19..83a01242e73 100644 --- a/subsystems/win/basesrv/dosdev.c +++ b/subsystems/win/basesrv/dosdev.c @@ -13,9 +13,21 @@ #define NDEBUG #include +typedef struct _BSM_REQUEST +{ + struct _BSM_REQUEST * Next; + LUID BroadcastLuid; + LONG DriveLetter; + LONG RemoveDefinition; +} BSM_REQUEST, *PBSM_REQUEST; + /* GLOBALS ********************************************************************/ static RTL_CRITICAL_SECTION BaseDefineDosDeviceCritSec; +RTL_CRITICAL_SECTION BaseSrvDDDBSMCritSec; +PBSM_REQUEST BSM_Request_Queue = NULL, BSM_Request_Queue_End = NULL; +ULONG BaseSrvpBSMThreadCount = 0; +LONG (WINAPI *PBROADCASTSYSTEMMESSAGEEXW)(DWORD, LPDWORD, UINT, WPARAM, LPARAM, PBSMINFO) = NULL; /* PRIVATE FUNCTIONS **********************************************************/ @@ -146,6 +158,317 @@ IsGlobalSymbolicLink(HANDLE LinkHandle, return Status; } +BOOLEAN +CheckForGlobalDriveLetter(SHORT DriveLetter) +{ + WCHAR Path[8]; + NTSTATUS Status; + BOOLEAN IsGlobal; + UNICODE_STRING PathU; + HANDLE SymbolicLinkHandle; + OBJECT_ATTRIBUTES ObjectAttributes; + + /* Setup our drive path */ + wcsncpy(Path, L"\\??\\X:", (sizeof(L"\\??\\X:") / sizeof(WCHAR))); + Path[4] = DriveLetter + L'A'; + Path[6] = UNICODE_NULL; + + /* Prepare everything to open the link */ + RtlInitUnicodeString(&PathU, Path); + InitializeObjectAttributes(&ObjectAttributes, + &PathU, + OBJ_CASE_INSENSITIVE, + NULL, + NULL); + + /* Impersonate the caller */ + if (!CsrImpersonateClient(NULL)) + { + return FALSE; + } + + /* Open our drive letter */ + Status = NtOpenSymbolicLinkObject(&SymbolicLinkHandle, + SYMBOLIC_LINK_QUERY, + &ObjectAttributes); + + CsrRevertToSelf(); + + if (!NT_SUCCESS(Status)) + { + return FALSE; + } + + /* Check whether it's global */ + Status = IsGlobalSymbolicLink(SymbolicLinkHandle, &IsGlobal); + NtClose(SymbolicLinkHandle); + + if (!NT_SUCCESS(Status)) + { + return FALSE; + } + + return IsGlobal; +} + +NTSTATUS +SendWinStationBSM(DWORD Flags, + LPDWORD Recipients, + UINT Message, + WPARAM wParam, + LPARAM lParam) +{ + UNIMPLEMENTED; + return STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS +BroadcastDriveLetterChange(LONG DriveLetter, + BOOLEAN RemoveDefinition, + PLUID BroadcastLuid) +{ + HANDLE hUser32; + NTSTATUS Status; + UNICODE_STRING User32U; + ANSI_STRING ProcedureName; + DWORD Recipients, Flags, wParam; + LUID SystemLuid = SYSTEM_LUID; + BSMINFO Info; + DEV_BROADCAST_VOLUME Volume; + + /* We need a broadcast LUID */ + if (BroadcastLuid == NULL) + { + return STATUS_INVALID_PARAMETER; + } + + /* Get the Csr procedure, and keep it forever */ + if (PBROADCASTSYSTEMMESSAGEEXW == NULL) + { + hUser32 = NULL; + RtlInitUnicodeString(&User32U, L"user32"); + Status = LdrGetDllHandle(NULL, NULL, &User32U, &hUser32); + if (hUser32 != NULL && NT_SUCCESS(Status)) + { + RtlInitString(&ProcedureName, "CsrBroadcastSystemMessageExW"); + Status = LdrGetProcedureAddress(hUser32, + &ProcedureName, + 0, + (PVOID *)&PBROADCASTSYSTEMMESSAGEEXW); + if (!NT_SUCCESS(Status)) + { + PBROADCASTSYSTEMMESSAGEEXW = NULL; + } + } + + /* If we failed to get broadcast procedure, no more actions left */ + if (PBROADCASTSYSTEMMESSAGEEXW == NULL) + { + return Status; + } + } + + /* Initialize broadcast info */ + Info.cbSize = sizeof(BSMINFO); + Info.hdesk = 0; + Info.hwnd = 0; + RtlCopyLuid(&Info.luid, BroadcastLuid); + + /* Initialize volume information */ + Volume.dbcv_size = sizeof(DEV_BROADCAST_VOLUME); + Volume.dbcv_devicetype = DBT_DEVTYP_VOLUME; + Volume.dbcv_reserved = 0; + Volume.dbcv_unitmask = 1 << DriveLetter; + Volume.dbcv_flags = DBTF_NET; + + /* Wide broadcast */ + Recipients = BSM_APPLICATIONS | BSM_ALLDESKTOPS; + Flags = BSF_NOHANG | BSF_NOTIMEOUTIFNOTHUNG | BSF_FORCEIFHUNG; + + /* + * If we don't broadcast as system, it's not a global drive + * notification, then mark it as LUID mapped drive + */ + if (!RtlEqualLuid(&Info.luid, &SystemLuid)) + { + Flags |= BSF_LUID; + } + + /* Set event type */ + wParam = RemoveDefinition ? DBT_DEVICEREMOVECOMPLETE : DBT_DEVICEARRIVAL; + + /* And broadcast! */ + Status = PBROADCASTSYSTEMMESSAGEEXW(Flags, &Recipients, WM_DEVICECHANGE, wParam, (LPARAM)&Volume, &Info); + + /* If the drive is global, notify Winsta */ + if (!(Flags & BSF_LUID)) + { + Status = SendWinStationBSM(Flags, &Recipients, WM_DEVICECHANGE, wParam, (LPARAM)&Volume); + } + + return Status; +} + +ULONG +NTAPI +BaseSrvBSMThread(PVOID StartupContext) +{ + ULONG ExitStatus; + NTSTATUS Status; + PBSM_REQUEST CurrentRequest; + + /* We have a thread */ + ExitStatus = 0; + RtlEnterCriticalSection(&BaseSrvDDDBSMCritSec); + ++BaseSrvpBSMThreadCount; + + while (TRUE) + { + /* If we flushed the queue, job done */ + if (BSM_Request_Queue == NULL) + { + break; + } + + /* Queue current request, and remove it from the queue */ + CurrentRequest = BSM_Request_Queue; + BSM_Request_Queue = BSM_Request_Queue->Next; + + /* If that was the last request, NULLify queue end */ + if (BSM_Request_Queue == NULL) + { + BSM_Request_Queue_End = NULL; + } + + RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec); + + /* Broadcast the message */ + Status = BroadcastDriveLetterChange(CurrentRequest->DriveLetter, + CurrentRequest->RemoveDefinition, + &CurrentRequest->BroadcastLuid); + + /* Reflect the last entry status on stop */ + CurrentRequest->Next = NULL; + ExitStatus = Status; + + RtlFreeHeap(BaseSrvHeap, 0, CurrentRequest); + RtlEnterCriticalSection(&BaseSrvDDDBSMCritSec); + } + + /* Here, we've flushed the queue, quit the user thread */ + --BaseSrvpBSMThreadCount; + RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec); + + NtCurrentTeb()->FreeStackOnTermination = TRUE; + NtTerminateThread(NtCurrentThread(), ExitStatus); + + return ExitStatus; +} + +NTSTATUS +CreateBSMThread(VOID) +{ + /* This can only be true for LUID mappings */ + if (BaseStaticServerData->LUIDDeviceMapsEnabled == 0) + { + return STATUS_ACCESS_DENIED; + } + + /* Create our user thread */ + return RtlCreateUserThread(NtCurrentProcess(), + NULL, + FALSE, + 0, + 0, + 0, + BaseSrvBSMThread, + NULL, + NULL, + NULL); +} + +NTSTATUS +AddBSMRequest(LONG DriveLetter, + BOOLEAN RemoveDefinition, + PLUID BroadcastLuid) +{ + LUID CallerLuid; + NTSTATUS Status; + LUID SystemLuid = SYSTEM_LUID; + PBSM_REQUEST Request; + + /* We need a broadcast LUID */ + if (BroadcastLuid == NULL) + { + return STATUS_INVALID_PARAMETER; + } + + /* + * If LUID mappings are not enabled, this call makes no sense + * It should not happen though + */ + if (BaseStaticServerData->LUIDDeviceMapsEnabled == 0) + { + return STATUS_ACCESS_DENIED; + } + + /* Get our caller LUID (not the broadcaster!) */ + Status = GetCallerLuid(&CallerLuid); + if (!NT_SUCCESS(Status)) + { + return Status; + } + + /* System cannot create LUID mapped drives - thus broadcast makes no sense */ + if (!RtlEqualLuid(&CallerLuid, &SystemLuid)) + { + return STATUS_ACCESS_DENIED; + } + + /* Allocate our request */ + Request = RtlAllocateHeap(BaseSrvHeap, 0, sizeof(BSM_REQUEST)); + if (Request == NULL) + { + return STATUS_NO_MEMORY; + } + + /* Initialize it */ + Request->DriveLetter = DriveLetter; + Request->RemoveDefinition = RemoveDefinition; + RtlCopyLuid(&Request->BroadcastLuid, BroadcastLuid); + Request->Next = NULL; + + /* And queue it */ + RtlEnterCriticalSection(&BaseSrvDDDBSMCritSec); + + /* At the end of the queue if not empty */ + if (BSM_Request_Queue_End != NULL) + { + BSM_Request_Queue_End->Next = Request; + } + /* Otherwise, initialize the queue */ + else + { + BSM_Request_Queue = Request; + } + + /* We're in FIFO mode */ + BSM_Request_Queue_End = Request; + + /* If we don't have a messaging thread running, then start one */ + if (BaseSrvpBSMThreadCount >= 1) + { + RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec); + } + else + { + RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec); + Status = CreateBSMThread(); + } + + return Status; +} + /* PUBLIC SERVER APIS *********************************************************/ CSR_API(BaseSrvDefineDosDevice) @@ -167,7 +490,7 @@ CSR_API(BaseSrvDefineDosDevice) BOOLEAN DriveLetter = FALSE; BOOLEAN RemoveDefinition; BOOLEAN HandleTarget; - BOOLEAN HandleSMB = FALSE; + BOOLEAN Broadcast = FALSE; BOOLEAN IsGlobal = FALSE; ULONG CchLengthLeft; ULONG CchLength; @@ -297,13 +620,19 @@ CSR_API(BaseSrvDefineDosDevice) _SEH2_LEAVE; } - /* While impersonating the caller, also get its LUID */ + /* + * While impersonating the caller, also get its LUID. + * This is mandatory in case we have a driver letter, + * Because we're in the case we've got LUID mapping + * enabled and broadcasting enabled. LUID will be required + * for the latter + */ if (DriveLetter) { Status = GetCallerLuid(&CallerLuid); if (NT_SUCCESS(Status)) { - HandleSMB = TRUE; + Broadcast = TRUE; } } @@ -744,10 +1073,31 @@ CSR_API(BaseSrvDefineDosDevice) /* Free our internal buffer */ RtlFreeHeap(BaseSrvHeap, 0, lpBuffer); - /* Handle SMB */ - if (DriveLetter && Status == STATUS_SUCCESS && HandleSMB) + /* Broadcast drive letter creation */ + if (DriveLetter && Status == STATUS_SUCCESS && Broadcast) { - UNIMPLEMENTED; + LUID SystemLuid = SYSTEM_LUID; + + /* If that's a global drive, broadcast as system */ + if (IsGlobal) + { + RtlCopyLuid(&CallerLuid, &SystemLuid); + } + + /* Broadcast the event */ + AddBSMRequest(AbsLetter, RemoveDefinition, &CallerLuid); + + /* + * If we removed drive, and the drive was shadowing a global one + * broadcast the arrival of the global drive (as system - global) + */ + if (RemoveDefinition && !RtlEqualLuid(&CallerLuid, &SystemLuid)) + { + if (CheckForGlobalDriveLetter(AbsLetter)) + { + AddBSMRequest(AbsLetter, FALSE, &CallerLuid); + } + } } /* Done! */ diff --git a/subsystems/win/basesrv/init.c b/subsystems/win/basesrv/init.c index a1bf0f8c021..0768f69e76a 100644 --- a/subsystems/win/basesrv/init.c +++ b/subsystems/win/basesrv/init.c @@ -580,7 +580,10 @@ BaseInitializeStaticServerData(IN PCSR_SERVER_DLL LoadedServerDll) NULL); ASSERT(NT_SUCCESS(Status)); BaseStaticServerData->LUIDDeviceMapsEnabled = (BOOLEAN)LuidEnabled; - if (!BaseStaticServerData->LUIDDeviceMapsEnabled) + + /* Initialize Global */ + if (!BaseStaticServerData->LUIDDeviceMapsEnabled || + NT_SUCCESS(RtlInitializeCriticalSectionAndSpinCount(&BaseSrvDDDBSMCritSec, 0x80000000))) { /* Make Global point back to BNO */ RtlInitUnicodeString(&DirectoryName, L"Global"); @@ -638,6 +641,11 @@ BaseInitializeStaticServerData(IN PCSR_SERVER_DLL LoadedServerDll) &ObjectAttributes); ASSERT(NT_SUCCESS(Status)); } + else + { + /* That should never happen */ + ASSERT(FALSE); + } /* Initialize NLS */ BaseSrvNLSInit(BaseStaticServerData); -- 2.17.1