From 0a392b188a1fcdabbab87f16d121249888f62964 Mon Sep 17 00:00:00 2001 From: Pierre Schweitzer Date: Wed, 8 May 2019 18:29:52 +0200 Subject: [PATCH] [BASESRV] Rewrite DOS devices management This will notably bring support for DOS mapping with LUID devices (not yet supported in the kernel, though). This also reduces complexity (and thus memory usage) with the "history" thing. Multiple targets are stored in the link target as MULTI_SZ string. This fixes regressions introduced with kernel32 fixes/rewrites. --- subsystems/win/basesrv/basesrv.h | 1 + subsystems/win/basesrv/dosdev.c | 1107 ++++++++++++++++++------------ 2 files changed, 657 insertions(+), 451 deletions(-) diff --git a/subsystems/win/basesrv/basesrv.h b/subsystems/win/basesrv/basesrv.h index 03ebb9c8e32..67b73a6da69 100644 --- a/subsystems/win/basesrv/basesrv.h +++ b/subsystems/win/basesrv/basesrv.h @@ -23,6 +23,7 @@ #include #include #include +#include /* PSEH for SEH Support */ #include diff --git a/subsystems/win/basesrv/dosdev.c b/subsystems/win/basesrv/dosdev.c index bd803eb3068..f683b46f532 100644 --- a/subsystems/win/basesrv/dosdev.c +++ b/subsystems/win/basesrv/dosdev.c @@ -15,60 +15,135 @@ /* GLOBALS ********************************************************************/ -typedef struct _BASE_DOS_DEVICE_HISTORY_ENTRY -{ - LIST_ENTRY Entry; - UNICODE_STRING Device; - UNICODE_STRING Target; -} BASE_DOS_DEVICE_HISTORY_ENTRY, *PBASE_DOS_DEVICE_HISTORY_ENTRY; - static RTL_CRITICAL_SECTION BaseDefineDosDeviceCritSec; -static LIST_ENTRY DosDeviceHistory; /* PRIVATE FUNCTIONS **********************************************************/ VOID BaseInitDefineDosDevice(VOID) { RtlInitializeCriticalSection(&BaseDefineDosDeviceCritSec); - InitializeListHead(&DosDeviceHistory); } VOID BaseCleanupDefineDosDevice(VOID) { - PLIST_ENTRY Entry, ListHead; - PBASE_DOS_DEVICE_HISTORY_ENTRY HistoryEntry; - RtlDeleteCriticalSection(&BaseDefineDosDeviceCritSec); +} - ListHead = &DosDeviceHistory; - Entry = ListHead->Flink; - while (Entry != ListHead) +NTSTATUS +GetCallerLuid(PLUID CallerLuid) +{ + NTSTATUS Status; + HANDLE TokenHandle; + ULONG ReturnLength; + TOKEN_STATISTICS TokenInformation; + + /* We need an output buffer */ + if (CallerLuid == NULL) { - HistoryEntry = (PBASE_DOS_DEVICE_HISTORY_ENTRY) - CONTAINING_RECORD(Entry, - BASE_DOS_DEVICE_HISTORY_ENTRY, - Entry); - Entry = Entry->Flink; + return STATUS_INVALID_PARAMETER; + } - if (HistoryEntry) + /* 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)) { - if (HistoryEntry->Target.Buffer) - { - RtlFreeHeap(BaseSrvHeap, - 0, - HistoryEntry->Target.Buffer); - } - if (HistoryEntry->Device.Buffer) - { - RtlFreeHeap(BaseSrvHeap, - 0, - HistoryEntry->Device.Buffer); - } - RtlFreeHeap(BaseSrvHeap, - 0, - HistoryEntry); + 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(RtlGetProcessHeap(), 0, PNameInfo); + } + } + _SEH2_END; + + return Status; } /* PUBLIC SERVER APIS *********************************************************/ @@ -78,478 +153,608 @@ CSR_API(BaseSrvDefineDosDevice) NTSTATUS Status; PBASE_DEFINE_DOS_DEVICE DefineDosDeviceRequest = &((PBASE_API_MESSAGE)ApiMessage)->Data.DefineDosDeviceRequest; OBJECT_ATTRIBUTES ObjectAttributes; - HANDLE LinkHandle = NULL; + HANDLE LinkHandle; UNICODE_STRING DeviceName = {0}; - UNICODE_STRING RequestDeviceName = {0}; UNICODE_STRING LinkTarget = {0}; - PUNICODE_STRING RequestLinkTarget; ULONG Length; SID_IDENTIFIER_AUTHORITY WorldAuthority = {SECURITY_WORLD_SID_AUTHORITY}; SID_IDENTIFIER_AUTHORITY SystemAuthority = {SECURITY_NT_AUTHORITY}; - PSECURITY_DESCRIPTOR SecurityDescriptor; - PACL Dacl; - PSID AdminSid; PSID SystemSid; PSID WorldSid; - ULONG SidLength; - PBASE_DOS_DEVICE_HISTORY_ENTRY HistoryEntry; - PLIST_ENTRY Entry; - PLIST_ENTRY ListHead; - BOOLEAN Matched, AddHistory; - DWORD dwFlags; PWSTR lpBuffer; + WCHAR Letter; + SHORT AbsLetter; + BOOLEAN DriveLetter = FALSE; + BOOLEAN RemoveDefinition; + BOOLEAN HandleTarget; + BOOLEAN HandleSMB = 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; - DPRINT("BaseSrvDefineDosDevice entered, Flags:%d, DeviceName:%wZ, TargetPath:%wZ\n", - DefineDosDeviceRequest->Flags, - &DefineDosDeviceRequest->DeviceName, - &DefineDosDeviceRequest->TargetPath); - - Matched = AddHistory = FALSE; - HistoryEntry = NULL; - AdminSid = SystemSid = WorldSid = NULL; - SecurityDescriptor = NULL; - ListHead = &DosDeviceHistory; - dwFlags = DefineDosDeviceRequest->Flags; - - /* Validate the flags */ - if ( (dwFlags & 0xFFFFFFF0) || - ((dwFlags & DDD_EXACT_MATCH_ON_REMOVE) && - !(dwFlags & DDD_REMOVE_DEFINITION)) ) +#if 0 + /* FIXME: Check why it fails.... */ + if (!CsrValidateMessageBuffer(ApiMessage, + (PVOID*)&DefineDosDeviceRequest->DeviceName, + DefineDosDeviceRequest->DeviceName.Length, + 1) || + (DefineDosDeviceRequest->DeviceName.Length & 1) != 0 || + !CsrValidateMessageBuffer(ApiMessage, + (PVOID*)&DefineDosDeviceRequest->TargetPath, + (DefineDosDeviceRequest->TargetPath.Length != 0 ? sizeof(UNICODE_NULL) : 0) + DefineDosDeviceRequest->TargetPath.Length, + 1) || + (DefineDosDeviceRequest->TargetPath.Length & 1) != 0) { return STATUS_INVALID_PARAMETER; } +#endif + DPRINT1("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 { - Status = - RtlUpcaseUnicodeString(&RequestDeviceName, - &DefineDosDeviceRequest->DeviceName, - TRUE); - if (!NT_SUCCESS(Status)) - _SEH2_LEAVE; - - RequestLinkTarget = &DefineDosDeviceRequest->TargetPath; - lpBuffer = (PWSTR)RtlAllocateHeap(BaseSrvHeap, - HEAP_ZERO_MEMORY, - RequestDeviceName.MaximumLength + 5 * sizeof(WCHAR)); - if (!lpBuffer) + /* First of all, check if that's a drive letter device amongst LUID mappings */ + if (BaseStaticServerData->LUIDDeviceMapsEnabled && !(DefineDosDeviceRequest->Flags & DDD_NO_BROADCAST_SYSTEM)) { - DPRINT1("Failed to allocate memory\n"); - Status = STATUS_NO_MEMORY; + 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; } - swprintf(lpBuffer, - L"\\??\\%wZ", - &RequestDeviceName); - RtlInitUnicodeString(&DeviceName, - lpBuffer); + /* 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 */ + if (DriveLetter) + { + Status = GetCallerLuid(&CallerLuid); + if (NT_SUCCESS(Status)) + { + HandleSMB = TRUE; + } + } + + /* Now, open the device */ Status = NtOpenSymbolicLinkObject(&LinkHandle, - DELETE | 0x1, + DELETE | SYMBOLIC_LINK_QUERY, &ObjectAttributes); - if (NT_SUCCESS(Status)) + + /* 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) { - Status = NtQuerySymbolicLinkObject(LinkHandle, - &LinkTarget, - &Length); - if (!NT_SUCCESS(Status) && - Status == STATUS_BUFFER_TOO_SMALL) - { - LinkTarget.Length = 0; - LinkTarget.MaximumLength = Length; - LinkTarget.Buffer = (PWSTR) - RtlAllocateHeap(BaseSrvHeap, - HEAP_ZERO_MEMORY, - Length); - if (!LinkTarget.Buffer) - { - DPRINT1("Failed to allocate memory\n"); - Status = STATUS_NO_MEMORY; - _SEH2_LEAVE; - } - - Status = NtQuerySymbolicLinkObject(LinkHandle, - &LinkTarget, - &Length); - } - + /* Zero handle in case of a failure */ if (!NT_SUCCESS(Status)) { - DPRINT1("NtQuerySymbolicLinkObject(%wZ) failed (Status %lx)\n", - &DeviceName, Status); - _SEH2_LEAVE; + LinkHandle = 0; } - if ((dwFlags & DDD_REMOVE_DEFINITION)) + /* If removal was asked, and no object found: the remval was successful */ + if (RemoveDefinition && Status == STATUS_OBJECT_NAME_NOT_FOUND) { - /* If no target name specified we remove the current symlink target */ - if (RequestLinkTarget->Length == 0) - Matched = TRUE; - else + 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) { - if (dwFlags & DDD_EXACT_MATCH_ON_REMOVE) - Matched = !RtlCompareUnicodeString(RequestLinkTarget, - &LinkTarget, - TRUE); - else - Matched = RtlPrefixUnicodeString(RequestLinkTarget, - &LinkTarget, - TRUE); + Status = STATUS_SUCCESS; } - if (Matched && IsListEmpty(ListHead)) - { - /* Current symlink target macthed and there is nothing to revert to */ - RequestLinkTarget = NULL; - } - else if (Matched && !IsListEmpty(ListHead)) - { - /* - * Fetch the first history entry we come across for the device name. - * This will become the current symlink target for the device name. - */ - Matched = FALSE; - Entry = ListHead->Flink; - while (Entry != ListHead) - { - HistoryEntry = (PBASE_DOS_DEVICE_HISTORY_ENTRY) - CONTAINING_RECORD(Entry, - BASE_DOS_DEVICE_HISTORY_ENTRY, - Entry); - Matched = - !RtlCompareUnicodeString(&RequestDeviceName, - &HistoryEntry->Device, - FALSE); - if (Matched) - { - RemoveEntryList(&HistoryEntry->Entry); - RequestLinkTarget = &HistoryEntry->Target; - break; - } - Entry = Entry->Flink; - HistoryEntry = NULL; - } - - /* Nothing to revert to so delete the symlink */ - if (!Matched) - RequestLinkTarget = NULL; - } - else if (!Matched) - { - /* - * Locate a previous symlink target as we did not get - * a hit earlier. If we find one we need to remove it. - */ - Entry = ListHead->Flink; - while (Entry != ListHead) - { - HistoryEntry = (PBASE_DOS_DEVICE_HISTORY_ENTRY) - CONTAINING_RECORD(Entry, - BASE_DOS_DEVICE_HISTORY_ENTRY, - Entry); - Matched = - !RtlCompareUnicodeString(&RequestDeviceName, - &HistoryEntry->Device, - FALSE); - if (!Matched) - { - HistoryEntry = NULL; - Entry = Entry->Flink; - continue; - } - - Matched = FALSE; - if (dwFlags & DDD_EXACT_MATCH_ON_REMOVE) - { - if (!RtlCompareUnicodeString(RequestLinkTarget, - &HistoryEntry->Target, - TRUE)) - { - Matched = TRUE; - } - } - else if (RtlPrefixUnicodeString(RequestLinkTarget, - &HistoryEntry->Target, - TRUE)) - { - Matched = TRUE; - } - - if (Matched) - { - RemoveEntryList(&HistoryEntry->Entry); - break; - } - Entry = Entry->Flink; - HistoryEntry = NULL; - } - - /* Leave existing symlink as is */ - if (!Matched) - Status = STATUS_OBJECT_NAME_NOT_FOUND; - else - Status = STATUS_SUCCESS; - _SEH2_LEAVE; - } - } - else - { - AddHistory = TRUE; - } - - Status = NtMakeTemporaryObject(LinkHandle); - if (!NT_SUCCESS(Status)) - { - DPRINT1("NtMakeTemporaryObject(%wZ) failed (Status %lx)\n", - &DeviceName, Status); + /* We're done, nothing to change */ _SEH2_LEAVE; } - Status = NtClose(LinkHandle); - LinkHandle = NULL; - if (!NT_SUCCESS(Status)) - { - DPRINT1("NtClose(%wZ) failed (Status %lx)\n", - &DeviceName, Status); - _SEH2_LEAVE; - } - } + /* There's no target to handle */ + HandleTarget = FALSE; - /* Don't create symlink if we don't have a target */ - if (!RequestLinkTarget || RequestLinkTarget->Length == 0) - _SEH2_LEAVE; - - if (AddHistory) - { - HistoryEntry = (PBASE_DOS_DEVICE_HISTORY_ENTRY) - RtlAllocateHeap(BaseSrvHeap, - HEAP_ZERO_MEMORY, - sizeof(BASE_DOS_DEVICE_HISTORY_ENTRY)); - if (!HistoryEntry) - { - DPRINT1("Failed to allocate memory\n"); - Status = STATUS_NO_MEMORY; - _SEH2_LEAVE; - } - - HistoryEntry->Target.Buffer = - RtlAllocateHeap(BaseSrvHeap, - HEAP_ZERO_MEMORY, - LinkTarget.Length); - if (!HistoryEntry->Target.Buffer) - { - DPRINT1("Failed to allocate memory\n"); - Status = STATUS_NO_MEMORY; - _SEH2_LEAVE; - } - HistoryEntry->Target.Length = - HistoryEntry->Target.MaximumLength = - LinkTarget.Length; - RtlCopyUnicodeString(&HistoryEntry->Target, - &LinkTarget); - - HistoryEntry->Device.Buffer = - RtlAllocateHeap(BaseSrvHeap, - HEAP_ZERO_MEMORY, - RequestDeviceName.Length); - if (!HistoryEntry->Device.Buffer) - { - DPRINT1("Failed to allocate memory\n"); - Status = STATUS_NO_MEMORY; - _SEH2_LEAVE; - } - HistoryEntry->Device.Length = - HistoryEntry->Device.MaximumLength = - RequestDeviceName.Length; - RtlCopyUnicodeString(&HistoryEntry->Device, - &RequestDeviceName); - - /* Remember previous symlink target for this device */ - InsertHeadList(ListHead, - &HistoryEntry->Entry); - HistoryEntry = NULL; - } - - RtlAllocateAndInitializeSid(&WorldAuthority, - 1, - SECURITY_WORLD_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - &WorldSid); - - RtlAllocateAndInitializeSid(&SystemAuthority, - 1, - SECURITY_LOCAL_SYSTEM_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - &SystemSid); - - RtlAllocateAndInitializeSid(&SystemAuthority, - 2, - SECURITY_BUILTIN_DOMAIN_RID, - DOMAIN_ALIAS_RID_ADMINS, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - SECURITY_NULL_RID, - &AdminSid); - - SidLength = RtlLengthSid(SystemSid) + - RtlLengthSid(AdminSid) + - RtlLengthSid(WorldSid); - Length = sizeof(ACL) + SidLength + 3 * sizeof(ACCESS_ALLOWED_ACE); - - SecurityDescriptor = RtlAllocateHeap(BaseSrvHeap, - 0, - SECURITY_DESCRIPTOR_MIN_LENGTH + Length); - if (!SecurityDescriptor) - { - DPRINT1("Failed to allocate memory\n"); - Status = STATUS_NO_MEMORY; - _SEH2_LEAVE; - } - - Dacl = (PACL)((ULONG_PTR)SecurityDescriptor + SECURITY_DESCRIPTOR_MIN_LENGTH); - Status = RtlCreateSecurityDescriptor(SecurityDescriptor, - SECURITY_DESCRIPTOR_REVISION); - if (!NT_SUCCESS(Status)) - { - DPRINT1("RtlCreateSecurityDescriptor() failed (Status %lx)\n", Status); - _SEH2_LEAVE; - } - - Status = RtlCreateAcl(Dacl, - Length, - ACL_REVISION); - if (!NT_SUCCESS(Status)) - { - DPRINT1("RtlCreateAcl() failed (Status %lx)\n", Status); - _SEH2_LEAVE; - } - - RtlAddAccessAllowedAce(Dacl, - ACL_REVISION, - GENERIC_ALL, - SystemSid); - RtlAddAccessAllowedAce(Dacl, - ACL_REVISION, - GENERIC_ALL, - AdminSid); - RtlAddAccessAllowedAce(Dacl, - ACL_REVISION, - STANDARD_RIGHTS_READ, - WorldSid); - - Status = RtlSetDaclSecurityDescriptor(SecurityDescriptor, - TRUE, - Dacl, - FALSE); - if (!NT_SUCCESS(Status)) - { - DPRINT1("RtlSetDaclSecurityDescriptor() failed (Status %lx)\n", Status); - _SEH2_LEAVE; - } - - InitializeObjectAttributes(&ObjectAttributes, - &DeviceName, - OBJ_CASE_INSENSITIVE, - NULL, - SecurityDescriptor); - Status = NtCreateSymbolicLinkObject(&LinkHandle, - SYMBOLIC_LINK_ALL_ACCESS, - &ObjectAttributes, - RequestLinkTarget); - if (NT_SUCCESS(Status)) - { - Status = NtMakePermanentObject(LinkHandle); - if (!NT_SUCCESS(Status)) - { - DPRINT1("NtMakePermanentObject(%wZ) failed (Status %lx)\n", - &DeviceName, Status); - } + /* + * 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 { - DPRINT1("NtCreateSymbolicLinkObject(%wZ) failed (Status %lx)\n", - &DeviceName, Status); + /* 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); + + /* Handle SMB */ + if (DriveLetter && Status == STATUS_SUCCESS && HandleSMB) + { + UNIMPLEMENTED; + } + + /* Done! */ RtlLeaveCriticalSection(&BaseDefineDosDeviceCritSec); - if (DeviceName.Buffer) - { - RtlFreeHeap(BaseSrvHeap, - 0, - DeviceName.Buffer); - } - if (LinkTarget.Buffer) - { - RtlFreeHeap(BaseSrvHeap, - 0, - LinkTarget.Buffer); - } - if (SecurityDescriptor) - { - RtlFreeHeap(BaseSrvHeap, - 0, - SecurityDescriptor); - } - - if (LinkHandle) NtClose(LinkHandle); - if (SystemSid) RtlFreeSid(SystemSid); - if (AdminSid) RtlFreeSid(AdminSid); - if (WorldSid) RtlFreeSid(WorldSid); - - RtlFreeUnicodeString(&RequestDeviceName); - - if (HistoryEntry) - { - if (HistoryEntry->Target.Buffer) - { - RtlFreeHeap(BaseSrvHeap, - 0, - HistoryEntry->Target.Buffer); - } - if (HistoryEntry->Device.Buffer) - { - RtlFreeHeap(BaseSrvHeap, - 0, - HistoryEntry->Device.Buffer); - } - RtlFreeHeap(BaseSrvHeap, - 0, - HistoryEntry); - } } - _SEH2_END + _SEH2_END; - DPRINT("BaseSrvDefineDosDevice exit, Status: 0x%x\n", Status); return Status; }