From b61a3c6bcaf4811169eced0c78a21dd5c71f8dd4 Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:10:01 +0900 Subject: [PATCH] Handle native WinRM manifest IDs without Int32 overflow --- CHANGELOG-Japanese.md | 2 +- CHANGELOG.md | 2 +- docs/native-provider-acceptance.md | 4 +++- scripts/NativeProviderPacks.ps1 | 7 +++++-- tests/NativeProviderConfigure.Windows.Tests.ps1 | 9 ++++++++- tests/NativeProviderPacks.Tests.ps1 | 9 +++++++++ website/docs/resources/changelog.ja.md | 2 +- website/docs/resources/changelog.md | 2 +- 8 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 9b931f9a..f7ce3054 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -4,7 +4,7 @@ **改善:** -- Server 2022/2025 の Windows PowerShell 5.1/PowerShell 7 で、公開 provider-packs コマンドの Plan、DryRun、Configure、冪等性、前提不足・手動対象の拒否、部分適用を実機検証する使い捨て CI を追加。DNS Client、CAPI2、WinRM、RDP Client の設定と復元記録・ハッシュを確認し、完全な ACL、保持モード、大きい既存バッファ、他チャネル、サービス、全監査マスクの保持とテスト後の正確な復元を検証。イベント生成や Sigma 対応の証明は含みません。(@Shirofune-Security) +- Server 2022/2025 の Windows PowerShell 5.1/PowerShell 7 で、公開 provider-packs コマンドの Plan、DryRun、Configure、冪等性、前提不足・手動対象の拒否、部分適用を実機検証する使い捨て CI を追加。DNS Client、CAPI2、WinRM、RDP Client の設定と復元記録・ハッシュを確認し、完全な ACL、保持モード、大きい既存バッファ、他チャネル、サービス、全監査マスクの保持とテスト後の正確な復元を検証。イベント生成や Sigma 対応の証明は含みません。 WinRM のマニフェスト ID をネイティブの Int64 として比較し、対象外の大きい ID により必要なイベントの確認が失敗する不具合も修正。 (@Shirofune-Security) - Server 2022/2025 と Windows PowerShell 5.1/PowerShell 7 の破棄可能な環境で、Securityログ警告設定の公開CLIを検証します。未設定・0・高いしきい値、早い警告値の維持、DryRun、再実行、不正型の拒否と完全な復元を確認し、無関係な設定は保持します。ログ枯渇や警告イベント生成は検証範囲外です。 (@Shirofune-Security) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd86000a..754e712d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ **Improvements:** -- Added disposable native acceptance for public provider-pack Plan, DryRun, Configure, idempotence, missing/manual refusal and partial outcomes on Server 2022/2025 under Windows PowerShell 5.1/PowerShell 7. Actual DNS Client, CAPI2, WinRM and RDP Client configuration preserves descriptors, retention, larger buffers, unrelated channels, services and all audit masks, with journal/hash evidence and exact fixture cleanup; no event or Sigma credit. (@Shirofune-Security) +- Added disposable native acceptance for public provider-pack Plan, DryRun, Configure, idempotence, missing/manual refusal and partial outcomes on Server 2022/2025 under Windows PowerShell 5.1/PowerShell 7. Actual DNS Client, CAPI2, WinRM and RDP Client configuration preserves descriptors, retention, larger buffers, unrelated channels, services and all audit masks, with journal/hash evidence and exact fixture cleanup; no event or Sigma credit. Fixed WinRM manifest inspection to preserve native Int64 event IDs, so unrelated large IDs no longer block the selected event schema. (@Shirofune-Security) - Verify public Security-log warning configuration on disposable Server 2022/2025 hosts under Windows PowerShell 5.1/PowerShell 7: absent/zero/higher thresholds, earlier-threshold preservation, dry run, idempotence, wrong-type refusal and exact cleanup. Preserve unrelated registry values, channel configuration, audit masks and CrashOnAuditFail; no log-exhaustion or warning-event claim. (@Shirofune-Security) diff --git a/docs/native-provider-acceptance.md b/docs/native-provider-acceptance.md index 2df6c226..53441d85 100644 --- a/docs/native-provider-acceptance.md +++ b/docs/native-provider-acceptance.md @@ -2,7 +2,7 @@ The dedicated `Native provider configuration acceptance` workflow tests the public `provider-packs` command on disposable GitHub-hosted Windows Server 2022 and 2025, separately under Windows PowerShell 5.1 and PowerShell 7. It complements the read-only manifest inventory and mocked failure tests described in [the provider-pack guide](native-provider-packs.md). -This fixture is destructive to the selected channels' temporary configuration and can discard records when restoring smaller buffers. It requires `-AllowDisposableProviderWrite`, `GITHUB_ACTIONS=true` and `RUNNER_ENVIRONMENT=github-hosted`; do not run it on ordinary machines. It makes no production configuration changes outside the existing public command's declared scope. +This fixture is destructive to the selected channels' temporary configuration and can discard records when restoring smaller buffers. It requires `-AllowDisposableProviderWrite`, `GITHUB_ACTIONS=true` and `RUNNER_ENVIRONMENT=github-hosted`; do not run it on ordinary machines. Production behavior is unchanged; only this opted-in disposable fixture prepares and restores the temporary test settings. ## Actual public behavior checked @@ -24,3 +24,5 @@ Each selected channel has independent cleanup that restores original enablement, `original.json`, public JSON reports, command output, actual journals, `completed.json`, `cleanup.json` and a SHA256 manifest are retained for seven days by the workflow. The manifest binds the fixture, product helpers, catalog, corpus and full reviewed rule-source bytes. Event records are not restored, and no retention-duration, Windows 11, domain/DC/ADCS, positive installed-DNS, forwarding or Sigma acceptance is implied. This advances issues #386 and #366 without closing their broader acceptance work. The underlying enablement, size, retention and backup options follow Microsoft's [wevtutil command reference](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/wevtutil). The product's existing channel floors and schema gates remain unchanged. + +The first native run exposed a WinRM manifest bug: an unrelated event ID `3221734403` overflowed the reader's signed 32-bit cast and made the whole provider schema unknown. The reader now compares [EventMetadata.Id in its native Int64 domain](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.eventing.reader.eventmetadata.id), then parses only the exact reviewed event/channel templates. Focused tests also require refusal when only unrelated large IDs exist; schema gates are unchanged. diff --git a/scripts/NativeProviderPacks.ps1 b/scripts/NativeProviderPacks.ps1 index 1ba0a64d..744d7d26 100644 --- a/scripts/NativeProviderPacks.ps1 +++ b/scripts/NativeProviderPacks.ps1 @@ -53,12 +53,15 @@ function Get-WelaProviderPackSchema { if ([guid]$provider.Id -eq [guid]::Empty) { throw 'Provider GUID is unknown.' } if (@($logs[0].ProviderNames) -notcontains $Pack.provider -or @($provider.LogLinks.LogName) -notcontains $Pack.channel) { throw 'Provider/channel links disagree.' } $events = @() + # EventMetadata.Id is Int64; WinRM includes unrelated IDs above Int32.MaxValue. + # Compare before parsing selected templates, without narrowing the native ID. + $expectedIds = @($Pack.events | ForEach-Object { [long]$_.id }) foreach ($event in $provider.Events) { - if (@($Pack.events.id) -contains [int]$event.Id -and $event.LogLink.LogName -eq $Pack.channel) { + if ($expectedIds -contains [long]$event.Id -and $event.LogLink.LogName -eq $Pack.channel) { $fields = @(Get-WelaProviderTemplateFields -Template $event.Template) $sha = [Security.Cryptography.SHA256]::Create() try { $templateHash = ([BitConverter]::ToString($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes([string]$event.Template)))).Replace('-','').ToLowerInvariant() } finally { $sha.Dispose() } - $events += [pscustomobject]@{ Id=[int]$event.Id; Version=[int]$event.Version; Channel=[string]$event.LogLink.LogName; Fields=$fields; TemplateSha256=$templateHash } + $events += [pscustomobject]@{ Id=[long]$event.Id; Version=[int]$event.Version; Channel=[string]$event.LogLink.LogName; Fields=$fields; TemplateSha256=$templateHash } } } [pscustomobject]@{ State='Observed'; Provider=[string]$provider.Name; ProviderGuid=[string]$provider.Id; ChannelType=[string]$logs[0].LogType; Events=$events; Diagnostic=$null } diff --git a/tests/NativeProviderConfigure.Windows.Tests.ps1 b/tests/NativeProviderConfigure.Windows.Tests.ps1 index aa66f979..be7ba299 100644 --- a/tests/NativeProviderConfigure.Windows.Tests.ps1 +++ b/tests/NativeProviderConfigure.Windows.Tests.ps1 @@ -74,6 +74,7 @@ $before=@{};$raw=@{};$prepared=@{};$preparedRaw=@{};$services=@(Services);$polic foreach($channel in $channels){$before[$channel]=Get-WelaNativeChannel $channel;if(Test-WelaNativeChannelSnapshot $before[$channel]){$raw[$channel]=Read-Raw $channel}} $rawText=@{};foreach($channel in $raw.Keys){$rawText[$channel]=$raw[$channel].OuterXml} Save 'original.json' @{Channels=$before;RawXml=$rawText;Services=$services;AuditMasks=$policies;Engine=$PSVersionTable.PSVersion.ToString()} +function Stable-Selected {foreach($channel in $selected.channel){Assert (Test-WelaNativeChannelSnapshotEqual $configured[$channel] (Get-WelaNativeChannel $channel)) 'Idempotent/refused/partial invocation preserves the expected complete selected-channel tuple.'}} function Preserved { foreach($channel in $channels){ $now=Get-WelaNativeChannel $channel @@ -95,6 +96,7 @@ try { $initial=Public 'initial' 'Plan' $names foreach($entry in $initial.ControlsPlan){Assert ($entry.ProviderEvidence.CanConfigure -and $entry.ProviderEvidence.Schema.State -ceq 'Observed' -and $entry.ProviderEvidence.Schema.Provider -ceq $entry.Pack.provider) 'Exact actual provider/schema permits the selected pack.'} Assert ($initial.ControlsPlan.Count -eq 4) 'Exactly four explicit packs are observed.' + foreach($channel in $raw.Keys){Assert ((Read-Raw $channel).OuterXml -ceq $raw[$channel].OuterXml) 'Initial public Plan preserves every registered channel configuration.'} foreach($pack in $selected){ $channel=$pack.channel;$mutated+=,$channel $size=if($pack.id -ceq 'winrm'){2147483648L}else{1048576L} @@ -111,12 +113,13 @@ try { $null=Public 'grant-option' 'Configure' $names -Expected 1 -Extra @('-GrantEventLogReaders') foreach($channel in $selected.channel){Assert (Test-WelaNativeChannelSnapshotEqual $prepared[$channel] (Get-WelaNativeChannel $channel)) 'Plan, DryRun and invalid options preserve prepared actual state.'} Preserved + $configured=@{} $applied=Public 'configure' 'Configure' $names Assert ($applied.Action -ceq 'Configure' -and $applied.Scope -ceq 'native-channel-settings-only' -and $applied.Results.Count -eq 4 -and @($applied.Results|Where-Object Status -cne 'Applied').Count -eq 0) 'All four explicit configurations are actually Applied.' $journal=@(Get-Content "$root/configure-journal/before.jsonl"|ForEach-Object {$_|ConvertFrom-Json}) Assert ($journal.Count -eq 4 -and @($journal|Where-Object {$selected.channel -notcontains $_.Target.Channel}).Count -eq 0) 'Exactly four selected changes have durable original journals.' foreach($pack in $selected){ - $channel=$pack.channel;$entry=@($applied.Results|Where-Object {$_.Target.Channel -ceq $channel});$j=@($journal|Where-Object {$_.Target.Channel -ceq $channel});$now=Get-WelaNativeChannel $channel + $channel=$pack.channel;$entry=@($applied.Results|Where-Object {$_.Target.Channel -ceq $channel});$j=@($journal|Where-Object {$_.Target.Channel -ceq $channel});$now=Get-WelaNativeChannel $channel;$configured[$channel]=$now $minimum=if($pack.id -ceq 'capi2'){102432768L}elseif($pack.id -ceq 'winrm'){2147483648L}else{33554432L} Assert ($entry.Count -eq 1 -and $j.Count -eq 1 -and (Test-WelaNativeChannelSnapshotEqual $j[0].Before $prepared[$channel]) -and (Test-WelaNativeChannelSnapshotEqual $entry[0].Before $prepared[$channel])) 'Native journal and result retain exact prepared before-state.' Assert ($now.IsEnabled -and $now.MaximumSizeInBytes -eq $minimum -and (Test-WelaNativeChannelSnapshotEqual $entry[0].After $now)) 'Exact native enable/floor/larger-buffer readback matches Applied after-state.' @@ -126,16 +129,20 @@ try { Preserved $repeat=Public 'repeat' 'Configure' $names Assert (@($repeat.Results|Where-Object Status -cne 'AlreadyCompliant').Count -eq 0 -and -not(Test-Path "$root/repeat-journal/before.jsonl")) 'Native repeat is idempotent and journals no write.' + Stable-Selected $manual=Public 'manual' 'Configure' @('dns-server-analytical','dns-server-classic') -Expected 1 Assert ($manual.Results.Count -eq 2 -and @($manual.Results|Where-Object Status -cne 'Failed').Count -eq 0 -and -not(Test-Path "$root/manual-journal/before.jsonl")) 'Both actual manual-only selections fail without channel mutation or journal.' + Stable-Selected $missing=Public 'missing-dns' 'Configure' @('dns-server-audit') -Expected 1 Assert ($missing.Results[0].Status -ceq 'Failed' -and $missing.ControlsPlan[0].ProviderEvidence.Service.State -ceq 'Not installed' -and -not(Test-Path "$root/missing-dns-journal/before.jsonl")) 'Missing actual DNS service cannot be replaced by an assumed server role.' + Stable-Selected # A genuine partial public run must retain one success and one manual refusal. $capi='Microsoft-Windows-CAPI2/Operational';$null=Invoke-WelaNative wevtutil.exe @('sl',$capi,'/e:false');$partialBefore=Get-WelaNativeChannel $capi $partial=Public 'partial' 'Configure' @('capi2','dns-server-analytical') -Expected 1 Assert (@($partial.Results|Where-Object Status -ceq 'Applied').Count -eq 1 -and @($partial.Results|Where-Object Status -ceq 'Failed').Count -eq 1 -and (Get-WelaNativeChannel $capi).IsEnabled) 'Actual partial configuration retains one verified change and explicit nonzero failure.' $partialJournal=@(Get-Content "$root/partial-journal/before.jsonl"|ForEach-Object {$_|ConvertFrom-Json}) Assert ($partialJournal.Count -eq 1 -and $partialJournal[0].Target.Channel -ceq $capi -and (Test-WelaNativeChannelSnapshotEqual $partialJournal[0].Before $partialBefore)) 'Partial run journals only its actual selected write.' + Stable-Selected Preserved Save 'completed.json' @{Status='Passed';Assertions=$count;ActualAppliedControls=5;IdempotentControls=4;ManualRefusals=3;MissingServiceRefusals=1;ReadyRuleCredit=0} }catch{$primary=$_} diff --git a/tests/NativeProviderPacks.Tests.ps1 b/tests/NativeProviderPacks.Tests.ps1 index 84cc23c7..6e51e5fd 100644 --- a/tests/NativeProviderPacks.Tests.ps1 +++ b/tests/NativeProviderPacks.Tests.ps1 @@ -43,6 +43,10 @@ function Get-WinEvent { $channel=if($f.TemplateMode -eq 'wrongchannel'){'Other/Operational'}else{$p.channel} $events+= [pscustomobject]@{Id=$e.id;Version=0;LogLink=[pscustomobject]@{LogName=$channel};Template=$template} } + if($f.LargeIds){ + if($f.LargeOnly){$events=@()} + foreach($large in @([long]3221734403,[long]4294967295)){$events+=[pscustomobject]@{Id=$large;Version=0;LogLink=[pscustomobject]@{LogName=$p.channel};Template='unselected template is never parsed'}} + } [pscustomobject]@{Name=$ListProvider;Id='11111111-1111-1111-1111-111111111111';LogLinks=@([pscustomobject]@{LogName=$p.channel});Events=$events} } function Read-Host {param($Prompt) if($f.PromptSchemaDrift){$f.TemplateMode='missing'};$f.Prompt} @@ -95,6 +99,11 @@ try { Assert (@($entry.RuleReviews|Where-Object Eligibility -ne 'Conditional').Count -eq 0 -and $report.ReadyRules -eq 0) 'Provider settings never convert incomplete rule evidence into Ready.' Assert ($entry.ProviderEvidence.Schema.Events[0].Fields[0].InType -eq 'win:UnicodeString' -and $entry.ProviderEvidence.Schema.Events[0].TemplateSha256.Length -eq 64) 'Report retains runtime version, native field types and template fingerprint.' Assert ($f.Writes.Count -eq 0 -and -not(Test-Path $backup)) 'Read-only plan creates no journal and makes no channel changes.' + Reset;$f.LargeIds=$true;$r=Invoke-WelaProviderPackCommand -Action Plan -Names winrm + Assert ($r.ExitCode -eq 0 -and $r.ControlsPlan[0].ProviderEvidence.CanConfigure) 'Actual WinRM Int64 event IDs above Int32 do not invalidate unrelated selected event6.' + Assert ($r.ControlsPlan[0].ProviderEvidence.Schema.Events.Count -eq 1 -and $r.ControlsPlan[0].ProviderEvidence.Schema.Events[0].Id -eq 6) 'Only the exact reviewed event6 enters schema evidence; large unselected IDs/templates are excluded.' + Reset;$f.LargeIds=$true;$f.LargeOnly=$true;$r=Invoke-WelaProviderPackCommand -Action Configure -Names winrm -Auto -BackupPath $backup + Assert ($r.ExitCode -eq 1 -and -not $r.ControlsPlan[0].ProviderEvidence.CanConfigure -and $f.Writes.Count -eq 0) 'Unrelated large native IDs cannot substitute for a missing selected event6.' Reset;$f.States['Microsoft-Windows-DNS-Client/Operational'].State='Not installed';$f.States['Microsoft-Windows-DNS-Client/Operational'].IsEnabled=$null $r=Invoke-WelaProviderPackCommand -Action Configure -Names dns-client -Auto -BackupPath $backup Assert ($r.ExitCode -eq 1 -and $f.Writes.Count -eq 0) 'Missing actual channel metadata cannot be replaced by provider-manifest availability.' diff --git a/website/docs/resources/changelog.ja.md b/website/docs/resources/changelog.ja.md index 9a2a9696..b85eb593 100644 --- a/website/docs/resources/changelog.ja.md +++ b/website/docs/resources/changelog.ja.md @@ -7,7 +7,7 @@ **改善:** -- Server 2022/2025 の Windows PowerShell 5.1/PowerShell 7 で、公開 provider-packs コマンドの Plan、DryRun、Configure、冪等性、前提不足・手動対象の拒否、部分適用を実機検証する使い捨て CI を追加。DNS Client、CAPI2、WinRM、RDP Client の設定と復元記録・ハッシュを確認し、完全な ACL、保持モード、大きい既存バッファ、他チャネル、サービス、全監査マスクの保持とテスト後の正確な復元を検証。イベント生成や Sigma 対応の証明は含みません。(@Shirofune-Security) +- Server 2022/2025 の Windows PowerShell 5.1/PowerShell 7 で、公開 provider-packs コマンドの Plan、DryRun、Configure、冪等性、前提不足・手動対象の拒否、部分適用を実機検証する使い捨て CI を追加。DNS Client、CAPI2、WinRM、RDP Client の設定と復元記録・ハッシュを確認し、完全な ACL、保持モード、大きい既存バッファ、他チャネル、サービス、全監査マスクの保持とテスト後の正確な復元を検証。イベント生成や Sigma 対応の証明は含みません。 WinRM のマニフェスト ID をネイティブの Int64 として比較し、対象外の大きい ID により必要なイベントの確認が失敗する不具合も修正。 (@Shirofune-Security) - Server 2022/2025 と Windows PowerShell 5.1/PowerShell 7 の破棄可能な環境で、Securityログ警告設定の公開CLIを検証します。未設定・0・高いしきい値、早い警告値の維持、DryRun、再実行、不正型の拒否と完全な復元を確認し、無関係な設定は保持します。ログ枯渇や警告イベント生成は検証範囲外です。 (@Shirofune-Security) diff --git a/website/docs/resources/changelog.md b/website/docs/resources/changelog.md index 86a00012..15a2ee40 100644 --- a/website/docs/resources/changelog.md +++ b/website/docs/resources/changelog.md @@ -7,7 +7,7 @@ **Improvements:** -- Added disposable native acceptance for public provider-pack Plan, DryRun, Configure, idempotence, missing/manual refusal and partial outcomes on Server 2022/2025 under Windows PowerShell 5.1/PowerShell 7. Actual DNS Client, CAPI2, WinRM and RDP Client configuration preserves descriptors, retention, larger buffers, unrelated channels, services and all audit masks, with journal/hash evidence and exact fixture cleanup; no event or Sigma credit. (@Shirofune-Security) +- Added disposable native acceptance for public provider-pack Plan, DryRun, Configure, idempotence, missing/manual refusal and partial outcomes on Server 2022/2025 under Windows PowerShell 5.1/PowerShell 7. Actual DNS Client, CAPI2, WinRM and RDP Client configuration preserves descriptors, retention, larger buffers, unrelated channels, services and all audit masks, with journal/hash evidence and exact fixture cleanup; no event or Sigma credit. Fixed WinRM manifest inspection to preserve native Int64 event IDs, so unrelated large IDs no longer block the selected event schema. (@Shirofune-Security) - Verify public Security-log warning configuration on disposable Server 2022/2025 hosts under Windows PowerShell 5.1/PowerShell 7: absent/zero/higher thresholds, earlier-threshold preservation, dry run, idempotence, wrong-type refusal and exact cleanup. Preserve unrelated registry values, channel configuration, audit masks and CrashOnAuditFail; no log-exhaustion or warning-event claim. (@Shirofune-Security)