|
|
|
|
using Alphaleonis.Win32.Filesystem;
|
|
|
|
|
using Microsoft.Win32.SafeHandles;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Security.AccessControl;
|
|
|
|
|
|
|
|
|
|
namespace Security2
|
|
|
|
|
{
|
|
|
|
|
internal partial class Win32
|
|
|
|
|
{
|
|
|
|
|
internal const string AUTHZ_OBJECTUUID_WITHCAP = "9a81c2bd-a525-471d-a4ed-49907c0b23da";
|
|
|
|
|
internal const string RCP_OVER_TCP_PROTOCOL = "ncacn_ip_tcp";
|
|
|
|
|
|
|
|
|
|
IntPtr userClientCtxt = IntPtr.Zero;
|
|
|
|
|
SafeAuthzRMHandle authzRM;
|
|
|
|
|
IntPtr pGrantedAccess = IntPtr.Zero;
|
|
|
|
|
IntPtr pErrorSecObj = IntPtr.Zero;
|
|
|
|
|
|
|
|
|
|
#region GetInheritedFrom
|
fix: compare output objects and report InheritedFrom correctly
Equals of FileSystemAccessRule2, FileSystemAuditRule2, and
FileSystemSecurity2 cast its argument to the .NET type, which throws for
the wrapper types themselves: -eq and -contains, and in PowerShell 7 also
Select-Object -Unique and Compare-Object, stopped with an
InvalidCastException. GetHashCode of FileSystemSecurity2 read a field
that is never set and threw a NullReferenceException. Two objects are now
equal when they hold the same entry or descriptor, like the .NET types.
InheritedFrom: Win32.GetInheritedFrom returned the sources of the SACL
whenever the descriptor had one, also for the access entries, and the
callers gave the filtered entries of -ExcludeExplicit the sources of the
first entries of the ACL. Get-NTFSAccess -SecurityDescriptor stopped with
an ArgumentOutOfRangeException for a descriptor with audit entries, as
Get-NTFSSecurityDescriptor reads them in an elevated session. The method
now takes the ACL of the entries, and the callers map the sources before
they filter.
The coverage report of rc6 pointed at both; each test fails without its
fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
// Returns the source of each entry of the DACL, or of the SACL for audit entries, in the order of the ACL. Before
|
|
|
|
|
// 5.0.0-rc6, a descriptor with a SACL returned the sources of the audit entries also for the access entries.
|
|
|
|
|
public static List<string> GetInheritedFrom(FileSystemInfo item, ObjectSecurity sd, bool auditEntries)
|
|
|
|
|
{
|
|
|
|
|
var inheritedFrom = new List<string>();
|
|
|
|
|
|
|
|
|
|
var sdBytes = sd.GetSecurityDescriptorBinaryForm();
|
|
|
|
|
var rawSd = new RawSecurityDescriptor(sdBytes, 0);
|
fix: compare output objects and report InheritedFrom correctly
Equals of FileSystemAccessRule2, FileSystemAuditRule2, and
FileSystemSecurity2 cast its argument to the .NET type, which throws for
the wrapper types themselves: -eq and -contains, and in PowerShell 7 also
Select-Object -Unique and Compare-Object, stopped with an
InvalidCastException. GetHashCode of FileSystemSecurity2 read a field
that is never set and threw a NullReferenceException. Two objects are now
equal when they hold the same entry or descriptor, like the .NET types.
InheritedFrom: Win32.GetInheritedFrom returned the sources of the SACL
whenever the descriptor had one, also for the access entries, and the
callers gave the filtered entries of -ExcludeExplicit the sources of the
first entries of the ACL. Get-NTFSAccess -SecurityDescriptor stopped with
an ArgumentOutOfRangeException for a descriptor with audit entries, as
Get-NTFSSecurityDescriptor reads them in an elevated session. The method
now takes the ACL of the entries, and the callers map the sources before
they filter.
The coverage report of rc6 pointed at both; each test fails without its
fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
var acl = auditEntries ? rawSd.SystemAcl : rawSd.DiscretionaryAcl;
|
|
|
|
|
|
fix: compare output objects and report InheritedFrom correctly
Equals of FileSystemAccessRule2, FileSystemAuditRule2, and
FileSystemSecurity2 cast its argument to the .NET type, which throws for
the wrapper types themselves: -eq and -contains, and in PowerShell 7 also
Select-Object -Unique and Compare-Object, stopped with an
InvalidCastException. GetHashCode of FileSystemSecurity2 read a field
that is never set and threw a NullReferenceException. Two objects are now
equal when they hold the same entry or descriptor, like the .NET types.
InheritedFrom: Win32.GetInheritedFrom returned the sources of the SACL
whenever the descriptor had one, also for the access entries, and the
callers gave the filtered entries of -ExcludeExplicit the sources of the
first entries of the ACL. Get-NTFSAccess -SecurityDescriptor stopped with
an ArgumentOutOfRangeException for a descriptor with audit entries, as
Get-NTFSSecurityDescriptor reads them in an elevated session. The method
now takes the ACL of the entries, and the callers map the sources before
they filter.
The coverage report of rc6 pointed at both; each test fails without its
fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
if (acl != null)
|
|
|
|
|
{
|
fix: compare output objects and report InheritedFrom correctly
Equals of FileSystemAccessRule2, FileSystemAuditRule2, and
FileSystemSecurity2 cast its argument to the .NET type, which throws for
the wrapper types themselves: -eq and -contains, and in PowerShell 7 also
Select-Object -Unique and Compare-Object, stopped with an
InvalidCastException. GetHashCode of FileSystemSecurity2 read a field
that is never set and threw a NullReferenceException. Two objects are now
equal when they hold the same entry or descriptor, like the .NET types.
InheritedFrom: Win32.GetInheritedFrom returned the sources of the SACL
whenever the descriptor had one, also for the access entries, and the
callers gave the filtered entries of -ExcludeExplicit the sources of the
first entries of the ACL. Get-NTFSAccess -SecurityDescriptor stopped with
an ArgumentOutOfRangeException for a descriptor with audit entries, as
Get-NTFSSecurityDescriptor reads them in an elevated session. The method
now takes the ACL of the entries, and the callers map the sources before
they filter.
The coverage report of rc6 pointed at both; each test fails without its
fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
var aceCount = acl.Count;
|
|
|
|
|
var aclBytes = new byte[acl.BinaryLength];
|
|
|
|
|
acl.GetBinaryForm(aclBytes, 0);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
inheritedFrom = GetInheritedFrom(item.FullName,
|
|
|
|
|
aclBytes,
|
|
|
|
|
aceCount,
|
|
|
|
|
item is DirectoryInfo ? true : false,
|
fix: compare output objects and report InheritedFrom correctly
Equals of FileSystemAccessRule2, FileSystemAuditRule2, and
FileSystemSecurity2 cast its argument to the .NET type, which throws for
the wrapper types themselves: -eq and -contains, and in PowerShell 7 also
Select-Object -Unique and Compare-Object, stopped with an
InvalidCastException. GetHashCode of FileSystemSecurity2 read a field
that is never set and threw a NullReferenceException. Two objects are now
equal when they hold the same entry or descriptor, like the .NET types.
InheritedFrom: Win32.GetInheritedFrom returned the sources of the SACL
whenever the descriptor had one, also for the access entries, and the
callers gave the filtered entries of -ExcludeExplicit the sources of the
first entries of the ACL. Get-NTFSAccess -SecurityDescriptor stopped with
an ArgumentOutOfRangeException for a descriptor with audit entries, as
Get-NTFSSecurityDescriptor reads them in an elevated session. The method
now takes the ACL of the entries, and the callers map the sources before
they filter.
The coverage report of rc6 pointed at both; each test fails without its
fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
auditEntries ? SECURITY_INFORMATION.SACL_SECURITY_INFORMATION : SECURITY_INFORMATION.DACL_SECURITY_INFORMATION);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Windows can't name the folders, for example because the item is gone or a folder above it can't
|
|
|
|
|
// be read. An explicit entry has no source in any case.
|
|
|
|
|
inheritedFrom = new List<string>();
|
|
|
|
|
for (int i = 0; i < aceCount; i++)
|
|
|
|
|
{
|
|
|
|
|
inheritedFrom.Add(acl[i].IsInherited ? "unknown parent" : string.Empty);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return inheritedFrom;
|
|
|
|
|
}
|
|
|
|
|
public static List<string> GetInheritedFrom(string path, byte[] aclBytes, int aceCount, bool isContainer, SECURITY_INFORMATION aclType)
|
|
|
|
|
{
|
|
|
|
|
var inheritedFrom = new List<string>();
|
|
|
|
|
path = Path.GetLongPath(path);
|
|
|
|
|
|
|
|
|
|
uint returnValue = 0;
|
|
|
|
|
GENERIC_MAPPING genericMap = new GENERIC_MAPPING();
|
|
|
|
|
genericMap.GenericRead = (uint)MappedGenericRights.FILE_GENERIC_READ;
|
|
|
|
|
genericMap.GenericWrite = (uint)MappedGenericRights.FILE_GENERIC_WRITE;
|
|
|
|
|
genericMap.GenericExecute = (uint)MappedGenericRights.FILE_GENERIC_EXECUTE;
|
|
|
|
|
genericMap.GenericAll = (uint)MappedGenericRights.FILE_GENERIC_ALL;
|
|
|
|
|
|
|
|
|
|
var pInheritInfo = Marshal.AllocHGlobal(aceCount * Marshal.SizeOf(typeof(PINHERITED_FROM)));
|
|
|
|
|
|
|
|
|
|
returnValue = GetInheritanceSource(
|
|
|
|
|
path,
|
|
|
|
|
ResourceType.FileObject,
|
|
|
|
|
aclType,
|
|
|
|
|
isContainer,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
0,
|
|
|
|
|
aclBytes,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
ref genericMap,
|
|
|
|
|
pInheritInfo
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (returnValue != 0)
|
|
|
|
|
{
|
|
|
|
|
throw new System.ComponentModel.Win32Exception((int)returnValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < aceCount; i++)
|
|
|
|
|
{
|
|
|
|
|
var inheritInfo = pInheritInfo.ElementAt<PINHERITED_FROM>(i);
|
|
|
|
|
|
|
|
|
|
inheritedFrom.Add(
|
|
|
|
|
!string.IsNullOrEmpty(inheritInfo.AncestorName) && inheritInfo.AncestorName.StartsWith(@"\\?\") ? inheritInfo.AncestorName.Substring(4) : inheritInfo.AncestorName
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FreeInheritedFromArray(pInheritInfo, (ushort)aceCount, IntPtr.Zero);
|
|
|
|
|
Marshal.FreeHGlobal(pInheritInfo);
|
|
|
|
|
|
|
|
|
|
return inheritedFrom;
|
|
|
|
|
}
|
|
|
|
|
#endregion GetInheritedFrom
|
|
|
|
|
|
|
|
|
|
public int GetEffectiveAccess(ObjectSecurity sd, IdentityReference2 identity, string serverName, out bool remoteServerAvailable, out Exception authzException)
|
|
|
|
|
{
|
|
|
|
|
int effectiveAccess = 0;
|
|
|
|
|
remoteServerAvailable = false;
|
|
|
|
|
authzException = null;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
GetEffectivePermissions_AuthzInitializeResourceManager(serverName, out remoteServerAvailable);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
GetEffectivePermissions_AuthzInitializeContextFromSid(identity);
|
|
|
|
|
effectiveAccess = GetEffectivePermissions_AuthzAccessCheck(sd);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
authzException = ex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{ }
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
GetEffectivePermissions_FreeResouces();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return effectiveAccess;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Win32 Wrapper
|
fix(access): don't warn for a name of this computer in Get-NTFSEffectiveAccess
Where a computer doesn't offer the remote interface of the authorization
manager, the cmdlet calculates the result with the local one and warns
that the result might be inaccurate. Only localhost in lowercase counted
as this computer, so the cmdlet warned that the computer couldn't be
reached for ., LOCALHOST, or the computer name, although the local result
is the result of that computer. A name of this computer is now localhost
in any case, ., the NetBIOS name, the DNS host name, or the fully
qualified domain name.
From the security-reviewer pass over be04cb7..4ee01e5 (Nit 7),
reproduced in Windows PowerShell 5.1 and PowerShell 7 on a workstation
without the remote interface.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
// Whether a name of -ServerName names this computer: localhost in any case, ., the NetBIOS name, the DNS host
|
|
|
|
|
// name, or the fully qualified domain name. It must not throw, because GetEffectiveAccess hides every exception
|
|
|
|
|
// of the resource manager behind a result without access.
|
|
|
|
|
private static bool IsLocalComputer(string serverName)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(serverName))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (serverName == "." ||
|
|
|
|
|
string.Equals(serverName, "localhost", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
string.Equals(serverName, Environment.MachineName, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var properties = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties();
|
|
|
|
|
return string.Equals(serverName, properties.HostName, StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
(!string.IsNullOrEmpty(properties.DomainName) &&
|
|
|
|
|
string.Equals(serverName, properties.HostName + "." + properties.DomainName, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
}
|
|
|
|
|
catch (System.Net.NetworkInformation.NetworkInformationException)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void GetEffectivePermissions_AuthzInitializeResourceManager(string serverName, out bool remoteServerAvailable)
|
|
|
|
|
{
|
|
|
|
|
remoteServerAvailable = false;
|
|
|
|
|
|
|
|
|
|
var rpcInitInfo = new AUTHZ_RPC_INIT_INFO_CLIENT();
|
|
|
|
|
|
|
|
|
|
rpcInitInfo.version = AuthzRpcClientVersion.V1;
|
|
|
|
|
rpcInitInfo.objectUuid = AUTHZ_OBJECTUUID_WITHCAP;
|
|
|
|
|
rpcInitInfo.protocol = RCP_OVER_TCP_PROTOCOL;
|
|
|
|
|
rpcInitInfo.server = serverName;
|
|
|
|
|
|
|
|
|
|
SafeHGlobalHandle pRpcInitInfo = SafeHGlobalHandle.AllocHGlobalStruct(rpcInitInfo);
|
|
|
|
|
if (!AuthzInitializeRemoteResourceManager(pRpcInitInfo.ToIntPtr(), out authzRM))
|
|
|
|
|
{
|
|
|
|
|
int error = Marshal.GetLastWin32Error();
|
|
|
|
|
|
|
|
|
|
// The computer can't be resolved or reached (RPC server unavailable), or it doesn't offer the remote
|
|
|
|
|
// interface (endpoint not registered); the local authorization manager calculates the result instead.
|
|
|
|
|
if (error != Win32Error.EPT_S_NOT_REGISTERED && error != Win32Error.RPC_S_SERVER_UNAVAILABLE)
|
|
|
|
|
{
|
|
|
|
|
throw new Win32Exception(error);
|
|
|
|
|
}
|
|
|
|
|
|
fix(access): don't warn for a name of this computer in Get-NTFSEffectiveAccess
Where a computer doesn't offer the remote interface of the authorization
manager, the cmdlet calculates the result with the local one and warns
that the result might be inaccurate. Only localhost in lowercase counted
as this computer, so the cmdlet warned that the computer couldn't be
reached for ., LOCALHOST, or the computer name, although the local result
is the result of that computer. A name of this computer is now localhost
in any case, ., the NetBIOS name, the DNS host name, or the fully
qualified domain name.
From the security-reviewer pass over be04cb7..4ee01e5 (Nit 7),
reproduced in Windows PowerShell 5.1 and PowerShell 7 on a workstation
without the remote interface.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
// The local authorization manager is the one of this computer, so its result is accurate for any name
|
|
|
|
|
// of this computer. Before 5.0.0-rc7, only localhost in lowercase counted, and the cmdlet warned for
|
|
|
|
|
// the others, such as ., the computer name, or LOCALHOST.
|
|
|
|
|
if (IsLocalComputer(serverName))
|
|
|
|
|
{
|
|
|
|
|
remoteServerAvailable = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// As a fallback we do AuthzInitializeResourceManager. But the results can be inaccurate.
|
|
|
|
|
//
|
|
|
|
|
if (!AuthzInitializeResourceManager(
|
|
|
|
|
AuthzResourceManagerFlags.NO_AUDIT,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
"EffectiveAccessCheck",
|
|
|
|
|
out authzRM))
|
|
|
|
|
{
|
|
|
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
remoteServerAvailable = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void GetEffectivePermissions_AuthzInitializeContextFromSid(IdentityReference2 id)
|
|
|
|
|
{
|
|
|
|
|
var rawSid = id.GetBinaryForm();
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Create an AuthZ context based on the user account
|
|
|
|
|
//
|
|
|
|
|
if (!AuthzInitializeContextFromSid(
|
|
|
|
|
AuthzInitFlags.Default,
|
|
|
|
|
rawSid,
|
|
|
|
|
authzRM,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
Win32.LUID.NullLuid,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
out userClientCtxt))
|
|
|
|
|
{
|
|
|
|
|
Win32Exception win32Expn = new Win32Exception(Marshal.GetLastWin32Error());
|
|
|
|
|
|
|
|
|
|
if (win32Expn.NativeErrorCode != Win32Error.RPC_S_SERVER_UNAVAILABLE)
|
|
|
|
|
{
|
|
|
|
|
throw win32Expn;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int GetEffectivePermissions_AuthzAccessCheck(ObjectSecurity sd)
|
|
|
|
|
{
|
|
|
|
|
var request = new AUTHZ_ACCESS_REQUEST();
|
|
|
|
|
request.DesiredAccess = StdAccess.MAXIMUM_ALLOWED;
|
|
|
|
|
request.PrincipalSelfSid = null;
|
|
|
|
|
request.ObjectTypeList = IntPtr.Zero;
|
|
|
|
|
request.ObjectTypeListLength = 0;
|
|
|
|
|
request.OptionalArguments = IntPtr.Zero;
|
|
|
|
|
|
|
|
|
|
var reply = new AUTHZ_ACCESS_REPLY();
|
|
|
|
|
reply.ResultListLength = 1;
|
|
|
|
|
reply.SaclEvaluationResults = IntPtr.Zero;
|
|
|
|
|
reply.GrantedAccessMask = pGrantedAccess = Marshal.AllocHGlobal(sizeof(uint));
|
|
|
|
|
reply.Error = pErrorSecObj = Marshal.AllocHGlobal(sizeof(uint));
|
|
|
|
|
|
|
|
|
|
byte[] rawSD = sd.GetSecurityDescriptorBinaryForm();
|
|
|
|
|
|
|
|
|
|
if (!AuthzAccessCheck(
|
|
|
|
|
AuthzACFlags.None,
|
|
|
|
|
userClientCtxt,
|
|
|
|
|
ref request,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
rawSD,
|
|
|
|
|
null,
|
|
|
|
|
0,
|
|
|
|
|
ref reply,
|
|
|
|
|
IntPtr.Zero))
|
|
|
|
|
{
|
|
|
|
|
var error = Marshal.GetLastWin32Error();
|
|
|
|
|
if (error != 0)
|
|
|
|
|
{
|
|
|
|
|
throw new Win32Exception();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var grantedAccess = Marshal.ReadInt32(pGrantedAccess);
|
|
|
|
|
|
|
|
|
|
return grantedAccess;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void GetEffectivePermissions_FreeResouces()
|
|
|
|
|
{
|
|
|
|
|
Marshal.FreeHGlobal(pGrantedAccess);
|
|
|
|
|
Marshal.FreeHGlobal(pErrorSecObj);
|
|
|
|
|
|
|
|
|
|
if (userClientCtxt != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
AuthzFreeContext(userClientCtxt);
|
|
|
|
|
userClientCtxt = IntPtr.Zero;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static RawSecurityDescriptor GetRawSecurityDescriptor(SafeFileHandle handle, SecurityInformationClass infoClass)
|
|
|
|
|
{
|
|
|
|
|
return new RawSecurityDescriptor(GetByteSecurityDescriptor(handle, infoClass), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] GetByteSecurityDescriptor(SafeFileHandle handle, SecurityInformationClass infoClass)
|
|
|
|
|
{
|
|
|
|
|
var tempSD = IntPtr.Zero;
|
|
|
|
|
var buffer = new byte[0];
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
uint error = GetSecurityInfo(handle,
|
|
|
|
|
ObjectType.File,
|
|
|
|
|
infoClass,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
out tempSD);
|
|
|
|
|
if (error != Win32Error.ERROR_SUCCESS)
|
|
|
|
|
{
|
|
|
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UInt32 sdLength = GetSecurityDescriptorLength(tempSD);
|
|
|
|
|
|
|
|
|
|
buffer = new byte[sdLength];
|
|
|
|
|
Marshal.Copy(tempSD, buffer, 0, (int)sdLength);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
Marshal.FreeHGlobal(tempSD);
|
|
|
|
|
tempSD = IntPtr.Zero;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|