|
|
|
|
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;
|
|
|
|
|
|
fix(effective-access): answer a user who isn't an administrator for this computer
On a computer in a domain, Get-NTFSEffectiveAccess wrote "Access is denied"
and no result for every user who wasn't an administrator of the computer,
also for the default -ServerName localhost and for every other name of this
computer. The remote interface of the authorization manager of a computer
answers only its administrators and the members of Access Control Assistance
Operators, and a computer in a domain offers that interface to every caller.
On a computer outside a domain the interface is not reachable, so the cmdlet
already used the local authorization manager there, which is why the tests
passed on the development host and on the CI runners.
For a name of this computer, the cmdlet now uses the local authorization
manager when the remote one refuses the user. That manager is the one the name
asks for, and it answered correctly in every probe on Windows Server 2019,
2022, and 2025 and on Windows 11: for a standard domain user, a local standard
user, and an administrator with a filtered token, for the user's own account,
Everyone, the Administrator of the computer, and the Administrator and Domain
Users of the domain. For the name of another computer, the denial stays an
error, as the cmdlet page and the live test of the delegated account describe.
Twenty tests of the suite failed in the basic-user mode on every domain-joined
machine of the operating-system matrix, with the published 5.0.0-rc7 code and
with the code before this change, and pass with it (Windows Server 2019: basic
user 782 and 780 passed, 0 failed, in Windows PowerShell and PowerShell 7). A
new live test runs the case as the administrator of the file server, who isn't
an administrator of the client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
// Whether the remote resource manager is the one of this computer. Its remote interface answers only the
|
|
|
|
|
// administrators of the computer and the members of Access Control Assistance Operators.
|
|
|
|
|
bool remoteResourceManagerIsLocal;
|
|
|
|
|
|
|
|
|
|
#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)));
|
|
|
|
|
|
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 <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
try
|
|
|
|
|
{
|
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 <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
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);
|
|
|
|
|
}
|
|
|
|
|
|
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 <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
for (int i = 0; i < aceCount; i++)
|
|
|
|
|
{
|
|
|
|
|
var inheritInfo = pInheritInfo.ElementAt<PINHERITED_FROM>(i);
|
|
|
|
|
|
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 <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
inheritedFrom.Add(
|
|
|
|
|
!string.IsNullOrEmpty(inheritInfo.AncestorName) && inheritInfo.AncestorName.StartsWith(@"\\?\") ? inheritInfo.AncestorName.Substring(4) : inheritInfo.AncestorName
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
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 <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
fix(access): report what the initialization of the authorization manager throws
Get-NTFSEffectiveAccess returned no access and no error when Windows refused
to start the authorization manager for the computer of -ServerName.
Win32.GetEffectiveAccess ignored what the initialization threw (an outer
catch { }), so the result looked like an account without any rights. It
reproduces on Windows Server 2025 in both editions with a name of 32,718
characters or more, which fails with error 14, where a shorter name gets RPC
server unavailable and falls back to this computer.
One try/catch now records every exception, and the cmdlet writes a
GetEffectiveAccessError. The warning that the result is based on this computer
comes only with a result now: it also preceded an error, such as for an
account that Windows can't resolve when the computer can't be reached, which
the cmdlet page and the lab tests contradict.
Two tests failed first in Windows PowerShell 5.1 and PowerShell 7, as an
administrator and as a basic user, and pass in all four configurations. The
cmdlet page, the help file, and the CHANGELOG say what changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
|
|
|
GetEffectivePermissions_AuthzInitializeContextFromSid(identity);
|
|
|
|
|
effectiveAccess = GetEffectivePermissions_AuthzAccessCheck(sd);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
// Also what the initialization of the resource manager throws. Before 5.0.0-rc8, that was ignored, and
|
|
|
|
|
// the caller got no access without an error.
|
|
|
|
|
authzException = ex;
|
|
|
|
|
}
|
|
|
|
|
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
|
fix(access): report what the initialization of the authorization manager throws
Get-NTFSEffectiveAccess returned no access and no error when Windows refused
to start the authorization manager for the computer of -ServerName.
Win32.GetEffectiveAccess ignored what the initialization threw (an outer
catch { }), so the result looked like an account without any rights. It
reproduces on Windows Server 2025 in both editions with a name of 32,718
characters or more, which fails with error 14, where a shorter name gets RPC
server unavailable and falls back to this computer.
One try/catch now records every exception, and the cmdlet writes a
GetEffectiveAccessError. The warning that the result is based on this computer
comes only with a result now: it also preceded an error, such as for an
account that Windows can't resolve when the computer can't be reached, which
the cmdlet page and the lab tests contradict.
Two tests failed first in Windows PowerShell 5.1 and PowerShell 7, as an
administrator and as a basic user, and pass in all four configurations. The
cmdlet page, the help file, and the CHANGELOG say what changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
|
|
|
// name, or the fully qualified domain name. It answers false when it can't tell and doesn't throw, so that the
|
|
|
|
|
// fallback for a computer that can't be reached doesn't fail on this question.
|
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
|
|
|
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;
|
|
|
|
|
|
fix(security): report one audit inheritance by path and by descriptor
The module's own tests ran on Windows Server 2019, 2022, and 2025 and on
Windows 11 in the operating-system matrix. Two tests failed on every
domain-joined machine and passed on the development host:
- Get-NTFSInheritance -SecurityDescriptor reported AuditInheritanceEnabled
as $false for an item without audit entries, where -Path reported $true.
On these computers Windows reports the SACL as protected from
inheritance when it reads all sections together, and as not protected
when it reads the SACL alone. The descriptor now takes the audit section
from a separate read, like it already did for the access section. Write()
stores the sections that were read, so a descriptor with the wrong flag
would also have written the SACL back as protected.
- Get-NTFSEffectiveAccess -ServerName '' wrote an "Access is denied"
error on computers where Windows takes an empty name for this computer.
An empty name no longer asks the remote interface of the authorization
manager; the cmdlet warns and returns the result of this computer, like
for any name that can't be reached.
The existing tests that found them stay as the regression guards: they were
red on three virtual machines (Windows Server 2022 and 2025, Windows 11)
and are green after the change. Three tests are new: the audit state of a
file and a folder without audit entries, and of a file and a folder with
disabled audit inheritance, by path and by descriptor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
// An empty name names no computer. Windows takes it for this computer on some computers, where the remote
|
|
|
|
|
// interface then refuses the check with "Access is denied", and for an unreachable one on others. So the
|
|
|
|
|
// remote interface isn't asked, and the local authorization manager calculates the result, like for any
|
|
|
|
|
// name that can't be reached.
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(serverName))
|
|
|
|
|
{
|
|
|
|
|
var rpcInitInfo = new AUTHZ_RPC_INIT_INFO_CLIENT();
|
|
|
|
|
|
fix(security): report one audit inheritance by path and by descriptor
The module's own tests ran on Windows Server 2019, 2022, and 2025 and on
Windows 11 in the operating-system matrix. Two tests failed on every
domain-joined machine and passed on the development host:
- Get-NTFSInheritance -SecurityDescriptor reported AuditInheritanceEnabled
as $false for an item without audit entries, where -Path reported $true.
On these computers Windows reports the SACL as protected from
inheritance when it reads all sections together, and as not protected
when it reads the SACL alone. The descriptor now takes the audit section
from a separate read, like it already did for the access section. Write()
stores the sections that were read, so a descriptor with the wrong flag
would also have written the SACL back as protected.
- Get-NTFSEffectiveAccess -ServerName '' wrote an "Access is denied"
error on computers where Windows takes an empty name for this computer.
An empty name no longer asks the remote interface of the authorization
manager; the cmdlet warns and returns the result of this computer, like
for any name that can't be reached.
The existing tests that found them stay as the regression guards: they were
red on three virtual machines (Windows Server 2022 and 2025, Windows 11)
and are green after the change. Three tests are new: the audit state of a
file and a folder without audit entries, and of a file and a folder with
disabled audit inheritance, by path and by descriptor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
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))
|
|
|
|
|
{
|
|
|
|
|
remoteServerAvailable = true;
|
fix(effective-access): answer a user who isn't an administrator for this computer
On a computer in a domain, Get-NTFSEffectiveAccess wrote "Access is denied"
and no result for every user who wasn't an administrator of the computer,
also for the default -ServerName localhost and for every other name of this
computer. The remote interface of the authorization manager of a computer
answers only its administrators and the members of Access Control Assistance
Operators, and a computer in a domain offers that interface to every caller.
On a computer outside a domain the interface is not reachable, so the cmdlet
already used the local authorization manager there, which is why the tests
passed on the development host and on the CI runners.
For a name of this computer, the cmdlet now uses the local authorization
manager when the remote one refuses the user. That manager is the one the name
asks for, and it answered correctly in every probe on Windows Server 2019,
2022, and 2025 and on Windows 11: for a standard domain user, a local standard
user, and an administrator with a filtered token, for the user's own account,
Everyone, the Administrator of the computer, and the Administrator and Domain
Users of the domain. For the name of another computer, the denial stays an
error, as the cmdlet page and the live test of the delegated account describe.
Twenty tests of the suite failed in the basic-user mode on every domain-joined
machine of the operating-system matrix, with the published 5.0.0-rc7 code and
with the code before this change, and pass with it (Windows Server 2019: basic
user 782 and 780 passed, 0 failed, in Windows PowerShell and PowerShell 7). A
new live test runs the case as the administrator of the file server, who isn't
an administrator of the client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
remoteResourceManagerIsLocal = IsLocalComputer(serverName);
|
fix(security): report one audit inheritance by path and by descriptor
The module's own tests ran on Windows Server 2019, 2022, and 2025 and on
Windows 11 in the operating-system matrix. Two tests failed on every
domain-joined machine and passed on the development host:
- Get-NTFSInheritance -SecurityDescriptor reported AuditInheritanceEnabled
as $false for an item without audit entries, where -Path reported $true.
On these computers Windows reports the SACL as protected from
inheritance when it reads all sections together, and as not protected
when it reads the SACL alone. The descriptor now takes the audit section
from a separate read, like it already did for the access section. Write()
stores the sections that were read, so a descriptor with the wrong flag
would also have written the SACL back as protected.
- Get-NTFSEffectiveAccess -ServerName '' wrote an "Access is denied"
error on computers where Windows takes an empty name for this computer.
An empty name no longer asks the remote interface of the authorization
manager; the cmdlet warns and returns the result of this computer, like
for any name that can't be reached.
The existing tests that found them stay as the regression guards: they were
red on three virtual machines (Windows Server 2022 and 2025, Windows 11)
and are green after the change. Three tests are new: the audit state of a
file and a folder without audit entries, and of a file and a folder with
disabled audit inheritance, by path and by descriptor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int error = Marshal.GetLastWin32Error();
|
fix(effective-access): answer a user who isn't an administrator for this computer
On a computer in a domain, Get-NTFSEffectiveAccess wrote "Access is denied"
and no result for every user who wasn't an administrator of the computer,
also for the default -ServerName localhost and for every other name of this
computer. The remote interface of the authorization manager of a computer
answers only its administrators and the members of Access Control Assistance
Operators, and a computer in a domain offers that interface to every caller.
On a computer outside a domain the interface is not reachable, so the cmdlet
already used the local authorization manager there, which is why the tests
passed on the development host and on the CI runners.
For a name of this computer, the cmdlet now uses the local authorization
manager when the remote one refuses the user. That manager is the one the name
asks for, and it answered correctly in every probe on Windows Server 2019,
2022, and 2025 and on Windows 11: for a standard domain user, a local standard
user, and an administrator with a filtered token, for the user's own account,
Everyone, the Administrator of the computer, and the Administrator and Domain
Users of the domain. For the name of another computer, the denial stays an
error, as the cmdlet page and the live test of the delegated account describe.
Twenty tests of the suite failed in the basic-user mode on every domain-joined
machine of the operating-system matrix, with the published 5.0.0-rc7 code and
with the code before this change, and pass with it (Windows Server 2019: basic
user 782 and 780 passed, 0 failed, in Windows PowerShell and PowerShell 7). A
new live test runs the case as the administrator of the file server, who isn't
an administrator of the client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
bool isLocalComputer = IsLocalComputer(serverName);
|
|
|
|
|
|
|
|
|
|
// 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.
|
fix(effective-access): answer a user who isn't an administrator for this computer
On a computer in a domain, Get-NTFSEffectiveAccess wrote "Access is denied"
and no result for every user who wasn't an administrator of the computer,
also for the default -ServerName localhost and for every other name of this
computer. The remote interface of the authorization manager of a computer
answers only its administrators and the members of Access Control Assistance
Operators, and a computer in a domain offers that interface to every caller.
On a computer outside a domain the interface is not reachable, so the cmdlet
already used the local authorization manager there, which is why the tests
passed on the development host and on the CI runners.
For a name of this computer, the cmdlet now uses the local authorization
manager when the remote one refuses the user. That manager is the one the name
asks for, and it answered correctly in every probe on Windows Server 2019,
2022, and 2025 and on Windows 11: for a standard domain user, a local standard
user, and an administrator with a filtered token, for the user's own account,
Everyone, the Administrator of the computer, and the Administrator and Domain
Users of the domain. For the name of another computer, the denial stays an
error, as the cmdlet page and the live test of the delegated account describe.
Twenty tests of the suite failed in the basic-user mode on every domain-joined
machine of the operating-system matrix, with the published 5.0.0-rc7 code and
with the code before this change, and pass with it (Windows Server 2019: basic
user 782 and 780 passed, 0 failed, in Windows PowerShell and PowerShell 7). A
new live test runs the case as the administrator of the file server, who isn't
an administrator of the client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
// This computer can also refuse the caller, who isn't one of its administrators; its own manager
|
|
|
|
|
// answers then, too.
|
|
|
|
|
if (error != Win32Error.EPT_S_NOT_REGISTERED && error != Win32Error.RPC_S_SERVER_UNAVAILABLE &&
|
|
|
|
|
!(isLocalComputer && error == Win32Error.ERROR_ACCESS_DENIED))
|
|
|
|
|
{
|
|
|
|
|
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.
|
fix(effective-access): answer a user who isn't an administrator for this computer
On a computer in a domain, Get-NTFSEffectiveAccess wrote "Access is denied"
and no result for every user who wasn't an administrator of the computer,
also for the default -ServerName localhost and for every other name of this
computer. The remote interface of the authorization manager of a computer
answers only its administrators and the members of Access Control Assistance
Operators, and a computer in a domain offers that interface to every caller.
On a computer outside a domain the interface is not reachable, so the cmdlet
already used the local authorization manager there, which is why the tests
passed on the development host and on the CI runners.
For a name of this computer, the cmdlet now uses the local authorization
manager when the remote one refuses the user. That manager is the one the name
asks for, and it answered correctly in every probe on Windows Server 2019,
2022, and 2025 and on Windows 11: for a standard domain user, a local standard
user, and an administrator with a filtered token, for the user's own account,
Everyone, the Administrator of the computer, and the Administrator and Domain
Users of the domain. For the name of another computer, the denial stays an
error, as the cmdlet page and the live test of the delegated account describe.
Twenty tests of the suite failed in the basic-user mode on every domain-joined
machine of the operating-system matrix, with the published 5.0.0-rc7 code and
with the code before this change, and pass with it (Windows Server 2019: basic
user 782 and 780 passed, 0 failed, in Windows PowerShell and PowerShell 7). A
new live test runs the case as the administrator of the file server, who isn't
an administrator of the client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
if (isLocalComputer)
|
|
|
|
|
{
|
|
|
|
|
remoteServerAvailable = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
fix(security): report one audit inheritance by path and by descriptor
The module's own tests ran on Windows Server 2019, 2022, and 2025 and on
Windows 11 in the operating-system matrix. Two tests failed on every
domain-joined machine and passed on the development host:
- Get-NTFSInheritance -SecurityDescriptor reported AuditInheritanceEnabled
as $false for an item without audit entries, where -Path reported $true.
On these computers Windows reports the SACL as protected from
inheritance when it reads all sections together, and as not protected
when it reads the SACL alone. The descriptor now takes the audit section
from a separate read, like it already did for the access section. Write()
stores the sections that were read, so a descriptor with the wrong flag
would also have written the SACL back as protected.
- Get-NTFSEffectiveAccess -ServerName '' wrote an "Access is denied"
error on computers where Windows takes an empty name for this computer.
An empty name no longer asks the remote interface of the authorization
manager; the cmdlet warns and returns the result of this computer, like
for any name that can't be reached.
The existing tests that found them stay as the regression guards: they were
red on three virtual machines (Windows Server 2022 and 2025, Windows 11)
and are green after the change. Three tests are new: the audit state of a
file and a folder without audit entries, and of a file and a folder with
disabled audit inheritance, by path and by descriptor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
|
fix(effective-access): answer a user who isn't an administrator for this computer
On a computer in a domain, Get-NTFSEffectiveAccess wrote "Access is denied"
and no result for every user who wasn't an administrator of the computer,
also for the default -ServerName localhost and for every other name of this
computer. The remote interface of the authorization manager of a computer
answers only its administrators and the members of Access Control Assistance
Operators, and a computer in a domain offers that interface to every caller.
On a computer outside a domain the interface is not reachable, so the cmdlet
already used the local authorization manager there, which is why the tests
passed on the development host and on the CI runners.
For a name of this computer, the cmdlet now uses the local authorization
manager when the remote one refuses the user. That manager is the one the name
asks for, and it answered correctly in every probe on Windows Server 2019,
2022, and 2025 and on Windows 11: for a standard domain user, a local standard
user, and an administrator with a filtered token, for the user's own account,
Everyone, the Administrator of the computer, and the Administrator and Domain
Users of the domain. For the name of another computer, the denial stays an
error, as the cmdlet page and the live test of the delegated account describe.
Twenty tests of the suite failed in the basic-user mode on every domain-joined
machine of the operating-system matrix, with the published 5.0.0-rc7 code and
with the code before this change, and pass with it (Windows Server 2019: basic
user 782 and 780 passed, 0 failed, in Windows PowerShell and PowerShell 7). A
new live test runs the case as the administrator of the file server, who isn't
an administrator of the client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
GetEffectivePermissions_AuthzInitializeLocalResourceManager();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void GetEffectivePermissions_AuthzInitializeLocalResourceManager()
|
|
|
|
|
{
|
fix(security): report one audit inheritance by path and by descriptor
The module's own tests ran on Windows Server 2019, 2022, and 2025 and on
Windows 11 in the operating-system matrix. Two tests failed on every
domain-joined machine and passed on the development host:
- Get-NTFSInheritance -SecurityDescriptor reported AuditInheritanceEnabled
as $false for an item without audit entries, where -Path reported $true.
On these computers Windows reports the SACL as protected from
inheritance when it reads all sections together, and as not protected
when it reads the SACL alone. The descriptor now takes the audit section
from a separate read, like it already did for the access section. Write()
stores the sections that were read, so a descriptor with the wrong flag
would also have written the SACL back as protected.
- Get-NTFSEffectiveAccess -ServerName '' wrote an "Access is denied"
error on computers where Windows takes an empty name for this computer.
An empty name no longer asks the remote interface of the authorization
manager; the cmdlet warns and returns the result of this computer, like
for any name that can't be reached.
The existing tests that found them stay as the regression guards: they were
red on three virtual machines (Windows Server 2022 and 2025, Windows 11)
and are green after the change. Three tests are new: the audit state of a
file and a folder without audit entries, and of a file and a folder with
disabled audit inheritance, by path and by descriptor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
//
|
|
|
|
|
// 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))
|
|
|
|
|
{
|
fix(security): report one audit inheritance by path and by descriptor
The module's own tests ran on Windows Server 2019, 2022, and 2025 and on
Windows 11 in the operating-system matrix. Two tests failed on every
domain-joined machine and passed on the development host:
- Get-NTFSInheritance -SecurityDescriptor reported AuditInheritanceEnabled
as $false for an item without audit entries, where -Path reported $true.
On these computers Windows reports the SACL as protected from
inheritance when it reads all sections together, and as not protected
when it reads the SACL alone. The descriptor now takes the audit section
from a separate read, like it already did for the access section. Write()
stores the sections that were read, so a descriptor with the wrong flag
would also have written the SACL back as protected.
- Get-NTFSEffectiveAccess -ServerName '' wrote an "Access is denied"
error on computers where Windows takes an empty name for this computer.
An empty name no longer asks the remote interface of the authorization
manager; the cmdlet warns and returns the result of this computer, like
for any name that can't be reached.
The existing tests that found them stay as the regression guards: they were
red on three virtual machines (Windows Server 2022 and 2025, Windows 11)
and are green after the change. Three tests are new: the audit state of a
file and a folder without audit entries, and of a file and a folder with
disabled audit inheritance, by path and by descriptor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
fix(effective-access): answer a user who isn't an administrator for this computer
On a computer in a domain, Get-NTFSEffectiveAccess wrote "Access is denied"
and no result for every user who wasn't an administrator of the computer,
also for the default -ServerName localhost and for every other name of this
computer. The remote interface of the authorization manager of a computer
answers only its administrators and the members of Access Control Assistance
Operators, and a computer in a domain offers that interface to every caller.
On a computer outside a domain the interface is not reachable, so the cmdlet
already used the local authorization manager there, which is why the tests
passed on the development host and on the CI runners.
For a name of this computer, the cmdlet now uses the local authorization
manager when the remote one refuses the user. That manager is the one the name
asks for, and it answered correctly in every probe on Windows Server 2019,
2022, and 2025 and on Windows 11: for a standard domain user, a local standard
user, and an administrator with a filtered token, for the user's own account,
Everyone, the Administrator of the computer, and the Administrator and Domain
Users of the domain. For the name of another computer, the denial stays an
error, as the cmdlet page and the live test of the delegated account describe.
Twenty tests of the suite failed in the basic-user mode on every domain-joined
machine of the operating-system matrix, with the published 5.0.0-rc7 code and
with the code before this change, and pass with it (Windows Server 2019: basic
user 782 and 780 passed, 0 failed, in Windows PowerShell and PowerShell 7). A
new live test runs the case as the administrator of the file server, who isn't
an administrator of the client.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
// A computer in a domain offers the remote interface of its authorization manager to every caller, but
|
|
|
|
|
// answers only its administrators and the members of Access Control Assistance Operators; any other
|
|
|
|
|
// account gets "Access is denied", whichever account the check is for. For a name of this computer, the
|
|
|
|
|
// local authorization manager is the manager of that computer and answers every caller. For another
|
|
|
|
|
// computer, the denial stays an error: no access instead would be a wrong result.
|
|
|
|
|
if (win32Expn.NativeErrorCode == Win32Error.ERROR_ACCESS_DENIED && remoteResourceManagerIsLocal)
|
|
|
|
|
{
|
|
|
|
|
remoteResourceManagerIsLocal = false;
|
|
|
|
|
userClientCtxt = IntPtr.Zero;
|
|
|
|
|
authzRM.Dispose();
|
|
|
|
|
GetEffectivePermissions_AuthzInitializeLocalResourceManager();
|
|
|
|
|
GetEffectivePermissions_AuthzInitializeContextFromSid(id);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|