mirror of
https://github.com/reactos/reactos.git
synced 2024-08-20 18:35:32 +00:00
![Hermès Bélusca-Maïto](/assets/img/avatar_default.png)
Addendum to commit 0a392b18
.
The actual problem that existed all along was that the buffers being
validated with CsrValidateMessageBuffer() were not the correct ones!
What had to be checked is the string buffer **INSIDE** the UNICODE_STRING
structures! Indeed, it is these buffers that we are allocating on client side,
see https://github.com/reactos/reactos/blob/9b421af1/dll/win32/kernel32/client/dosdev.c#L324-L336
Dedicated to Pierre Schweitzer.
1111 lines
35 KiB
C
1111 lines
35 KiB
C
/*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS Base API Server DLL
|
|
* FILE: subsystems/win/basesrv/dosdev.c
|
|
* PURPOSE: DOS Devices Management
|
|
* PROGRAMMERS: Pierre Schweitzer (pierre.schweitzer@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES *******************************************************************/
|
|
|
|
#include "basesrv.h"
|
|
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
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 **********************************************************/
|
|
|
|
VOID BaseInitDefineDosDevice(VOID)
|
|
{
|
|
RtlInitializeCriticalSection(&BaseDefineDosDeviceCritSec);
|
|
}
|
|
|
|
VOID BaseCleanupDefineDosDevice(VOID)
|
|
{
|
|
RtlDeleteCriticalSection(&BaseDefineDosDeviceCritSec);
|
|
}
|
|
|
|
NTSTATUS
|
|
GetCallerLuid(PLUID CallerLuid)
|
|
{
|
|
NTSTATUS Status;
|
|
HANDLE TokenHandle;
|
|
ULONG ReturnLength;
|
|
TOKEN_STATISTICS TokenInformation;
|
|
|
|
/* We need an output buffer */
|
|
if (CallerLuid == NULL)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Open thread token */
|
|
TokenHandle = 0;
|
|
ReturnLength = 0;
|
|
Status = NtOpenThreadToken(NtCurrentThread(),
|
|
READ_CONTROL | TOKEN_QUERY,
|
|
FALSE, &TokenHandle);
|
|
/* If we fail, open process token */
|
|
if (Status == STATUS_NO_TOKEN)
|
|
{
|
|
Status = NtOpenProcessToken(NtCurrentProcess(),
|
|
READ_CONTROL | TOKEN_QUERY,
|
|
&TokenHandle);
|
|
}
|
|
|
|
/* In case of a success get caller LUID and copy it back */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = NtQueryInformationToken(TokenHandle,
|
|
TokenStatistics,
|
|
&TokenInformation,
|
|
sizeof(TokenInformation),
|
|
&ReturnLength);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
RtlCopyLuid(CallerLuid, &TokenInformation.AuthenticationId);
|
|
}
|
|
}
|
|
|
|
/* Close token handle */
|
|
if (TokenHandle != 0)
|
|
{
|
|
NtClose(TokenHandle);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
IsGlobalSymbolicLink(HANDLE LinkHandle,
|
|
PBOOLEAN IsGlobal)
|
|
{
|
|
NTSTATUS Status;
|
|
DWORD ReturnLength;
|
|
UNICODE_STRING GlobalString;
|
|
OBJECT_NAME_INFORMATION NameInfo, *PNameInfo;
|
|
|
|
/* We need both parameters */
|
|
if (LinkHandle == 0 || IsGlobal == NULL)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
PNameInfo = NULL;
|
|
_SEH2_TRY
|
|
{
|
|
/* Query handle information */
|
|
Status = NtQueryObject(LinkHandle,
|
|
ObjectNameInformation,
|
|
&NameInfo,
|
|
0,
|
|
&ReturnLength);
|
|
/* Only failure we tolerate is length mismatch */
|
|
if (NT_SUCCESS(Status) || Status == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
/* Allocate big enough buffer */
|
|
PNameInfo = RtlAllocateHeap(BaseSrvHeap, 0, ReturnLength);
|
|
if (PNameInfo == NULL)
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* Query again handle information */
|
|
Status = NtQueryObject(LinkHandle,
|
|
ObjectNameInformation,
|
|
PNameInfo,
|
|
ReturnLength,
|
|
&ReturnLength);
|
|
|
|
/*
|
|
* If it succeed, check we have Global??
|
|
* If so, return success
|
|
*/
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
RtlInitUnicodeString(&GlobalString, L"\\GLOBAL??");
|
|
*IsGlobal = RtlPrefixUnicodeString(&GlobalString, &PNameInfo->Name, FALSE);
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
_SEH2_FINALLY
|
|
{
|
|
if (PNameInfo != NULL)
|
|
{
|
|
RtlFreeHeap(BaseSrvHeap, 0, PNameInfo);
|
|
}
|
|
}
|
|
_SEH2_END;
|
|
|
|
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)
|
|
{
|
|
NTSTATUS Status;
|
|
PBASE_DEFINE_DOS_DEVICE DefineDosDeviceRequest = &((PBASE_API_MESSAGE)ApiMessage)->Data.DefineDosDeviceRequest;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
HANDLE LinkHandle;
|
|
UNICODE_STRING DeviceName = {0};
|
|
UNICODE_STRING LinkTarget = {0};
|
|
ULONG Length;
|
|
SID_IDENTIFIER_AUTHORITY WorldAuthority = {SECURITY_WORLD_SID_AUTHORITY};
|
|
SID_IDENTIFIER_AUTHORITY SystemAuthority = {SECURITY_NT_AUTHORITY};
|
|
PSID SystemSid;
|
|
PSID WorldSid;
|
|
PWSTR lpBuffer;
|
|
WCHAR Letter;
|
|
SHORT AbsLetter;
|
|
BOOLEAN DriveLetter = FALSE;
|
|
BOOLEAN RemoveDefinition;
|
|
BOOLEAN HandleTarget;
|
|
BOOLEAN Broadcast = FALSE;
|
|
BOOLEAN IsGlobal = FALSE;
|
|
ULONG CchLengthLeft;
|
|
ULONG CchLength;
|
|
ULONG TargetLength;
|
|
PWSTR TargetBuffer;
|
|
PWSTR CurrentBuffer;
|
|
/* We store them on the stack, they are known in advance */
|
|
union {
|
|
SECURITY_DESCRIPTOR SecurityDescriptor;
|
|
UCHAR Buffer[20];
|
|
} SecurityDescriptor;
|
|
union {
|
|
ACL Dacl;
|
|
UCHAR Buffer[256];
|
|
} Dacl;
|
|
ACCESS_MASK AccessMask;
|
|
LUID CallerLuid;
|
|
WCHAR * CurrentPtr;
|
|
WCHAR CurrentChar;
|
|
PWSTR OrigPtr;
|
|
PWSTR InterPtr;
|
|
BOOLEAN RemoveFound;
|
|
|
|
if (!CsrValidateMessageBuffer(ApiMessage,
|
|
(PVOID*)&DefineDosDeviceRequest->DeviceName.Buffer,
|
|
DefineDosDeviceRequest->DeviceName.Length,
|
|
sizeof(BYTE)) ||
|
|
(DefineDosDeviceRequest->DeviceName.Length & 1) != 0 ||
|
|
!CsrValidateMessageBuffer(ApiMessage,
|
|
(PVOID*)&DefineDosDeviceRequest->TargetPath.Buffer,
|
|
DefineDosDeviceRequest->TargetPath.Length +
|
|
(DefineDosDeviceRequest->TargetPath.Length != 0
|
|
? sizeof(UNICODE_NULL) : 0),
|
|
sizeof(BYTE)) ||
|
|
(DefineDosDeviceRequest->TargetPath.Length & 1) != 0)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
DPRINT("BaseSrvDefineDosDevice entered, Flags:%d, DeviceName:%wZ (%d), TargetPath:%wZ (%d)\n",
|
|
DefineDosDeviceRequest->Flags,
|
|
&DefineDosDeviceRequest->DeviceName,
|
|
DefineDosDeviceRequest->DeviceName.Length,
|
|
&DefineDosDeviceRequest->TargetPath,
|
|
DefineDosDeviceRequest->TargetPath.Length);
|
|
|
|
/*
|
|
* Allocate a buffer big enough to contain:
|
|
* - device name
|
|
* - targets
|
|
*/
|
|
lpBuffer = RtlAllocateHeap(BaseSrvHeap, 0, 0x2000);
|
|
if (lpBuffer == NULL)
|
|
{
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
/* Enter our critical section */
|
|
Status = RtlEnterCriticalSection(&BaseDefineDosDeviceCritSec);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("RtlEnterCriticalSection() failed (Status %lx)\n",
|
|
Status);
|
|
RtlFreeHeap(BaseSrvHeap, 0, lpBuffer);
|
|
return Status;
|
|
}
|
|
|
|
LinkHandle = 0;
|
|
/* Does the caller wants to remove definition? */
|
|
RemoveDefinition = !!(DefineDosDeviceRequest->Flags & DDD_REMOVE_DEFINITION);
|
|
_SEH2_TRY
|
|
{
|
|
/* First of all, check if that's a drive letter device amongst LUID mappings */
|
|
if (BaseStaticServerData->LUIDDeviceMapsEnabled && !(DefineDosDeviceRequest->Flags & DDD_NO_BROADCAST_SYSTEM))
|
|
{
|
|
if (DefineDosDeviceRequest->DeviceName.Buffer != NULL &&
|
|
DefineDosDeviceRequest->DeviceName.Length == 2 * sizeof(WCHAR) &&
|
|
DefineDosDeviceRequest->DeviceName.Buffer[1] == L':')
|
|
{
|
|
Letter = DefineDosDeviceRequest->DeviceName.Buffer[0];
|
|
|
|
/* Handle both lower cases and upper cases */
|
|
AbsLetter = Letter - L'a';
|
|
if (AbsLetter < 26 && AbsLetter >= 0)
|
|
{
|
|
Letter = RtlUpcaseUnicodeChar(Letter);
|
|
}
|
|
|
|
AbsLetter = Letter - L'A';
|
|
if (AbsLetter < 26)
|
|
{
|
|
/* That's a letter! */
|
|
DriveLetter = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We can only broadcast drive letters in case of LUID mappings */
|
|
if (DefineDosDeviceRequest->Flags & DDD_LUID_BROADCAST_DRIVE &&
|
|
!DriveLetter)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* First usage of our buffer: create device name */
|
|
CchLength = _snwprintf(lpBuffer, 0x1000, L"\\??\\%wZ", &DefineDosDeviceRequest->DeviceName);
|
|
CchLengthLeft = 0x1000 - 1 - CchLength; /* UNICODE_NULL */
|
|
CurrentBuffer = lpBuffer + CchLength + 1; /* UNICODE_NULL */
|
|
RtlInitUnicodeString(&DeviceName, lpBuffer);
|
|
|
|
/* And prepare to open it */
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&DeviceName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
/* Assume it's OK and has a target to deal with */
|
|
HandleTarget = TRUE;
|
|
|
|
/* Move to the client context if the mapping was local */
|
|
if (!CsrImpersonateClient(NULL))
|
|
{
|
|
Status = STATUS_BAD_IMPERSONATION_LEVEL;
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/*
|
|
* 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))
|
|
{
|
|
Broadcast = TRUE;
|
|
}
|
|
}
|
|
|
|
/* Now, open the device */
|
|
Status = NtOpenSymbolicLinkObject(&LinkHandle,
|
|
DELETE | SYMBOLIC_LINK_QUERY,
|
|
&ObjectAttributes);
|
|
|
|
/* And get back to our context */
|
|
CsrRevertToSelf();
|
|
|
|
/* In case of LUID broadcast, do nothing but return to trigger broadcast */
|
|
if (DefineDosDeviceRequest->Flags & DDD_LUID_BROADCAST_DRIVE)
|
|
{
|
|
/* Zero handle in case of a failure */
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
LinkHandle = 0;
|
|
}
|
|
|
|
/* If removal was asked, and no object found: the remval was successful */
|
|
if (RemoveDefinition && Status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
/* We're done here, nothing more to do */
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* If device was not found */
|
|
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
/* No handle */
|
|
LinkHandle = 0;
|
|
|
|
/* If we were asked to remove... */
|
|
if (RemoveDefinition)
|
|
{
|
|
/*
|
|
* If caller asked to pop first entry, nothing specific,
|
|
* then, we can consider this as a success
|
|
*/
|
|
if (DefineDosDeviceRequest->TargetPath.Length == 0)
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
/* We're done, nothing to change */
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* There's no target to handle */
|
|
HandleTarget = FALSE;
|
|
|
|
/*
|
|
* We'll consider, that's a success
|
|
* Failing to open the device doesn't prevent
|
|
* from creating it later on to create
|
|
* the linking.
|
|
*/
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
/* Unexpected failure, forward to caller */
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* If LUID mapping enabled */
|
|
if (BaseStaticServerData->LUIDDeviceMapsEnabled)
|
|
{
|
|
/* Check if that's global link */
|
|
Status = IsGlobalSymbolicLink(LinkHandle, &IsGlobal);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* If so, change our device name namespace to GLOBAL?? for link creation */
|
|
if (IsGlobal)
|
|
{
|
|
CchLength = _snwprintf(lpBuffer, 0x1000, L"\\GLOBAL??\\%wZ", &DefineDosDeviceRequest->DeviceName);
|
|
CchLengthLeft = 0x1000 - 1 - CchLength; /* UNICODE_NULL */
|
|
CurrentBuffer = lpBuffer + CchLength + 1; /* UNICODE_NULL */
|
|
|
|
DeviceName.Length = CchLength * sizeof(WCHAR);
|
|
DeviceName.MaximumLength = CchLength * sizeof(WCHAR) + sizeof(UNICODE_NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If caller provided a target */
|
|
if (DefineDosDeviceRequest->TargetPath.Length != 0)
|
|
{
|
|
/* Make sure it's null terminated */
|
|
DefineDosDeviceRequest->TargetPath.Buffer[DefineDosDeviceRequest->TargetPath.Length / sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
/* Compute its size */
|
|
TargetLength = wcslen(DefineDosDeviceRequest->TargetPath.Buffer);
|
|
|
|
/* And make sure it fits our buffer */
|
|
if (TargetLength + 1 >= CchLengthLeft)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* Copy it to our internal buffer */
|
|
RtlMoveMemory(CurrentBuffer, DefineDosDeviceRequest->TargetPath.Buffer, TargetLength * sizeof(WCHAR) + sizeof(UNICODE_NULL));
|
|
TargetBuffer = CurrentBuffer;
|
|
|
|
/* Update our buffer status */
|
|
CchLengthLeft -= (TargetLength + 1);
|
|
CurrentBuffer += (TargetLength + 1);
|
|
}
|
|
/* Otherwise, zero everything */
|
|
else
|
|
{
|
|
TargetBuffer = NULL;
|
|
TargetLength = 0;
|
|
}
|
|
|
|
/* If we opened the device, then, handle its current target */
|
|
if (HandleTarget)
|
|
{
|
|
/* Query it with our internal buffer */
|
|
LinkTarget.Length = 0;
|
|
LinkTarget.MaximumLength = CchLengthLeft * sizeof(WCHAR);
|
|
LinkTarget.Buffer = CurrentBuffer;
|
|
|
|
Status = NtQuerySymbolicLinkObject(LinkHandle,
|
|
&LinkTarget,
|
|
&Length);
|
|
/* If we overflow, give up */
|
|
if (Length == LinkTarget.MaximumLength)
|
|
{
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
/* In case of a failure, bye bye */
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/*
|
|
* Properly null it for MULTI_SZ if needed
|
|
* Always update max length with
|
|
* the need size
|
|
* This is needed to hand relatively "small"
|
|
* strings to Ob and avoid killing ourselves
|
|
* on the next query
|
|
*/
|
|
CchLength = Length / sizeof(WCHAR);
|
|
if (CchLength < 2 ||
|
|
CurrentBuffer[CchLength - 2] != UNICODE_NULL ||
|
|
CurrentBuffer[CchLength - 1] != UNICODE_NULL)
|
|
{
|
|
CurrentBuffer[CchLength] = UNICODE_NULL;
|
|
LinkTarget.MaximumLength = Length + sizeof(UNICODE_NULL);
|
|
}
|
|
else
|
|
{
|
|
LinkTarget.MaximumLength = Length;
|
|
}
|
|
}
|
|
/* There's no target, and we're asked to remove, so null target */
|
|
else if (RemoveDefinition)
|
|
{
|
|
RtlInitUnicodeString(&LinkTarget, NULL);
|
|
}
|
|
/* There's a target provided - new device, update buffer */
|
|
else
|
|
{
|
|
RtlInitUnicodeString(&LinkTarget, CurrentBuffer - TargetLength - 1);
|
|
}
|
|
|
|
/*
|
|
* We no longer need old symlink, just drop it, we'll recreate it now
|
|
* with updated target.
|
|
* The benefit of it is that if caller asked us to drop last target, then
|
|
* the device is removed and not dangling
|
|
*/
|
|
if (LinkHandle != 0)
|
|
{
|
|
Status = NtMakeTemporaryObject(LinkHandle);
|
|
NtClose(LinkHandle);
|
|
LinkHandle = 0;
|
|
}
|
|
|
|
/* At this point, we must have no failure */
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/*
|
|
* If we have to remove definition, let's start to browse our
|
|
* target to actually drop it.
|
|
*/
|
|
if (RemoveDefinition)
|
|
{
|
|
/* We'll browse our multi sz string */
|
|
RemoveFound = FALSE;
|
|
CurrentPtr = LinkTarget.Buffer;
|
|
InterPtr = LinkTarget.Buffer;
|
|
while (*CurrentPtr != UNICODE_NULL)
|
|
{
|
|
CchLength = 0;
|
|
OrigPtr = CurrentPtr;
|
|
/* First, find next string */
|
|
while (TRUE)
|
|
{
|
|
CurrentChar = *CurrentPtr;
|
|
++CurrentPtr;
|
|
|
|
if (CurrentChar == UNICODE_NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
++CchLength;
|
|
}
|
|
|
|
/* This check is a bit tricky, but dead useful:
|
|
* If on the previous loop, we found the caller provided target
|
|
* in our list, then, we'll move current entry over the found one
|
|
* So that, it gets deleted.
|
|
* Also, if we don't find caller entry in our entries, then move
|
|
* current entry in the string if a previous one got deleted
|
|
*/
|
|
if (RemoveFound ||
|
|
((!(DefineDosDeviceRequest->Flags & DDD_EXACT_MATCH_ON_REMOVE) ||
|
|
TargetLength != CchLength || _wcsicmp(OrigPtr, TargetBuffer) != 0) &&
|
|
((DefineDosDeviceRequest->Flags & DDD_EXACT_MATCH_ON_REMOVE) ||
|
|
(TargetLength != 0 && _wcsnicmp(OrigPtr, TargetBuffer, TargetLength) != 0))))
|
|
{
|
|
if (InterPtr != OrigPtr)
|
|
{
|
|
RtlMoveMemory(InterPtr, OrigPtr, sizeof(WCHAR) * CchLength + sizeof(UNICODE_NULL));
|
|
}
|
|
|
|
InterPtr += (CchLength + 1);
|
|
}
|
|
else
|
|
{
|
|
/* Match case! Remember for next loop turn and to delete it */
|
|
RemoveFound = TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Drop last entry, as required (pop)
|
|
* If there was a match previously, everything
|
|
* is already moved, so we're just nulling
|
|
* the end of the string
|
|
* If there was no match, this is the pop
|
|
*/
|
|
*InterPtr = UNICODE_NULL;
|
|
++InterPtr;
|
|
|
|
/* Compute new target length */
|
|
TargetLength = wcslen(LinkTarget.Buffer) * sizeof(WCHAR);
|
|
/*
|
|
* If it's empty, quit
|
|
* Beware, here, we quit with STATUS_SUCCESS, and that's expected!
|
|
* In case we dropped last target entry, then, it's empty
|
|
* and there's no need to recreate the device we deleted previously
|
|
*/
|
|
if (TargetLength == 0)
|
|
{
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* Update our target string */
|
|
LinkTarget.Length = TargetLength;
|
|
LinkTarget.MaximumLength = (ULONG_PTR)InterPtr - (ULONG_PTR)LinkTarget.Buffer;
|
|
}
|
|
/* If that's not a removal, just update the target to include new target */
|
|
else if (HandleTarget)
|
|
{
|
|
LinkTarget.Buffer = LinkTarget.Buffer - TargetLength - 1;
|
|
LinkTarget.Length = TargetLength * sizeof(WCHAR);
|
|
LinkTarget.MaximumLength += (TargetLength * sizeof(WCHAR) + sizeof(UNICODE_NULL));
|
|
TargetLength *= sizeof(WCHAR);
|
|
}
|
|
/* No changes */
|
|
else
|
|
{
|
|
TargetLength = LinkTarget.Length;
|
|
}
|
|
|
|
/* Make sure we don't create empty symlink */
|
|
if (TargetLength == 0)
|
|
{
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* Initialize our SIDs for symlink ACLs */
|
|
Status = RtlAllocateAndInitializeSid(&WorldAuthority,
|
|
1,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
&WorldSid);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
Status = RtlAllocateAndInitializeSid(&SystemAuthority,
|
|
1,
|
|
SECURITY_RESTRICTED_CODE_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
SECURITY_NULL_RID,
|
|
&SystemSid);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
RtlFreeSid(WorldSid);
|
|
_SEH2_LEAVE;
|
|
}
|
|
|
|
/* Initialize our SD (on stack) */
|
|
RtlCreateSecurityDescriptor(&SecurityDescriptor,
|
|
SECURITY_DESCRIPTOR_REVISION);
|
|
|
|
/* And our ACL (still on stack) */
|
|
RtlCreateAcl(&Dacl.Dacl, sizeof(Dacl), ACL_REVISION);
|
|
|
|
/*
|
|
* For access mask, if we have no session ID, or if
|
|
* protection mode is disabled, make them wide open
|
|
*/
|
|
if (SessionId == 0 ||
|
|
(ProtectionMode & 3) == 0)
|
|
{
|
|
AccessMask = DELETE | SYMBOLIC_LINK_QUERY;
|
|
}
|
|
else
|
|
{
|
|
AccessMask = SYMBOLIC_LINK_QUERY;
|
|
}
|
|
|
|
/* Setup the ACL */
|
|
RtlAddAccessAllowedAce(&Dacl.Dacl, ACL_REVISION2, AccessMask, WorldSid);
|
|
RtlAddAccessAllowedAce(&Dacl.Dacl, ACL_REVISION2, AccessMask, SystemSid);
|
|
|
|
/* Drop SIDs */
|
|
RtlFreeSid(WorldSid);
|
|
RtlFreeSid(SystemSid);
|
|
|
|
/* Link DACL to the SD */
|
|
RtlSetDaclSecurityDescriptor(&SecurityDescriptor, TRUE, &Dacl.Dacl, TRUE);
|
|
|
|
/* And set it in the OA used for creation */
|
|
ObjectAttributes.SecurityDescriptor = &SecurityDescriptor;
|
|
|
|
/*
|
|
* If LUID and not global, we need to impersonate the caller
|
|
* to make it local.
|
|
*/
|
|
if (BaseStaticServerData->LUIDDeviceMapsEnabled)
|
|
{
|
|
if (!IsGlobal)
|
|
{
|
|
if (!CsrImpersonateClient(NULL))
|
|
{
|
|
Status = STATUS_BAD_IMPERSONATION_LEVEL;
|
|
_SEH2_LEAVE;
|
|
}
|
|
}
|
|
}
|
|
/* The object will be permanent */
|
|
else
|
|
{
|
|
ObjectAttributes.Attributes |= OBJ_PERMANENT;
|
|
}
|
|
|
|
/* (Re)Create the symbolic link/device */
|
|
Status = NtCreateSymbolicLinkObject(&LinkHandle,
|
|
SYMBOLIC_LINK_ALL_ACCESS,
|
|
&ObjectAttributes,
|
|
&LinkTarget);
|
|
|
|
/* Revert to self if required */
|
|
if (BaseStaticServerData->LUIDDeviceMapsEnabled && !IsGlobal)
|
|
{
|
|
CsrRevertToSelf();
|
|
}
|
|
|
|
/* In case of a success, make object permanent for LUID links */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if (BaseStaticServerData->LUIDDeviceMapsEnabled)
|
|
{
|
|
Status = NtMakePermanentObject(LinkHandle);
|
|
}
|
|
|
|
/* Close the link */
|
|
NtClose(LinkHandle);
|
|
|
|
/*
|
|
* Specific failure case here:
|
|
* We were asked to remove something
|
|
* but we didn't find the something
|
|
* (we recreated the symlink hence the fail here!)
|
|
* so fail with appropriate status
|
|
*/
|
|
if (RemoveDefinition && !RemoveFound)
|
|
{
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
/* We closed link, don't double close */
|
|
LinkHandle = 0;
|
|
}
|
|
_SEH2_FINALLY
|
|
{
|
|
/* If we need to close the link, do it now */
|
|
if (LinkHandle != 0)
|
|
{
|
|
NtClose(LinkHandle);
|
|
}
|
|
|
|
/* Free our internal buffer */
|
|
RtlFreeHeap(BaseSrvHeap, 0, lpBuffer);
|
|
|
|
/* Broadcast drive letter creation */
|
|
if (DriveLetter && Status == STATUS_SUCCESS && Broadcast)
|
|
{
|
|
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! */
|
|
RtlLeaveCriticalSection(&BaseDefineDosDeviceCritSec);
|
|
}
|
|
_SEH2_END;
|
|
|
|
return Status;
|
|
}
|
|
|
|
/* EOF */
|