You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

343 lines
13 KiB

10 years ago
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
// 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)
10 years ago
{
var inheritedFrom = new List<string>();
var sdBytes = sd.GetSecurityDescriptorBinaryForm();
var rawSd = new RawSecurityDescriptor(sdBytes, 0);
var acl = auditEntries ? rawSd.SystemAcl : rawSd.DiscretionaryAcl;
10 years ago
if (acl != null)
10 years ago
{
var aceCount = acl.Count;
var aclBytes = new byte[acl.BinaryLength];
acl.GetBinaryForm(aclBytes, 0);
10 years ago
try
{
inheritedFrom = GetInheritedFrom(item.FullName,
aclBytes,
aceCount,
item is DirectoryInfo ? true : false,
auditEntries ? SECURITY_INFORMATION.SACL_SECURITY_INFORMATION : SECURITY_INFORMATION.DACL_SECURITY_INFORMATION);
10 years ago
}
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.
10 years ago
inheritedFrom = new List<string>();
for (int i = 0; i < aceCount; i++)
{
inheritedFrom.Add(acl[i].IsInherited ? "unknown parent" : string.Empty);
10 years ago
}
}
}
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)));
fix: pass on what a later command raises, and return every item for -Filter *.* A throw in a later command, or an error with -ErrorAction Stop, reaches a cmdlet through its Write call as an ordinary exception. The catch for the failures of an item reported it as the error of that item and went on, so that Remove-Item2 -PassThru removed the next item after a throw, and the caller never saw the exception. The earlier check found only the end of the pipeline and a break or continue. BaseCmdlet now notes the exception that its WriteObject, WriteVerbose, and WriteDebug raised, and every catch that can enclose a write passes it on; Get-DiskSpace writes outside its try. Set-NTFSSecurityDescriptor and Get-FileHash2 also caught it at a verbose message. Get-ChildItem2 -Filter *.* returns every item, as Get-ChildItem does. The cmdlet compared each name with the pattern again and dropped the items without a dot, most folders among them; the dot stays an ordinary character in other patterns. The failed lookup of InheritedFrom frees its native buffer. The help paragraph of -Filter has no pair of asterisks, which platyPS turns into emphasis, and the page has an example for *.*. Review of the independent pass: the restored-owner test asserts that a plain write is denied, the drive-mapping helper has guard tests and takes letters that the no-volume tests do not, and the pipeline tests cover a throw, an error with -ErrorAction Stop, and the verbose and debug streams for every cmdlet that can reach the code. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
try
10 years ago
{
fix: pass on what a later command raises, and return every item for -Filter *.* A throw in a later command, or an error with -ErrorAction Stop, reaches a cmdlet through its Write call as an ordinary exception. The catch for the failures of an item reported it as the error of that item and went on, so that Remove-Item2 -PassThru removed the next item after a throw, and the caller never saw the exception. The earlier check found only the end of the pipeline and a break or continue. BaseCmdlet now notes the exception that its WriteObject, WriteVerbose, and WriteDebug raised, and every catch that can enclose a write passes it on; Get-DiskSpace writes outside its try. Set-NTFSSecurityDescriptor and Get-FileHash2 also caught it at a verbose message. Get-ChildItem2 -Filter *.* returns every item, as Get-ChildItem does. The cmdlet compared each name with the pattern again and dropped the items without a dot, most folders among them; the dot stays an ordinary character in other patterns. The failed lookup of InheritedFrom frees its native buffer. The help paragraph of -Filter has no pair of asterisks, which platyPS turns into emphasis, and the page has an example for *.*. Review of the independent pass: the restored-owner test asserts that a plain write is denied, the drive-mapping helper has guard tests and takes letters that the no-volume tests do not, and the pipeline tests cover a throw, an error with -ErrorAction Stop, and the verbose and debug streams for every cmdlet that can reach the code. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
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);
}
10 years ago
fix: pass on what a later command raises, and return every item for -Filter *.* A throw in a later command, or an error with -ErrorAction Stop, reaches a cmdlet through its Write call as an ordinary exception. The catch for the failures of an item reported it as the error of that item and went on, so that Remove-Item2 -PassThru removed the next item after a throw, and the caller never saw the exception. The earlier check found only the end of the pipeline and a break or continue. BaseCmdlet now notes the exception that its WriteObject, WriteVerbose, and WriteDebug raised, and every catch that can enclose a write passes it on; Get-DiskSpace writes outside its try. Set-NTFSSecurityDescriptor and Get-FileHash2 also caught it at a verbose message. Get-ChildItem2 -Filter *.* returns every item, as Get-ChildItem does. The cmdlet compared each name with the pattern again and dropped the items without a dot, most folders among them; the dot stays an ordinary character in other patterns. The failed lookup of InheritedFrom frees its native buffer. The help paragraph of -Filter has no pair of asterisks, which platyPS turns into emphasis, and the page has an example for *.*. Review of the independent pass: the restored-owner test asserts that a plain write is denied, the drive-mapping helper has guard tests and takes letters that the no-volume tests do not, and the pipeline tests cover a throw, an error with -ErrorAction Stop, and the verbose and debug streams for every cmdlet that can reach the code. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
for (int i = 0; i < aceCount; i++)
{
var inheritInfo = pInheritInfo.ElementAt<PINHERITED_FROM>(i);
10 years ago
fix: pass on what a later command raises, and return every item for -Filter *.* A throw in a later command, or an error with -ErrorAction Stop, reaches a cmdlet through its Write call as an ordinary exception. The catch for the failures of an item reported it as the error of that item and went on, so that Remove-Item2 -PassThru removed the next item after a throw, and the caller never saw the exception. The earlier check found only the end of the pipeline and a break or continue. BaseCmdlet now notes the exception that its WriteObject, WriteVerbose, and WriteDebug raised, and every catch that can enclose a write passes it on; Get-DiskSpace writes outside its try. Set-NTFSSecurityDescriptor and Get-FileHash2 also caught it at a verbose message. Get-ChildItem2 -Filter *.* returns every item, as Get-ChildItem does. The cmdlet compared each name with the pattern again and dropped the items without a dot, most folders among them; the dot stays an ordinary character in other patterns. The failed lookup of InheritedFrom frees its native buffer. The help paragraph of -Filter has no pair of asterisks, which platyPS turns into emphasis, and the page has an example for *.*. Review of the independent pass: the restored-owner test asserts that a plain write is denied, the drive-mapping helper has guard tests and takes letters that the no-volume tests do not, and the pipeline tests cover a throw, an error with -ErrorAction Stop, and the verbose and debug streams for every cmdlet that can reach the code. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
inheritedFrom.Add(
!string.IsNullOrEmpty(inheritInfo.AncestorName) && inheritInfo.AncestorName.StartsWith(@"\\?\") ? inheritInfo.AncestorName.Substring(4) : inheritInfo.AncestorName
);
}
10 years ago
fix: pass on what a later command raises, and return every item for -Filter *.* A throw in a later command, or an error with -ErrorAction Stop, reaches a cmdlet through its Write call as an ordinary exception. The catch for the failures of an item reported it as the error of that item and went on, so that Remove-Item2 -PassThru removed the next item after a throw, and the caller never saw the exception. The earlier check found only the end of the pipeline and a break or continue. BaseCmdlet now notes the exception that its WriteObject, WriteVerbose, and WriteDebug raised, and every catch that can enclose a write passes it on; Get-DiskSpace writes outside its try. Set-NTFSSecurityDescriptor and Get-FileHash2 also caught it at a verbose message. Get-ChildItem2 -Filter *.* returns every item, as Get-ChildItem does. The cmdlet compared each name with the pattern again and dropped the items without a dot, most folders among them; the dot stays an ordinary character in other patterns. The failed lookup of InheritedFrom frees its native buffer. The help paragraph of -Filter has no pair of asterisks, which platyPS turns into emphasis, and the page has an example for *.*. Review of the independent pass: the restored-owner test asserts that a plain write is denied, the drive-mapping helper has guard tests and takes letters that the no-volume tests do not, and the pipeline tests cover a throw, an error with -ErrorAction Stop, and the verbose and debug streams for every cmdlet that can reach the code. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
FreeInheritedFromArray(pInheritInfo, (ushort)aceCount, IntPtr.Zero);
}
finally
{
// Also after a failed call, which the fallback of GetInheritedFrom now expects.
Marshal.FreeHGlobal(pInheritInfo);
}
10 years ago
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
// 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;
}
}
10 years ago
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)
10 years ago
{
throw new Win32Exception(error);
}
// 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))
10 years ago
{
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
}
}