Merge remote-tracking branch 'origin/dev' into feat/381-applocker-readiness

# Conflicts:
#	WELA.ps1
This commit is contained in:
Shirofune-Security committed 2026-09-19 06:52:50 +09:00
commit aff422bf99
14 files changed
+877 -3

No files matched your search

@@ -0,0 +1,85 @@
# Actual SACL writes, confined to fresh temporary namespaces on a disposable VM.
# Existing namespaces are read only as the parent/factory; never passed to a setter.
param([switch]$AllowDisposableNamespaceWrite, [string]$EvidencePath)
$ErrorActionPreference = 'Stop'
if (-not $AllowDisposableNamespaceWrite) { throw 'This integration test requires -AllowDisposableNamespaceWrite on a disposable Windows VM.' }
if ($env:OS -ne 'Windows_NT') { throw 'Disposable-namespace integration requires Windows.' }
$repo = Split-Path $PSScriptRoot -Parent
. (Join-Path $repo 'scripts/Configuration.ps1')
. (Join-Path $repo 'scripts/WmiNamespaceAuditing.ps1')
Initialize-WelaWmiInterop
$script:assertions = 0
function Assert($Condition, [string]$Message) { if (-not $Condition) { throw "FAIL: $Message" }; $script:assertions++ }
$evidence = [pscustomobject]@{
SchemaVersion = 1; Computer = $env:COMPUTERNAME; OperatingSystem = [Environment]::OSVersion.VersionString
PowerShell = $PSVersionTable.PSVersion.ToString(); StartedUtc = [DateTime]::UtcNow.ToString('o')
Scope = 'Real SACL write/readback on uniquely created root child namespaces only'
EventGeneration = 'Not tested'; Forwarding = 'Not tested'; Cases = @(); Complete = $false
}
$backup = Join-Path ([IO.Path]::GetTempPath()) ('wela-wmi-integration-' + [guid]::NewGuid().ToString('N'))
try {
foreach ($flags in @(64, 66)) {
$name = 'WelaSaclTest_' + [guid]::NewGuid().ToString('N')
$namespace = 'root\' + $name
Assert ($namespace -match '^root\\WelaSaclTest_[0-9a-f]{32}$') 'Only the generated test namespace can receive writes'
$created = $false; $factory = $null; $instance = $null
$case = [pscustomobject]@{ Namespace=$namespace; AceFlags=$flags; Before=$null; After=$null; Result=$null; RepeatResult=$null; BeforeControlFlags=$null; ExpectedControlFlags=$null; AfterControlFlags=$null; Removed=$false }
$evidence.Cases += $case
try {
# CreateOnly is essential: never adopt or delete an existing namespace.
$factory = New-Object System.Management.ManagementClass -ArgumentList '\\.\root:__Namespace'
$instance = $factory.CreateInstance(); $instance.Name = $name
$options = New-Object System.Management.PutOptions
$options.Type = [System.Management.PutType]::CreateOnly
$createdPath = $instance.Put($options)
$created = $true
Assert ($createdPath.RelativePath -eq ('__NAMESPACE.Name="' + $name + '"')) 'Created namespace identity matches the generated name'
$before = Get-WelaWmiNamespaceSnapshot $namespace
$case.Before = $before
$beforeData = $before.DescriptorJson | ConvertFrom-Json
$case.BeforeControlFlags = [uint32]$beforeData.ControlFlags
$case.ExpectedControlFlags = [uint32]$beforeData.ControlFlags -bor 16
Assert (@($beforeData.SACL | Where-Object { $null -ne $_ }).Count -eq 0) 'Fixture exercises first SACL creation on a namespace with no existing audit ACEs'
# Reuse the real ASD root-default mask/SID, with the test target and
# explicit inheritance mode. Production profile scope is unchanged.
$definitions = @(Get-WelaWmiAuditDefinitions -Namespace 'root\default' -IncludeChildren)
$definitions[0].Namespace = $namespace; $definitions[0].AceFlags = [uint32]$flags
$entry = [pscustomobject]@{ Namespace=$namespace; Definitions=$definitions }
$context = New-WelaConfigurationContext -Auto -BackupPath (Join-Path $backup ('first-' + $flags))
Set-WelaWmiAuditControls -Context $context -Plan @($entry)
$case.Result = Complete-WelaConfiguration -Context $context -Scope 'wmi-namespace-sacl-only'
$after = Get-WelaWmiNamespaceSnapshot $namespace
$case.After = $after
$afterData = $after.DescriptorJson | ConvertFrom-Json
$case.AfterControlFlags = [uint32]$afterData.ControlFlags
Write-Host "Native flags: mode=$flags before=$($case.BeforeControlFlags) expected=$($case.ExpectedControlFlags) after=$($case.AfterControlFlags)"
Assert ($case.Result.ExitCode -eq 0 -and $case.Result.Results[0].Status -eq 'Applied') 'Actual production runner accepts the provider readback after first SACL creation'
Assert ($case.AfterControlFlags -eq $case.ExpectedControlFlags) 'Provider control flags match the exact preservation contract for this tested host/mode'
Assert (Test-WelaWmiDescriptorPreserved $beforeData $afterData) 'Original access fields and existing ACEs survive the real SACL-only write'
Assert (@(Get-WelaWmiMissingAces $afterData $definitions).Count -eq 0) 'Native provider stores the requested SID/mask/outcome/inheritance'
$repeat = New-WelaConfigurationContext -Auto -BackupPath (Join-Path $backup ('repeat-' + $flags))
Set-WelaWmiAuditControls -Context $repeat -Plan @($entry)
$case.RepeatResult = Complete-WelaConfiguration -Context $repeat -Scope 'wmi-namespace-sacl-only'
Assert ($case.RepeatResult.ExitCode -eq 0 -and $case.RepeatResult.Results[0].Status -eq 'AlreadyCompliant') 'Repeated real configuration is idempotent'
Assert ((Get-WelaWmiNamespaceSnapshot $namespace).DescriptorJson -ceq $after.DescriptorJson) 'Repeat leaves the full descriptor unchanged'
} finally {
try {
if ($created) {
# The only deletion target is the instance this run created.
$instance.Delete()
$remaining = @(Get-CimInstance -Namespace root -ClassName __Namespace -Filter ("Name='$name'") -ErrorAction Stop)
Assert ($remaining.Count -eq 0) 'Owned temporary namespace was removed'
$case.Removed = $true
}
} finally {
if ($instance) { $instance.Dispose() }
if ($factory) { $factory.Dispose() }
}
}
}
$evidence.Complete = $true
Write-Host "PASS: $script:assertions disposable-namespace native SACL assertions. Event generation and forwarding were not tested."
} finally {
if ($EvidencePath) { $evidence | ConvertTo-Json -Depth 25 | Set-Content -LiteralPath $EvidencePath -Encoding UTF8 -ErrorAction Stop }
if (Test-Path -LiteralPath $backup) { Remove-Item -LiteralPath $backup -Recurse -Force -ErrorAction Stop }
}
@@ -0,0 +1,76 @@
# Compile the production privilege lifecycle with in-memory native API substitutes.
# No process token or live WMI namespace is modified by this test.
$ErrorActionPreference = 'Stop'
$repo = Split-Path $PSScriptRoot -Parent
$source = Get-Content -LiteralPath (Join-Path $repo 'scripts/WmiNamespaceAuditing.ps1') -Raw
. (Join-Path $repo 'scripts/WmiNamespaceAuditing.ps1')
$script:assertions = 0
function Assert($Condition, [string]$Message) { if (-not $Condition) { throw "FAIL: $Message" }; $script:assertions++ }
$match = [regex]::Match($source, "(?s)Add-Type -TypeDefinition @'\r?\n(.*?)\r?\n'@ -ErrorAction Stop")
Assert $match.Success 'Production privilege helper located'
$csharp = $match.Groups[1].Value.Replace('namespace Wela {', 'namespace WelaPrivilegeFixture {')
# Replace only external API declarations/error reads, retaining constructor and
# Dispose control flow from the shipped helper rather than mirroring that logic.
$csharp = [regex]::Replace($csharp, '(?m)^ \[DllImport[^\r\n]+\r?\n', '')
$csharp = $csharp.Replace('Marshal.GetLastWin32Error()', 'TestError')
$native = @'
public static int TestError, EnableError, RestoreError, AdjustCalls, CloseCalls;
public static bool RestoreSuccess = true;
public static void Reset() { TestError=EnableError=RestoreError=AdjustCalls=CloseCalls=0; RestoreSuccess=true; }
static IntPtr GetCurrentProcess() { return (IntPtr)1; }
static bool CloseHandle(IntPtr handle) { CloseCalls++; return true; }
static bool OpenProcessToken(IntPtr process, uint access, out IntPtr token) { token=(IntPtr)2; return true; }
static bool LookupPrivilegeValue(string system, string name, out Luid luid) { luid=new Luid(); return true; }
static bool AdjustTokenPrivileges(IntPtr token, bool disable, ref TokenPrivileges current, uint size, out TokenPrivileges previous, out uint required) {
previous=current; previous.Attributes=0; required=16;
AdjustCalls++; TestError=AdjustCalls==1 ? EnableError : RestoreError;
return AdjustCalls==1 || RestoreSuccess;
}
'@
$csharp = $csharp.Replace(' IntPtr token;', $native + "`n IntPtr token;")
Assert ($csharp -notmatch '\[DllImport') 'All token API imports are replaced before compilation'
Add-Type -TypeDefinition $csharp -ErrorAction Stop
$type = [WelaPrivilegeFixture.WmiSecurityPrivilege]
$type::Reset()
$instance = [WelaPrivilegeFixture.WmiSecurityPrivilege]::new()
$instance.Dispose(); $instance.Dispose()
Assert ($type::AdjustCalls -eq 2 -and $type::CloseCalls -eq 1) 'Normal restoration executes once and closes the token once'
foreach ($restoreError in @(1300, 5)) {
$type::Reset(); $type::RestoreError = $restoreError
$instance = [WelaPrivilegeFixture.WmiSecurityPrivilege]::new()
$failed = $false
try { $instance.Dispose() } catch { $failed = $_.Exception.InnerException.NativeErrorCode -eq $restoreError }
Assert $failed 'A true AdjustTokenPrivileges return with nonzero last error is a restoration failure'
Assert ($type::CloseCalls -eq 1) 'Failed privilege restoration still closes the token handle'
}
$type::Reset(); $type::RestoreError = 5; $type::RestoreSuccess = $false
$instance = [WelaPrivilegeFixture.WmiSecurityPrivilege]::new()
$failed = $false; try { $instance.Dispose() } catch { $failed = $true }
Assert ($failed -and $type::CloseCalls -eq 1) 'False API restoration result is reported and handle is closed'
$type::Reset(); $type::EnableError = 1300
$failed = $false; try { [WelaPrivilegeFixture.WmiSecurityPrivilege]::new() } catch { $failed = $true }
Assert ($failed -and $type::AdjustCalls -eq 1 -and $type::CloseCalls -eq 1) 'Unavailable SeSecurityPrivilege refuses the operation and closes its handle'
# Exercise the production PowerShell cleanup paths with a throwing connection.
function Initialize-WelaWmiInterop { }
$script:disposed = 0
$script:privilegeFixture = [pscustomobject]@{}
$script:privilegeFixture | Add-Member ScriptMethod Dispose { $script:disposed++ }
function New-Object {
param([string]$TypeName, [object[]]$ArgumentList)
if ($TypeName -eq 'Wela.WmiSecurityPrivilege') { return $script:privilegeFixture }
throw "Unexpected construction in failure fixture: $TypeName"
}
$script:connectionFixture = [pscustomobject]@{}
$script:connectionFixture | Add-Member ScriptMethod Dispose { throw 'fixture COM cleanup failure' }
function New-WelaWmiConnection { param($Namespace) return $script:connectionFixture }
function Get-WelaWmiNativeDescriptor { param($Connection) throw 'fixture descriptor read failure' }
foreach ($operation in @('Get', 'Set')) {
$before = $script:disposed; $failed = $false
try {
if ($operation -eq 'Get') { Get-WelaWmiNamespaceSnapshot 'root\cimv2' }
else { Set-WelaWmiNamespaceDescriptor 'root\cimv2' '{}' @() }
} catch { $failed = $true }
Assert ($failed -and $script:disposed -eq $before + 1) "$operation restores privilege even when connection cleanup throws"
}
Write-Host "PASS: $script:assertions WMI privilege/cleanup assertions with in-memory APIs only."
+137
View File
@@ -0,0 +1,137 @@
# In-memory descriptors only. All native readers/writers are replaced before control execution.
$ErrorActionPreference = 'Stop'
$repo = Split-Path $PSScriptRoot -Parent
$script:ScriptRoot = $repo
. (Join-Path $repo 'scripts/Configuration.ps1')
. (Join-Path $repo 'scripts/WmiNamespaceAuditing.ps1')
$script:assertions = 0
$root = Join-Path ([IO.Path]::GetTempPath()) ('wela-wmi-' + [guid]::NewGuid().ToString('N'))
$null = New-Item -ItemType Directory -Path $root
$fixture = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'fixtures/wmi-namespace-descriptor.json') -Raw
function Assert($Condition, [string]$Message) { if (-not $Condition) { throw "FAIL: $Message" }; $script:assertions++ }
function Throws([scriptblock]$Action, [string]$Message) { $caught = $false; try { & $Action } catch { $caught = $true }; Assert $caught $Message }
function Reset-Mocks {
$script:descriptor = $fixture | ConvertFrom-Json
$script:writes = 0; $script:reads = 0; $script:readFail = $false; $script:writeCode = 0
$script:race = $false; $script:alterOwner = $false; $script:dropUnknown = $false; $script:ineffective = $false
$script:decline = $false; $script:promptCallback = $null
}
function Get-WelaWmiNamespaceSnapshot {
param($Namespace)
$script:reads++
if ($script:readFail) { throw 'Access denied or namespace missing; no complete SACL available.' }
[pscustomobject]@{ Namespace = $Namespace; DescriptorJson = ConvertTo-WelaWmiJson $script:descriptor; DescriptorMof = 'mock full descriptor'; SaclReadPrivilege = 'mock assigned/enabled' }
}
function Set-WelaWmiNamespaceDescriptor {
param($Namespace, $ExpectedJson, $Definitions)
# Model the production immediate re-read and verify the generic runner journal.
$entry = @(Get-Content -LiteralPath (Join-Path $script:context.BackupPath 'before.jsonl') | ConvertFrom-Json)[-1]
Assert ($entry.Before.DescriptorJson -ceq $ExpectedJson -and $entry.Target.Namespace -eq $Namespace) 'Full recovery descriptor persisted before any setter'
Assert ($entry.Before.DescriptorMof -eq 'mock full descriptor') 'Journal includes native descriptor representation'
if ($script:race) { $script:descriptor.Owner.SIDString = 'S-1-5-18' }
if ((ConvertTo-WelaWmiJson $script:descriptor) -cne $ExpectedJson) { throw 'Namespace descriptor changed after its recovery snapshot; no SACL was written.' }
$script:writes++
Assert-WelaWmiReturnCode ([pscustomobject]@{ ReturnValue = $script:writeCode }) 'SetSecurityDescriptor'
if ($script:ineffective) { return }
foreach ($definition in @(Get-WelaWmiMissingAces $script:descriptor $Definitions)) {
$script:descriptor.SACL += [pscustomobject]@{ AccessMask = $definition.AccessMask; AceFlags = $definition.AceFlags; AceType = 2; Trustee = [pscustomobject]@{ SIDString = $definition.Sid } }
}
$script:descriptor.ControlFlags = [uint32]$script:descriptor.ControlFlags -bor 16
if ($script:alterOwner) { $script:descriptor.Owner.SIDString = 'S-1-5-18' }
if ($script:dropUnknown) { $script:descriptor.SACL = @($script:descriptor.SACL | Where-Object AceType -ne 19) }
}
function Read-Host { param($Prompt) if ($script:promptCallback) { & $script:promptCallback }; if ($script:decline) { 'n' } else { 'Y' } }
function New-TestContext([switch]$DryRun, [switch]$Prompt) {
$script:context = New-WelaConfigurationContext -Auto:(-not $Prompt) -DryRun:$DryRun -BackupPath (Join-Path $root ([guid]::NewGuid().ToString('N')))
return $script:context
}
function Run-Controls { param($Context, [string[]]$Namespace = @('root\cimv2'), [switch]$IncludeChildren)
Set-WelaWmiAuditControls -Context $Context -Plan @(Get-WelaWmiAuditPlan -Namespace $Namespace -IncludeChildren:$IncludeChildren)
}
try {
$source = @(Get-WelaWmiAuditDefinitions -IncludeChildren)
Assert ($source.Count -eq 8) 'All eight pinned ASD entries are represented'
Assert (@($source.Namespace | Select-Object -Unique).Count -eq 5) 'Exactly five supported namespaces'
$expected = @('root\cimv2|262146|64|S-1-1-0', 'root\cimv2|1|64|S-1-5-4', 'root\cimv2|1|64|S-1-5-2', 'root\cimv2|1|64|S-1-5-3', 'root\SecurityCenter|262145|66|S-1-1-0', 'root\SecurityCenter2|262145|66|S-1-1-0', 'root\subscription|262174|66|S-1-1-0', 'root\default|262175|66|S-1-1-0')
foreach ($i in 0..7) { Assert (("$($source[$i].Namespace)|$($source[$i].AccessMask)|$($source[$i].AceFlags)|$($source[$i].Sid)") -eq $expected[$i]) 'Masks, principals and inheritance match pinned ASD source' }
Assert (@(Get-WelaWmiAuditDefinitions | Where-Object AceFlags -ne 64).Count -eq 0) 'Default does not extend auditing into unselected descendants'
Assert (@(Get-WelaWmiAuditDefinitions -Namespace @('ROOT\CIMV2','root\cimv2')).Count -eq 4) 'Case-insensitive duplicate selections do not duplicate ACE definitions'
foreach ($invalid in @('root\*','\\server\root\cimv2','root/cimv2','root\cimv2\child','root\default ')) { Throws { Get-WelaWmiAuditDefinitions -Namespace $invalid } 'Wildcards, remote paths and unreviewed namespace targets rejected' }
Throws { Get-WelaWmiAuditPlan } 'Empty selection refuses implicit configuration'
foreach ($value in @(2,8,9,21,4294967295,$null,$false,'unknown')) {
Throws { Assert-WelaWmiReturnCode ([pscustomobject]@{ReturnValue=$value}) 'GetSecurityDescriptor' } 'Every nonzero/missing/invalid return fails'
Throws { Assert-WelaWmiReturnCode ([pscustomobject]@{ReturnValue=$value}) 'SetSecurityDescriptor' } 'Every setter error is checked'
}
Assert-WelaWmiReturnCode ([pscustomobject]@{ReturnValue=[uint32]0}) 'GetSecurityDescriptor'
$getter = [pscustomobject]@{ Code = 2; Descriptor = [pscustomobject]@{ControlFlags=4} }
$getter | Add-Member ScriptMethod InvokeMethod { param($Name,$Parameters,$Options) [pscustomobject]@{ReturnValue=$this.Code;Descriptor=$this.Descriptor} }
Throws { Get-WelaWmiNativeDescriptor $getter } 'Production getter checks provider return code even when descriptor is populated'
$getter.Code=0; $getter.Descriptor=$null
Throws { Get-WelaWmiNativeDescriptor $getter } 'Production getter refuses missing descriptor even with success code'
$getter.Descriptor=[pscustomobject]@{ControlFlags=4;SACL=$null}
Assert ((Get-WelaWmiNativeDescriptor $getter).ControlFlags -eq 4) 'Production getter accepts explicit success and descriptor'
Assert ((Get-WelaWmiSid ([pscustomobject]@{ SID = [byte[]]@(1,1,0,0,0,0,0,1,0,0,0,0) })) -eq 'S-1-1-0') 'Binary SID identity supported without localized names'
Reset-Mocks
$before = $script:descriptor | ConvertTo-Json -Depth 30 | ConvertFrom-Json
$context = New-TestContext -DryRun
Run-Controls $context
Assert ($script:writes -eq 0 -and -not (Test-Path $context.BackupPath) -and $context.Results[0].Status -eq 'Skipped') 'Dry-run has no setter or journal mutation'
$context = New-TestContext -Prompt; $script:decline = $true
Run-Controls $context
Assert ($script:writes -eq 0 -and $context.Results[0].Diagnostic -match 'Declined') 'Operator decline has no setter'
Reset-Mocks
$context = New-TestContext
Run-Controls $context
Assert ($script:writes -eq 1 -and $context.Results[0].Status -eq 'Applied') 'One namespace update appends four missing CIMV2 ACEs'
Assert ((Complete-WelaConfiguration $context -Scope wmi-namespace-sacl-only).ExitCode -eq 0) 'Applied namespace passes final verification'
Assert (Test-WelaWmiDescriptorPreserved $before $script:descriptor) 'Owner/group/DACL/control flags and duplicate unknown ACEs preserved'
Assert ($script:descriptor.ControlFlags -eq (36868 -bor 16)) 'Only SACL_PRESENT is added to descriptor control flags'
Assert ($script:descriptor.SACL.Count -eq 7 -and @($script:descriptor.SACL | Where-Object AceType -eq 19).Count -eq 2) 'Unknown ACE multiplicity preserved'
$context = New-TestContext
Run-Controls $context
Assert ($script:writes -eq 1 -and $context.Results[0].Status -eq 'AlreadyCompliant') 'Second run does not duplicate entries'
$script:descriptor.DACL[0].AccessMask = 1
Assert ((Complete-WelaConfiguration $context).ExitCode -eq 1 -and $context.Results[0].Status -eq 'Overridden') 'Final already-compliant DACL drift is detected'
Reset-Mocks; $context = New-TestContext; $script:race = $true
Run-Controls $context
Assert ($script:writes -eq 0 -and $context.Results[0].Status -eq 'Failed') 'Changed owner between journal and setter refuses update'
Reset-Mocks; $context = New-TestContext -Prompt
$script:promptCallback = { $script:descriptor.SACL[1].OpaqueFutureField = @(99) }
Run-Controls $context
Assert ($script:writes -eq 0 -and $context.Results[0].Status -eq 'Failed') 'Unknown ACE changes during operator prompt are preserved by refusing write'
foreach ($failure in @('alterOwner','dropUnknown','ineffective')) {
Reset-Mocks; Set-Variable -Scope Script -Name $failure -Value $true; $context = New-TestContext
Run-Controls $context
Assert ($context.Results[0].Status -eq 'Failed' -and (Complete-WelaConfiguration $context).ExitCode -eq 1) 'Read-back rejects permission damage, dropped unknown ACE or ineffective update'
}
Reset-Mocks; $script:writeCode = 9; $context = New-TestContext
Run-Controls $context
Assert ($context.Results[0].Status -eq 'Failed' -and $script:descriptor.SACL.Count -eq 3) 'Provider return-code error is a failed control'
Reset-Mocks; $script:readFail = $true; $context = New-TestContext
Run-Controls $context
Assert ($script:writes -eq 0 -and $context.Results[0].Status -eq 'Failed') 'Unreadable/missing selected namespace fails without partial descriptor writes'
Reset-Mocks; $context = New-TestContext
Remove-Item -LiteralPath $context.BackupPath -Recurse
Run-Controls $context
Assert ($script:writes -eq 0 -and $context.Results[0].Status -eq 'Failed') 'Journal failure prevents setter invocation'
Reset-Mocks; $context = New-TestContext
Run-Controls $context
$script:descriptor.SACL += [pscustomobject]@{ AceType=2; AceFlags=128; AccessMask=1; Trustee=[pscustomobject]@{SIDString='S-1-5-18'} }
Assert ((Complete-WelaConfiguration $context).ExitCode -eq 1 -and $context.Results[0].Status -eq 'Overridden') 'Extra SACL drift after successful write is detected at final check'
Reset-Mocks; $context = New-TestContext
Run-Controls $context -Namespace 'root\subscription' -IncludeChildren
Assert ($script:descriptor.SACL[-1].AceFlags -eq 66 -and $script:descriptor.SACL[-1].AccessMask -eq 262174) 'Explicit child option enables exactly ASD inheritance for subscription'
$definition = @(Get-WelaWmiAuditDefinitions -Namespace 'root\cimv2')[0]
$ace = [pscustomobject]@{AceType=2;AceFlags=64;AccessMask=262146;Trustee=[pscustomobject]@{SIDString='S-1-1-0'}}
Assert (Test-WelaWmiAceMatch $ace $definition) 'Exact ordinary success ACE satisfies requirement'
foreach ($flags in @(80,192,66,72)) { $ace.AceFlags=$flags; Assert (-not (Test-WelaWmiAceMatch $ace $definition)) 'Inherited/broader/different-scope ACE does not hide a missing exact request' }
$tokens=$null;$parseErrors=$null
$ast=[System.Management.Automation.Language.Parser]::ParseFile((Join-Path $repo 'WELA.ps1'),[ref]$tokens,[ref]$parseErrors)
Assert ($parseErrors.Count -eq 0) 'Combined CLI parses'
# Execute only the actual top-level DryRun guard, with no command dispatch.
$guard=$ast.EndBlock.Statements | Where-Object { $_ -is [System.Management.Automation.Language.IfStatementAst] -and $_.Extent.Text.StartsWith('if ($DryRun') } | Select-Object -First 1
$Cmd='wmi-auditing';$DryRun=$true;$WmiAction='Configure';$FirewallAction='Audit';$SmbAction='Audit'
& ([scriptblock]::Create($guard.Extent.Text));$WmiAction='Audit'
Throws { & ([scriptblock]::Create($guard.Extent.Text)) } 'WMI Audit rejects DryRun before dispatch'
Write-Host "PASS: $script:assertions WMI namespace assertions (mocked, no live namespace changes)."
} finally { Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue }
@@ -0,0 +1,68 @@
# Actual reads and in-memory typed provider responses only. Never sends a native SetSecurityDescriptor.
$ErrorActionPreference = 'Stop'
if ($env:OS -ne 'Windows_NT') { Write-Host 'SKIP: Windows only'; return }
$repo = Split-Path $PSScriptRoot -Parent
. (Join-Path $repo 'scripts/Configuration.ps1')
. (Join-Path $repo 'scripts/WmiNamespaceAuditing.ps1')
$script:assertions = 0
function Assert($Condition, [string]$Message) { if (-not $Condition) { throw "FAIL: $Message" }; $script:assertions++ }
$before = Get-WelaWmiNamespaceSnapshot 'root\cimv2'
Assert ($before.DescriptorJson -and $before.DescriptorMof -and $before.SaclReadPrivilege -eq 'SeSecurityPrivilege enabled') 'Actual privileged native descriptor read and full export'
$inventory = @(Get-WelaWmiNamespaceInventory)
Assert ($inventory.Count -eq 5 -and @($inventory | Where-Object { $_.Namespace -eq 'root\cimv2' -and $_.State -eq 'Present' }).Count -eq 1) 'Supported namespace inventory reads actual local namespaces'
$plan = @(Get-WelaWmiAuditPlan -Namespace 'root\cimv2')
Assert ($plan[0].Status -in @('AlreadyCompliant','ChangeRequired')) 'Actual CIMV2 plan reads successfully'
$path = Join-Path ([IO.Path]::GetTempPath()) ('wela-wmi-readonly-' + [guid]::NewGuid().ToString('N'))
$context = New-WelaConfigurationContext -Auto -DryRun -BackupPath $path
Set-WelaWmiAuditControls -Context $context -Plan $plan
Assert (-not (Test-Path $path) -and $context.Results[0].Status -in @('AlreadyCompliant','Skipped')) 'Real dry-run neither writes SACL nor creates journal'
$after = Get-WelaWmiNamespaceSnapshot 'root\cimv2'
Assert ($before.DescriptorJson -ceq $after.DescriptorJson) 'Full descriptor unchanged by read-only planning/dry-run'
# Read actual native objects once; from here the native connection factory is
# replaced in the same script scope before invoking ANY setter code.
Initialize-WelaWmiInterop
$privilege = New-Object Wela.WmiSecurityPrivilege
$connection = $null
try {
$connection = New-WelaWmiConnection 'root\cimv2'
$script:fixtureDescriptor = (Get-WelaWmiNativeDescriptor $connection).Clone()
$script:fixtureParameters = $connection.GetMethodParameters('SetSecurityDescriptor')
} finally { if ($connection) { $connection.Dispose() }; $privilege.Dispose() }
# An empty in-memory SACL forces all requested additions without changing Windows.
$script:fixtureDescriptor.SACL = $null
$expected = ConvertTo-WelaWmiJson (ConvertTo-WelaWmiData $script:fixtureDescriptor)
$script:setCalls = 0; $script:captured = $null; $script:returnCode = [uint32]0
$script:fake = [pscustomobject]@{}
$script:fake | Add-Member ScriptMethod InvokeMethod {
param($Name, $Parameters, $Options)
if ($Name -eq 'GetSecurityDescriptor') { return [pscustomobject]@{ ReturnValue = [uint32]0; Descriptor = $script:fixtureDescriptor } }
if ($Name -ne 'SetSecurityDescriptor') { throw "Unexpected method: $Name" }
$script:setCalls++; $script:captured = $Parameters.Descriptor.Clone()
return [pscustomobject]@{ ReturnValue = $script:returnCode }
}
$script:fake | Add-Member ScriptMethod GetMethodParameters { param($Name) if ($Name -ne 'SetSecurityDescriptor') { throw 'Unexpected method parameters' }; return $script:fixtureParameters.Clone() }
$script:fake | Add-Member ScriptMethod Dispose { }
function New-WelaWmiConnection { param($Namespace) if ($Namespace -ne 'root\cimv2') { throw 'Unexpected fake target' }; return $script:fake }
$definitions = @(Get-WelaWmiAuditDefinitions -Namespace 'root\cimv2')
Set-WelaWmiNamespaceDescriptor -Namespace 'root\cimv2' -ExpectedJson $expected -Definitions $definitions
Assert ($script:setCalls -eq 1 -and $script:captured -is [System.Management.ManagementBaseObject]) 'Production writer builds typed descriptor against fake provider only'
$original = $expected | ConvertFrom-Json
$captured = ConvertTo-WelaWmiData $script:captured
Assert ($null -eq $captured.DACL -and $null -eq $captured.Owner -and $null -eq $captured.Group) 'Native request omits access-permission fields instead of requesting that they be rewritten'
Assert (([uint32]$captured.ControlFlags -band 4) -eq 0 -and ([uint32]$captured.ControlFlags -band 16) -eq 16) 'Native request uses only SACL-present mutation semantics, with DACL-present cleared'
Assert ((ConvertTo-WelaWmiJson (ConvertTo-WelaWmiData $script:fixtureDescriptor)) -ceq $expected) 'Building the SACL-only request leaves the complete original descriptor unchanged'
# Simulate the documented provider contract in memory: absent access fields and
# SE_DACL_PRESENT preserve the current access permissions.
$effective = $expected | ConvertFrom-Json
$effective.SACL = $captured.SACL
$effective.ControlFlags = [uint32]$effective.ControlFlags -bor 16
Assert (Test-WelaWmiDescriptorPreserved $original $effective) 'SACL-only provider semantics retain every original non-SACL field'
Assert (@(Get-WelaWmiMissingAces $captured $definitions).Count -eq 0 -and @($captured.SACL).Count -eq 4) 'Actual Win32_ACE/Trustee objects carry all four exact masks and binary SIDs'
$script:returnCode = [uint32]9
$failed = $false
try { Set-WelaWmiNamespaceDescriptor -Namespace 'root\cimv2' -ExpectedJson $expected -Definitions $definitions } catch { $failed = $_.Exception.Message -match 'ReturnValue=9' }
Assert $failed 'Production SetSecurityDescriptor wrapper rejects native nonzero return code'
$prior = $script:setCalls; $failed = $false
try { Set-WelaWmiNamespaceDescriptor -Namespace 'root\cimv2' -ExpectedJson '{}' -Definitions $definitions } catch { $failed = $_.Exception.Message -match 'changed after' }
Assert ($failed -and $script:setCalls -eq $prior) 'Production writer detects changed snapshot before fake setter'
Write-Host "PASS: $script:assertions Windows namespace read-only / in-memory native adapter assertions. No live SACL changes or event-generation claims."
+15
View File
@@ -0,0 +1,15 @@
{
"ControlFlags": 36868,
"DACL": [
{"AccessMask": 393279, "AceFlags": 2, "AceType": 0, "GuidObjectType": null, "GuidInheritedObjectType": null, "Trustee": {"SIDString": "S-1-5-32-544", "Name": "Administrators", "Domain": "BUILTIN"}},
{"AccessMask": 32, "AceFlags": 0, "AceType": 1, "Trustee": {"SIDString": "S-1-5-21-1-2-3-1001"}}
],
"Group": {"SIDString": "S-1-5-18"},
"Owner": {"SIDString": "S-1-5-32-544"},
"SACL": [
{"AccessMask": 2, "AceFlags": 128, "AceType": 2, "Trustee": {"SIDString": "S-1-1-0"}},
{"AccessMask": 8, "AceFlags": 16, "AceType": 19, "GuidObjectType": "unfamiliar-object", "OpaqueFutureField": [1, 7, 255], "Trustee": {"SIDString": "S-1-5-18"}},
{"AccessMask": 8, "AceFlags": 16, "AceType": 19, "GuidObjectType": "unfamiliar-object", "OpaqueFutureField": [1, 7, 255], "Trustee": {"SIDString": "S-1-5-18"}}
],
"ProviderExtension": {"Keep": "unchanged"}
}