mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 16:33:02 -05:00
509 lines
18 KiB
C
509 lines
18 KiB
C
/**
|
|
* @file dll/security.c
|
|
*
|
|
* @copyright 2015-2022 Bill Zissimopoulos
|
|
*/
|
|
/*
|
|
* This file is part of WinFsp.
|
|
*
|
|
* You can redistribute it and/or modify it under the terms of the GNU
|
|
* General Public License version 3 as published by the Free Software
|
|
* Foundation.
|
|
*
|
|
* Licensees holding a valid commercial license may use this software
|
|
* in accordance with the commercial license agreement provided in
|
|
* conjunction with the software. The terms and conditions of any such
|
|
* commercial license agreement shall govern, supersede, and render
|
|
* ineffective any application of the GPLv3 license to this software,
|
|
* notwithstanding of any reference thereto in the software or
|
|
* associated repository.
|
|
*/
|
|
|
|
#include <dll/library.h>
|
|
|
|
static GENERIC_MAPPING FspFileGenericMapping =
|
|
{
|
|
.GenericRead = FILE_GENERIC_READ,
|
|
.GenericWrite = FILE_GENERIC_WRITE,
|
|
.GenericExecute = FILE_GENERIC_EXECUTE,
|
|
.GenericAll = FILE_ALL_ACCESS,
|
|
};
|
|
|
|
FSP_API PGENERIC_MAPPING FspGetFileGenericMapping(VOID)
|
|
{
|
|
return &FspFileGenericMapping;
|
|
}
|
|
|
|
static NTSTATUS FspGetSecurityByName(FSP_FILE_SYSTEM *FileSystem,
|
|
PWSTR FileName, PUINT32 PFileAttributes,
|
|
PSECURITY_DESCRIPTOR *PSecurityDescriptor, SIZE_T *PSecurityDescriptorSize)
|
|
{
|
|
for (;;)
|
|
{
|
|
NTSTATUS Result = FileSystem->Interface->GetSecurityByName(FileSystem,
|
|
FileName, PFileAttributes, *PSecurityDescriptor, PSecurityDescriptorSize);
|
|
if (STATUS_BUFFER_OVERFLOW != Result)
|
|
return Result;
|
|
|
|
MemFree(*PSecurityDescriptor);
|
|
*PSecurityDescriptor = MemAlloc(*PSecurityDescriptorSize);
|
|
if (0 == *PSecurityDescriptor)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
FSP_API NTSTATUS FspAccessCheckEx(FSP_FILE_SYSTEM *FileSystem,
|
|
FSP_FSCTL_TRANSACT_REQ *Request,
|
|
BOOLEAN CheckParentOrMain, BOOLEAN AllowTraverseCheck,
|
|
UINT32 DesiredAccess, PUINT32 PGrantedAccess,
|
|
PSECURITY_DESCRIPTOR *PSecurityDescriptor)
|
|
{
|
|
BOOLEAN CheckParentDirectory, CheckMainFile;
|
|
|
|
CheckParentDirectory = CheckMainFile = FALSE;
|
|
if (CheckParentOrMain)
|
|
{
|
|
if (!Request->Req.Create.NamedStream)
|
|
CheckParentDirectory = TRUE;
|
|
else
|
|
CheckMainFile = TRUE;
|
|
}
|
|
|
|
*PGrantedAccess = 0;
|
|
if (0 != PSecurityDescriptor)
|
|
*PSecurityDescriptor = 0;
|
|
|
|
if (FspFsctlTransactCreateKind != Request->Kind)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (CheckParentDirectory &&
|
|
L'\\' == ((PWSTR)Request->Buffer)[0] && L'\0' == ((PWSTR)Request->Buffer)[1])
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (0 == FileSystem->Interface->GetSecurityByName ||
|
|
(!Request->Req.Create.UserMode && 0 == PSecurityDescriptor))
|
|
{
|
|
*PGrantedAccess = (MAXIMUM_ALLOWED & DesiredAccess) ?
|
|
FspFileGenericMapping.GenericAll : DesiredAccess;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS Result;
|
|
WCHAR Root[2] = L"\\", TraverseCheckRoot[2] = L"\\";
|
|
PWSTR FileName, Suffix, Prefix, Remain;
|
|
UINT32 FileAttributes = 0;
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor = 0;
|
|
SIZE_T SecurityDescriptorSize;
|
|
UINT8 PrivilegeSetBuf[sizeof(PRIVILEGE_SET) + 15 * sizeof(LUID_AND_ATTRIBUTES)];
|
|
PPRIVILEGE_SET PrivilegeSet = (PVOID)PrivilegeSetBuf;
|
|
DWORD PrivilegeSetLength = sizeof PrivilegeSetBuf;
|
|
UINT32 TraverseAccess, ParentAccess, DesiredAccess2;
|
|
UINT16 NamedStreamSave;
|
|
BOOL AccessStatus;
|
|
|
|
if (CheckParentDirectory)
|
|
FspPathSuffix((PWSTR)Request->Buffer, &FileName, &Suffix, Root);
|
|
else if (CheckMainFile)
|
|
{
|
|
((PWSTR)Request->Buffer)[Request->Req.Create.NamedStream / sizeof(WCHAR)] = L'\0';
|
|
FileName = (PWSTR)Request->Buffer;
|
|
}
|
|
else
|
|
FileName = (PWSTR)Request->Buffer;
|
|
|
|
SecurityDescriptorSize = 1024;
|
|
SecurityDescriptor = MemAlloc(SecurityDescriptorSize);
|
|
if (0 == SecurityDescriptor)
|
|
{
|
|
Result = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto exit;
|
|
}
|
|
|
|
if (Request->Req.Create.UserMode &&
|
|
AllowTraverseCheck && !Request->Req.Create.HasTraversePrivilege &&
|
|
!(L'\\' == FileName[0] && L'\0' == FileName[1])/* no need to traverse check for root */)
|
|
{
|
|
Remain = FileName;
|
|
for (;;)
|
|
{
|
|
while (L'\\' != *Remain)
|
|
{
|
|
if (L'\0' == *Remain || L':' == *Remain)
|
|
goto traverse_check_done;
|
|
Remain++;
|
|
}
|
|
|
|
*Remain = L'\0';
|
|
Prefix = Remain > FileName ? FileName : TraverseCheckRoot;
|
|
|
|
FileAttributes = 0;
|
|
Result = FspGetSecurityByName(FileSystem, Prefix, &FileAttributes,
|
|
&SecurityDescriptor, &SecurityDescriptorSize);
|
|
|
|
/*
|
|
* We check to see if this is a reparse point and then compute the ReparsePointIndex
|
|
* and place it in FileAttributes. We do this check BEFORE the directory check,
|
|
* because contrary to NTFS we want to allow non-directory symlinks to directories.
|
|
*/
|
|
if (NT_SUCCESS(Result) && STATUS_REPARSE != Result &&
|
|
(FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
|
|
{
|
|
FileAttributes = FspPathSuffixIndex(Prefix);
|
|
Result = STATUS_REPARSE;
|
|
}
|
|
|
|
*Remain = L'\\';
|
|
do
|
|
{
|
|
Remain++;
|
|
} while (L'\\' == *Remain);
|
|
|
|
if (!NT_SUCCESS(Result) || STATUS_REPARSE == Result)
|
|
{
|
|
if (STATUS_OBJECT_NAME_NOT_FOUND == Result)
|
|
Result = STATUS_OBJECT_PATH_NOT_FOUND;
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* Check if this is a directory, otherwise the path is invalid.
|
|
*/
|
|
if (0 == (FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
Result = STATUS_OBJECT_PATH_NOT_FOUND; /* use STATUS_OBJECT_PATH_INVALID? */
|
|
goto exit;
|
|
}
|
|
|
|
if (0 < SecurityDescriptorSize)
|
|
{
|
|
if (AccessCheck(SecurityDescriptor,
|
|
FSP_FSCTL_TRANSACT_REQ_TOKEN_HANDLE(Request->Req.Create.AccessToken),
|
|
FILE_TRAVERSE,
|
|
&FspFileGenericMapping,
|
|
PrivilegeSet, &PrivilegeSetLength,
|
|
&TraverseAccess, &AccessStatus))
|
|
Result = AccessStatus ? STATUS_SUCCESS : STATUS_ACCESS_DENIED;
|
|
else
|
|
Result = FspNtStatusFromWin32(GetLastError());
|
|
if (!NT_SUCCESS(Result))
|
|
goto exit;
|
|
}
|
|
}
|
|
traverse_check_done:
|
|
;
|
|
}
|
|
|
|
FileAttributes = 0;
|
|
Result = FspGetSecurityByName(FileSystem, FileName, &FileAttributes,
|
|
&SecurityDescriptor, &SecurityDescriptorSize);
|
|
if (!NT_SUCCESS(Result) || STATUS_REPARSE == Result)
|
|
goto exit;
|
|
|
|
if (!CheckParentOrMain && Request->Req.Create.HasTrailingBackslash &&
|
|
!(FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
Result = STATUS_OBJECT_NAME_INVALID;
|
|
goto exit;
|
|
}
|
|
|
|
if (Request->Req.Create.UserMode && 0 < SecurityDescriptorSize)
|
|
{
|
|
if (0 == DesiredAccess)
|
|
Result = STATUS_SUCCESS;
|
|
else if (AccessCheck(SecurityDescriptor,
|
|
FSP_FSCTL_TRANSACT_REQ_TOKEN_HANDLE(Request->Req.Create.AccessToken),
|
|
DesiredAccess,
|
|
&FspFileGenericMapping,
|
|
PrivilegeSet, &PrivilegeSetLength,
|
|
PGrantedAccess, &AccessStatus))
|
|
Result = AccessStatus ? STATUS_SUCCESS : STATUS_ACCESS_DENIED;
|
|
else
|
|
Result = FspNtStatusFromWin32(GetLastError());
|
|
if (!NT_SUCCESS(Result))
|
|
{
|
|
/*
|
|
* If the desired access includes the DELETE or FILE_READ_ATTRIBUTES
|
|
* (or MAXIMUM_ALLOWED) rights we must still check with our parent to
|
|
* see if it gives us access (through the FILE_DELETE_CHILD and
|
|
* FILE_LIST_DIRECTORY rights).
|
|
*
|
|
* Does the Windows security model suck? Ermmmm...
|
|
*/
|
|
if (STATUS_ACCESS_DENIED != Result ||
|
|
0 == ((MAXIMUM_ALLOWED | DELETE | FILE_READ_ATTRIBUTES) & DesiredAccess))
|
|
goto exit;
|
|
|
|
NamedStreamSave = Request->Req.Create.NamedStream;
|
|
Request->Req.Create.NamedStream = 0;
|
|
Result = FspAccessCheck(FileSystem, Request, TRUE, FALSE,
|
|
(MAXIMUM_ALLOWED & DesiredAccess) ? (FILE_DELETE_CHILD | FILE_LIST_DIRECTORY) :
|
|
(
|
|
((DELETE & DesiredAccess) ? FILE_DELETE_CHILD : 0) |
|
|
((FILE_READ_ATTRIBUTES & DesiredAccess) ? FILE_LIST_DIRECTORY : 0)
|
|
),
|
|
&ParentAccess);
|
|
Request->Req.Create.NamedStream = NamedStreamSave;
|
|
if (!NT_SUCCESS(Result))
|
|
{
|
|
/* any failure just becomes ACCESS DENIED at this point */
|
|
Result = STATUS_ACCESS_DENIED;
|
|
goto exit;
|
|
}
|
|
|
|
/* redo the access check but remove the DELETE and/or FILE_READ_ATTRIBUTES rights */
|
|
DesiredAccess2 = DesiredAccess & ~(
|
|
((FILE_DELETE_CHILD & ParentAccess) ? DELETE : 0) |
|
|
((FILE_LIST_DIRECTORY & ParentAccess) ? FILE_READ_ATTRIBUTES : 0));
|
|
if (0 != DesiredAccess2)
|
|
{
|
|
if (AccessCheck(SecurityDescriptor,
|
|
FSP_FSCTL_TRANSACT_REQ_TOKEN_HANDLE(Request->Req.Create.AccessToken),
|
|
DesiredAccess2,
|
|
&FspFileGenericMapping,
|
|
PrivilegeSet, &PrivilegeSetLength,
|
|
PGrantedAccess, &AccessStatus))
|
|
Result = AccessStatus ? STATUS_SUCCESS : STATUS_ACCESS_DENIED;
|
|
else
|
|
/* any failure just becomes ACCESS DENIED at this point */
|
|
Result = STATUS_ACCESS_DENIED;
|
|
if (!NT_SUCCESS(Result))
|
|
goto exit;
|
|
}
|
|
|
|
if (FILE_DELETE_CHILD & ParentAccess)
|
|
*PGrantedAccess |= DELETE;
|
|
if (FILE_LIST_DIRECTORY & ParentAccess)
|
|
*PGrantedAccess |= FILE_READ_ATTRIBUTES;
|
|
}
|
|
}
|
|
|
|
if (CheckParentDirectory)
|
|
{
|
|
/*
|
|
* We check to see if this is a reparse point and then immediately return
|
|
* STATUS_REPARSE. We do this check BEFORE the directory check, because
|
|
* contrary to NTFS we want to allow non-directory symlinks to directories.
|
|
*/
|
|
if (FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
|
{
|
|
FileAttributes = FspPathSuffixIndex(FileName);
|
|
Result = STATUS_REPARSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (0 == (FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
Result = STATUS_NOT_A_DIRECTORY;
|
|
goto exit;
|
|
}
|
|
}
|
|
else if (CheckMainFile)
|
|
{
|
|
/*
|
|
* We check to see if this is a reparse point and FILE_OPEN_REPARSE_POINT
|
|
* was not specified, in which case we return STATUS_REPARSE.
|
|
*/
|
|
if (0 != (FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
|
|
0 == (Request->Req.Create.CreateOptions & FILE_OPEN_REPARSE_POINT))
|
|
{
|
|
FileAttributes = FspPathSuffixIndex(FileName);
|
|
Result = STATUS_REPARSE;
|
|
goto exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We check to see if this is a reparse point and FILE_OPEN_REPARSE_POINT
|
|
* was not specified, in which case we return STATUS_REPARSE.
|
|
*/
|
|
if (0 != (FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
|
|
0 == (Request->Req.Create.CreateOptions & FILE_OPEN_REPARSE_POINT))
|
|
{
|
|
FileAttributes = FspPathSuffixIndex(FileName);
|
|
Result = STATUS_REPARSE;
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* We allow some file systems (notably FUSE) to open reparse points
|
|
* regardless of the FILE_DIRECTORY_FILE / FILE_NON_DIRECTORY_FILE options.
|
|
*/
|
|
if (0 != (FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && FileSystem->UmNoReparsePointsDirCheck)
|
|
goto skip_reparse_dir_check;
|
|
|
|
if ((Request->Req.Create.CreateOptions & FILE_DIRECTORY_FILE) &&
|
|
0 == (FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
Result = STATUS_NOT_A_DIRECTORY;
|
|
goto exit;
|
|
}
|
|
if ((Request->Req.Create.CreateOptions & FILE_NON_DIRECTORY_FILE) &&
|
|
0 != (FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
Result = STATUS_FILE_IS_A_DIRECTORY;
|
|
goto exit;
|
|
}
|
|
|
|
skip_reparse_dir_check:
|
|
;
|
|
}
|
|
|
|
if (Request->Req.Create.UserMode)
|
|
{
|
|
if (FILE_ATTRIBUTE_READONLY == (FileAttributes & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_DIRECTORY)) &&
|
|
(DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD)))
|
|
{
|
|
Result = STATUS_ACCESS_DENIED;
|
|
goto exit;
|
|
}
|
|
|
|
if (FILE_ATTRIBUTE_READONLY == (FileAttributes & FILE_ATTRIBUTE_READONLY) &&
|
|
(Request->Req.Create.CreateOptions & FILE_DELETE_ON_CLOSE))
|
|
{
|
|
Result = STATUS_CANNOT_DELETE;
|
|
goto exit;
|
|
}
|
|
|
|
if (0 == SecurityDescriptorSize)
|
|
*PGrantedAccess = (MAXIMUM_ALLOWED & DesiredAccess) ?
|
|
FspFileGenericMapping.GenericAll : DesiredAccess;
|
|
|
|
if (FILE_ATTRIBUTE_READONLY == (FileAttributes & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_DIRECTORY)) &&
|
|
0 != (MAXIMUM_ALLOWED & DesiredAccess))
|
|
*PGrantedAccess &= ~(FILE_WRITE_DATA | FILE_APPEND_DATA |
|
|
FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD);
|
|
}
|
|
else
|
|
*PGrantedAccess = (MAXIMUM_ALLOWED & DesiredAccess) ?
|
|
FspFileGenericMapping.GenericAll : DesiredAccess;
|
|
|
|
Result = STATUS_SUCCESS;
|
|
|
|
exit:
|
|
if (STATUS_REPARSE == Result)
|
|
*PGrantedAccess = FileAttributes; /* FileAttributes contains ReparsePointIndex */
|
|
else if (0 != PSecurityDescriptor && 0 < SecurityDescriptorSize && NT_SUCCESS(Result))
|
|
*PSecurityDescriptor = SecurityDescriptor;
|
|
else
|
|
MemFree(SecurityDescriptor);
|
|
|
|
if (CheckParentDirectory)
|
|
{
|
|
FspPathCombine((PWSTR)Request->Buffer, Suffix);
|
|
|
|
if (STATUS_OBJECT_NAME_NOT_FOUND == Result)
|
|
Result = STATUS_OBJECT_PATH_NOT_FOUND;
|
|
}
|
|
else if (CheckMainFile)
|
|
((PWSTR)Request->Buffer)[Request->Req.Create.NamedStream / sizeof(WCHAR)] = L':';
|
|
|
|
return Result;
|
|
}
|
|
|
|
FSP_API NTSTATUS FspCreateSecurityDescriptor(FSP_FILE_SYSTEM *FileSystem,
|
|
FSP_FSCTL_TRANSACT_REQ *Request,
|
|
PSECURITY_DESCRIPTOR ParentDescriptor,
|
|
PSECURITY_DESCRIPTOR *PSecurityDescriptor)
|
|
{
|
|
*PSecurityDescriptor = 0;
|
|
|
|
if (FspFsctlTransactCreateKind != Request->Kind)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
/* stream support: return NULL security descriptor when creating named stream */
|
|
if (Request->Req.Create.NamedStream)
|
|
return STATUS_SUCCESS;
|
|
|
|
if (!CreatePrivateObjectSecurity(
|
|
ParentDescriptor,
|
|
0 != Request->Req.Create.SecurityDescriptor.Offset ?
|
|
(PSECURITY_DESCRIPTOR)(Request->Buffer + Request->Req.Create.SecurityDescriptor.Offset) : 0,
|
|
PSecurityDescriptor,
|
|
0 != (Request->Req.Create.CreateOptions & FILE_DIRECTORY_FILE),
|
|
FSP_FSCTL_TRANSACT_REQ_TOKEN_HANDLE(Request->Req.Create.AccessToken),
|
|
&FspFileGenericMapping))
|
|
return FspNtStatusFromWin32(GetLastError());
|
|
|
|
//DEBUGLOGSD("SDDL=%s", *PSecurityDescriptor);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
FSP_API NTSTATUS FspSetSecurityDescriptor(
|
|
PSECURITY_DESCRIPTOR InputDescriptor,
|
|
SECURITY_INFORMATION SecurityInformation,
|
|
PSECURITY_DESCRIPTOR ModificationDescriptor,
|
|
PSECURITY_DESCRIPTOR *PSecurityDescriptor)
|
|
{
|
|
*PSecurityDescriptor = 0;
|
|
|
|
if (0 == InputDescriptor)
|
|
return STATUS_NO_SECURITY_ON_OBJECT;
|
|
|
|
/*
|
|
* SetPrivateObjectSecurity is a broken API. It assumes that the passed
|
|
* descriptor resides on memory allocated by CreatePrivateObjectSecurity
|
|
* or SetPrivateObjectSecurity and frees the descriptor on success.
|
|
*
|
|
* In our case the security descriptor comes from the user mode file system,
|
|
* which may conjure it any way it sees fit. So we have to somehow make a copy
|
|
* of the InputDescriptor and place it in memory that SetPrivateObjectSecurity
|
|
* can then free. To complicate matters there is no API that can be used for
|
|
* this purpose. What a PITA!
|
|
*/
|
|
/* !!!: HACK! HACK! HACK!
|
|
*
|
|
* Turns out that SetPrivateObjectSecurity and friends really use RtlProcessHeap
|
|
* internally, which is just another name for GetProcessHeap().
|
|
*
|
|
* I wish there was a cleaner way to do this!
|
|
*/
|
|
|
|
HANDLE ProcessHeap = GetProcessHeap();
|
|
DWORD InputDescriptorSize = GetSecurityDescriptorLength(InputDescriptor);
|
|
PSECURITY_DESCRIPTOR CopiedDescriptor;
|
|
|
|
CopiedDescriptor = HeapAlloc(ProcessHeap, 0, InputDescriptorSize);
|
|
if (0 == CopiedDescriptor)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
memcpy(CopiedDescriptor, InputDescriptor, InputDescriptorSize);
|
|
InputDescriptor = CopiedDescriptor;
|
|
|
|
if (!SetPrivateObjectSecurity(
|
|
SecurityInformation,
|
|
ModificationDescriptor,
|
|
&InputDescriptor,
|
|
&FspFileGenericMapping,
|
|
0))
|
|
{
|
|
HeapFree(ProcessHeap, 0, CopiedDescriptor);
|
|
return FspNtStatusFromWin32(GetLastError());
|
|
}
|
|
|
|
/* CopiedDescriptor has been freed by SetPrivateObjectSecurity! */
|
|
|
|
*PSecurityDescriptor = InputDescriptor;
|
|
|
|
//DEBUGLOGSD("SDDL=%s", *PSecurityDescriptor);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
FSP_API VOID FspDeleteSecurityDescriptor(PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
NTSTATUS (*CreateFunc)())
|
|
{
|
|
/* stream support: allow NULL security descriptors */
|
|
if (0 == SecurityDescriptor)
|
|
return;
|
|
|
|
if ((NTSTATUS (*)())FspAccessCheckEx == CreateFunc ||
|
|
(NTSTATUS (*)())FspPosixMapPermissionsToSecurityDescriptor == CreateFunc ||
|
|
(NTSTATUS (*)())FspPosixMergePermissionsToSecurityDescriptor == CreateFunc)
|
|
MemFree(SecurityDescriptor);
|
|
else
|
|
if ((NTSTATUS (*)())FspCreateSecurityDescriptor == CreateFunc ||
|
|
(NTSTATUS (*)())FspSetSecurityDescriptor == CreateFunc)
|
|
DestroyPrivateObjectSecurity(&SecurityDescriptor);
|
|
}
|