From 8f5da8b33de450fcbc8e9e770292216b867678db Mon Sep 17 00:00:00 2001 From: Raimund Andree Date: Thu, 8 Oct 2026 10:46:31 +0000 Subject: [PATCH] test: write the deny entries of the test helper without Set-Acl Set-Acl compares AreAuditRulesProtected of the new descriptor with AreAccessRulesProtected of the item (FileSystemSecurity.cs in PowerShell), so it also writes the audit section of an item whose DACL is protected. Without the Security privilege that fails with PrivilegeNotHeldException: after Disable-NTFSAccessInheritance, Add-TestDenyRule added nothing, and the take-ownership test of PathErrors.Tests.ps1 failed as a basic user. With the privilege, Set-Acl wrote all sections and dropped the audit entries of the item. Add-TestDenyRule now writes only the DACL with SetAccessControl. Two regression tests cover both effects; each failed before the change in its configuration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: AI Assistant --- Tests/TestHelpers.Tests.ps1 | 37 +++++++++++++++++++++++++++++++++++++ Tests/TestHelpers.psm1 | 11 ++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Tests/TestHelpers.Tests.ps1 b/Tests/TestHelpers.Tests.ps1 index 0969633..1daf2f4 100644 --- a/Tests/TestHelpers.Tests.ps1 +++ b/Tests/TestHelpers.Tests.ps1 @@ -11,6 +11,8 @@ BeforeDiscovery { Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force # Assigning an owner other than the user or one of its groups needs the Restore privilege. $canAssignAnyOwner = Test-PrivilegeHeld -Name 'SeRestorePrivilege' + # Reading and writing audit entries needs the Security privilege. + $holdsSecurityPrivilege = Test-PrivilegeHeld -Name 'SeSecurityPrivilege' } BeforeAll { @@ -212,6 +214,41 @@ Describe 'Test helpers' { $rules[0].AccessControlType | Should -Be 'Deny' } + # Set-Acl also writes the audit section of an item whose DACL is protected, which fails without the Security + # privilege. + It 'Should add a deny entry to an item whose DACL is protected' { + $file = New-TestSandboxItem -Sandbox $sandbox -Name 'Protected' + & icacls.exe $file /inheritance:d *> $null + $LASTEXITCODE | Should -Be 0 + + Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-5-32-546' = 'ReadData' } + + $acl = Get-Acl -LiteralPath $file + $acl.AreAccessRulesProtected | Should -BeTrue + $rules = @($acl.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) | + Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-32-546' }) + $rules | Should -HaveCount 1 + $rules[0].AccessControlType | Should -Be 'Deny' + } + + # With the Security privilege, Set-Acl writes all sections, so the audit entries of the item would be lost. + It 'Should keep the audit entries of the item' -Skip:(-not $holdsSecurityPrivilege) { + $file = New-TestSandboxItem -Sandbox $sandbox -Name 'Audited' + $auditAcl = Get-Acl -LiteralPath $file -Audit + $auditAcl.AddAuditRule((New-Object -TypeName 'System.Security.AccessControl.FileSystemAuditRule' -ArgumentList ( + (New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList 'S-1-1-0'), + [System.Security.AccessControl.FileSystemRights]::Delete, + [System.Security.AccessControl.AuditFlags]::Success + ))) + Set-Acl -LiteralPath $file -AclObject $auditAcl + + Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-5-32-546' = 'ReadData' } + + $auditRules = @((Get-Acl -LiteralPath $file -Audit).GetAuditRules($true, $false, [System.Security.Principal.SecurityIdentifier])) + $auditRules | Should -HaveCount 1 + $auditRules[0].IdentityReference.Value | Should -Be 'S-1-1-0' + } + It 'Should refuse an item outside the sandbox' { { Add-TestDenyRule -Sandbox $sandbox -Path "$sandbox-Other\File.txt" -Rights @{ 'S-1-5-32-546' = 'ReadData' } } | Should -Throw -ExpectedMessage 'Refusing to change*' diff --git a/Tests/TestHelpers.psm1 b/Tests/TestHelpers.psm1 index 277a485..6a15057 100644 --- a/Tests/TestHelpers.psm1 +++ b/Tests/TestHelpers.psm1 @@ -277,6 +277,7 @@ function Add-TestDenyRule { ) Assert-TestSandboxPath -Sandbox $Sandbox -Path $Path + $item = Get-Item -LiteralPath $Path -Force -ErrorAction Stop $acl = Get-Acl -LiteralPath $Path foreach ($sid in $Rights.Keys) { $identity = New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList $sid @@ -286,7 +287,15 @@ function Add-TestDenyRule { ) $acl.AddAccessRule($rule) } - Set-Acl -LiteralPath $Path -AclObject $acl + # Not Set-Acl: it compares AreAuditRulesProtected with AreAccessRulesProtected, so it also writes the audit + # section of an item whose DACL is protected, which fails without the Security privilege. With the privilege, it + # writes all sections and drops the audit entries. SetAccessControl writes only the DACL, the section that changed. + if ($PSVersionTable.PSEdition -eq 'Core') { + [System.IO.FileSystemAclExtensions]::SetAccessControl($item, $acl) + } + else { + $item.SetAccessControl($acl) + } } function Set-TestOwner {