mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-01 20:14:43 +02:00
124 lines
9.5 KiB
PowerShell
124 lines
9.5 KiB
PowerShell
$ErrorActionPreference='Stop'
|
|
$repo=Split-Path $PSScriptRoot -Parent
|
|
. (Join-Path $repo 'scripts/Configuration.ps1')
|
|
. (Join-Path $repo 'scripts/WmiNamespaceAuditing.ps1')
|
|
$script:assertions=0
|
|
function Assert($v,[string]$m){if(-not $v){throw $m};$script:assertions++}
|
|
function Throws([scriptblock]$f,[string]$m){$yes=$false;try{& $f|Out-Null}catch{$yes=$true};Assert $yes $m}
|
|
function Descriptor([uint32]$flags=32772){[pscustomobject]@{ControlFlags=$flags;Owner='owner';Group='group';DACL=@('a','b');SACL=@();Opaque='preserve'}}
|
|
function Snapshot([string]$ns,$d){[pscustomobject]@{Namespace=$ns;DescriptorJson=(ConvertTo-WelaWmiJson $d);DescriptorMof='native full descriptor';SaclReadPrivilege='test'}}
|
|
function Ace([uint32]$flags=82){[pscustomobject]@{AceType=2;AceFlags=$flags;AccessMask=262175;Trustee=[pscustomobject]@{SIDString='S-1-1-0'}}}
|
|
$script:tree=@{};$script:reads=0;$script:changeAt=0;$script:context='caller/host/source';$script:writes=0
|
|
function Reset {
|
|
$script:tree=@{'root\default'=(Descriptor);'root\default\A'=(Descriptor);'root\default\A\B'=(Descriptor);'root\default\Protected'=(Descriptor 40964);'root\default\Protected\B'=(Descriptor)}
|
|
$script:reads=0;$script:changeAt=0;$script:context='caller/host/source';$script:writes=0;$script:prompt=$null;$script:failChildrenAfterWrite=$false
|
|
}
|
|
function Get-WelaWmiChildNames {
|
|
param($Namespace,$Maximum)
|
|
if($script:failChildrenAfterWrite -and $script:writes -gt 0){throw "Injected post-write child-read refusal."}
|
|
@($script:tree.Keys|Where-Object {$_ -clike ($Namespace+'\*') -and $_.Substring($Namespace.Length+1) -notmatch '\\'}|ForEach-Object {$_.Substring($Namespace.Length+1)}|Sort-Object)
|
|
}
|
|
function Get-WelaWmiDescendantContext {$script:context}
|
|
function Get-WelaWmiNamespaceSnapshot {
|
|
param($Namespace)
|
|
$script:reads++
|
|
if($script:changeAt -and $script:reads -eq $script:changeAt){$script:tree['root\default\A'].Owner='racing owner'}
|
|
if(-not $script:tree.ContainsKey($Namespace)){throw 'Unknown namespace.'}
|
|
Snapshot $Namespace $script:tree[$Namespace]
|
|
}
|
|
function Set-WelaWmiNamespaceDescriptor {
|
|
param($Namespace,$ExpectedJson,$Definitions)
|
|
if((ConvertTo-WelaWmiJson $script:tree[$Namespace]) -cne $ExpectedJson){throw 'Immediate parent drift'}
|
|
$script:writes++
|
|
foreach($d in $Definitions){$script:tree[$Namespace].SACL+=Ace $d.AceFlags}
|
|
$script:tree[$Namespace].ControlFlags=$script:tree[$Namespace].ControlFlags -bor 16
|
|
# Model the provider's potential inherited-only propagation exactly, leaving protected tree unchanged.
|
|
foreach($ns in @('root\default\A','root\default\A\B')){$script:tree[$ns].SACL+=Ace;$script:tree[$ns].ControlFlags=$script:tree[$ns].ControlFlags -bor 16}
|
|
}
|
|
function Read-Host {param($Prompt)if($script:prompt){& $script:prompt};'Y'}
|
|
$defs=@(Get-WelaWmiAuditDefinitions -Namespace 'root\default' -IncludeChildren)
|
|
$temp=Join-Path ([IO.Path]::GetTempPath()) ('wela-wmi-desc-test-'+[guid]::NewGuid().ToString('N'))
|
|
try{
|
|
Reset
|
|
$before=Get-WelaWmiStableDescendants 'root\default'
|
|
Assert ($before.Entries.Count -eq 4) 'Complete multilevel inventory.'
|
|
Assert (@($before.Entries|Where-Object ProtectedBarrier).Count -eq 2) 'Protected ancestor marks whole subtree.'
|
|
Assert ((Get-WelaWmiDescendantKey $before) -ceq (Get-WelaWmiDescendantKey (Get-WelaWmiStableDescendants 'root\default'))) 'Stable tree key omits observation clock.'
|
|
$script:changeAt=$script:reads+7
|
|
Throws {Get-WelaWmiStableDescendants 'root\default'} 'Second-pass descriptor drift must fail.'
|
|
Reset;$script:tree['root\default\A\bad-child']=Descriptor
|
|
Assert ((Get-WelaWmiDescendants 'root\default').Status -eq 'Incomplete') 'Ambiguous child name rejected.'
|
|
Reset;foreach($i in 1..65){$script:tree['root\default\N'+$i]=Descriptor}
|
|
Assert ((Get-WelaWmiDescendants 'root\default').Status -eq 'Incomplete') 'Count overflow fails rather than truncates.'
|
|
Reset;$n='root\default';foreach($i in 1..9){$n+='\Deep';$script:tree[$n]=Descriptor}
|
|
Assert ((Get-WelaWmiDescendants 'root\default').Status -eq 'Incomplete') 'Depth overflow fails rather than truncates.'
|
|
Reset;$script:tree['root\default\A'].Opaque='x'*2097153
|
|
Assert ((Get-WelaWmiDescendants 'root\default').Status -eq 'Incomplete') 'Serialized descriptor budget enforced.'
|
|
Reset;$script:tree['root\default'].Opaque='x'*2097153
|
|
Assert ((Get-WelaWmiDescendants 'root\default').Status -eq 'Incomplete') 'The selected root also counts toward the descriptor budget.'
|
|
Reset
|
|
$p=@(Get-WelaWmiAuditPlan -Namespace 'root\default' -IncludeChildren)
|
|
$c=New-WelaConfigurationContext -Auto -DryRun -BackupPath $temp
|
|
Set-WelaWmiAuditControls $c $p
|
|
Assert ($script:writes -eq 0 -and $c.Results[0].Status -eq 'Skipped' -and -not (Test-Path $temp)) 'DryRun no state or journal mutation.'
|
|
$script:context='changed token';$c=New-WelaConfigurationContext -Auto -BackupPath (Join-Path $temp token)
|
|
Set-WelaWmiAuditControls $c $p
|
|
Assert ($script:writes -eq 0 -and $c.Results[0].Status -eq 'Failed') 'Full context drift invalidates planned subtree.'
|
|
$script:context='caller/host/source'
|
|
$script:tree['root\default\New']=Descriptor
|
|
$c=New-WelaConfigurationContext -Auto -BackupPath (Join-Path $temp stale)
|
|
Set-WelaWmiAuditControls $c $p
|
|
Assert ($script:writes -eq 0 -and $c.Results[0].Status -eq 'Failed') 'Stale membership blocks before journal or setter.'
|
|
Reset;$p=@(Get-WelaWmiAuditPlan -Namespace 'root\default' -IncludeChildren)
|
|
$script:prompt={$script:tree['root\default\A'].DACL=@('changed')}
|
|
$c=New-WelaConfigurationContext -BackupPath (Join-Path $temp prompt)
|
|
Set-WelaWmiAuditControls $c $p
|
|
Assert ($script:writes -eq 0 -and $c.Results[0].Status -eq 'Failed') 'Descendant drift during confirmation blocks parent setter.'
|
|
Reset;$p=@(Get-WelaWmiAuditPlan -Namespace 'root\default' -IncludeChildren)
|
|
$c=New-WelaConfigurationContext -Auto -BackupPath (Join-Path $temp successful)
|
|
Set-WelaWmiAuditControls $c $p
|
|
$result=Complete-WelaConfiguration $c
|
|
Assert ($script:writes -eq 1 -and $result.ExitCode -eq 0 -and $result.Results[0].Status -eq 'Applied') 'Exact inherited propagation and protected preservation pass.'
|
|
$ob=$result.Results[0].DescendantVerification.Observation
|
|
Assert (@($ob.Outcomes|Where-Object Status -eq InheritedAceObserved).Count -eq 2) 'Two inherited readbacks represented.'
|
|
Assert (@($ob.Outcomes|Where-Object Status -eq ProtectedUnchanged).Count -eq 2) 'Two protected readbacks represented.'
|
|
$journal=Get-Content (Join-Path $temp successful/before.jsonl)|ConvertFrom-Json
|
|
Assert ($journal.Before.Descendants.Entries.Count -eq 4 -and $journal.Before.DescriptorMof -eq 'native full descriptor') 'Original journal fields and full subtree retained.'
|
|
Assert ((Get-WelaWmiDescendantKey $journal.Before.Descendants) -ceq (Get-WelaWmiDescendantKey $p[0].Descendants)) 'Journal serialization does not truncate original child snapshots.'
|
|
$script:tree['root\default\A'].Opaque='drift'
|
|
Assert ((Complete-WelaConfiguration $c).ExitCode -eq 1) 'Final child drift propagates failure.'
|
|
Reset;$script:tree['root\default'].SACL+=Ace 66;$script:tree['root\default'].ControlFlags=32788
|
|
$unverified=@(Get-WelaWmiAuditPlan -Namespace 'root\default' -IncludeChildren)
|
|
Assert ($unverified[0].Status -eq 'Unknown' -and $unverified[0].Diagnostic -match 'descendants are unverified') 'Already-compliant parent cannot imply descendant compliance.'
|
|
$c=New-WelaConfigurationContext -Auto -BackupPath (Join-Path $temp unverified)
|
|
Set-WelaWmiAuditControls $c $unverified
|
|
Assert ($script:writes -eq 0 -and (Complete-WelaConfiguration $c).ExitCode -eq 1) 'Missing existing-child inheritance fails without an unnecessary parent rewrite.'
|
|
Reset;$p=@(Get-WelaWmiAuditPlan -Namespace 'root\default' -IncludeChildren);$script:failChildrenAfterWrite=$true
|
|
$c=New-WelaConfigurationContext -Auto -BackupPath (Join-Path $temp partial)
|
|
Set-WelaWmiAuditControls $c $p
|
|
$r=Complete-WelaConfiguration $c
|
|
Assert ($r.ExitCode -eq 1 -and $script:writes -eq 1 -and $r.Results[0].Status -eq 'Failed') 'Post-write enumeration failure propagates a nonzero result.'
|
|
$v=$r.Results[0].DescendantVerification
|
|
Assert ($v.ParentSetterAttempted -and $v.ParentSetterAccepted -and $null -eq $v.Observation -and $v.LastTree.Status -eq 'Incomplete') 'Partial read failure retains parent-write flags and incomplete native observations without claiming verified outcomes.'
|
|
# Each unrelated mutation invalidates observed propagation, even when required ACE still exists.
|
|
foreach($kind in @('Owner','Dacl','Control','Unknown','NewProperty','Protected','Removed','Extra','Duplicate','Missing','New')){
|
|
Reset;$a=Get-WelaWmiStableDescendants 'root\default';$null=Set-WelaWmiNamespaceDescriptor 'root\default' $a.Root.DescriptorJson $defs
|
|
switch($kind){
|
|
Owner {$script:tree['root\default\A'].Owner='other'}
|
|
Dacl {$script:tree['root\default\A'].DACL=@('other')}
|
|
Control {$script:tree['root\default\A'].ControlFlags=$script:tree['root\default\A'].ControlFlags -bor 256}
|
|
Unknown {$script:tree['root\default\A'].Opaque='other'}
|
|
NewProperty {$script:tree['root\default\A']|Add-Member NoteProperty NewOpaque 1}
|
|
Protected {$script:tree['root\default\Protected\B'].SACL+=Ace}
|
|
Removed {$script:tree.Remove('root\default\A\B')}
|
|
Extra {$script:tree['root\default\A'].SACL+=Ace 64}
|
|
Duplicate {$script:tree['root\default\A'].SACL+=Ace}
|
|
Missing {$script:tree['root\default\A'].SACL=@()}
|
|
New {$script:tree['root\default\Unreviewed']=Descriptor}
|
|
}
|
|
$b=Get-WelaWmiStableDescendants 'root\default'
|
|
Assert ((Test-WelaWmiDescendantOutcomes $a $b $defs).Status -eq 'Unverified') "$kind child change must fail verification."
|
|
}
|
|
Write-Host "PASS: $script:assertions bounded WMI descendant assertions."
|
|
}finally{if(Test-Path $temp){Remove-Item $temp -Recurse -Force}}
|