fix(launcher): harden LA11 gate evidence
This commit is contained in:
parent
accd01a008
commit
9f9c116792
7 changed files with 514 additions and 89 deletions
|
|
@ -28,6 +28,7 @@ if ([string]::IsNullOrWhiteSpace($pwsh)) {
|
|||
}
|
||||
$validator = Join-Path $Repository 'tools/test-campaign-la-session-status.ps1'
|
||||
$capture = Join-Path $Repository 'tools/capture-campaign-la-session-process.ps1'
|
||||
. (Join-Path $Repository 'tools/CampaignLaProcessCorrelation.ps1')
|
||||
|
||||
function Write-Profile([string]$Path, [string]$Secret) {
|
||||
$document = [ordered]@{
|
||||
|
|
@ -86,16 +87,14 @@ function Invoke-Validator(
|
|||
[string]$Status,
|
||||
[string]$Profile,
|
||||
[string]$Report,
|
||||
[int]$ExpectedProcessId,
|
||||
[bool]$ShouldPass,
|
||||
[string]$SessionConfig = '') {
|
||||
[string]$ProcessCapture,
|
||||
[bool]$ShouldPass) {
|
||||
$arguments = [Collections.Generic.List[string]]::new()
|
||||
foreach ($value in @(
|
||||
'-NoProfile', '-File', $validator,
|
||||
'-StatusFile', $Status,
|
||||
'-Mode', 'gui',
|
||||
'-ExpectedProcessId', $ExpectedProcessId.ToString(
|
||||
[Globalization.CultureInfo]::InvariantCulture),
|
||||
'-ProcessCapturePath', $ProcessCapture,
|
||||
'-CredentialProfilePath', $Profile,
|
||||
'-ExpectedPlugin', 'smoke',
|
||||
'-AllowPluginFailure',
|
||||
|
|
@ -103,10 +102,6 @@ function Invoke-Validator(
|
|||
'-ReportPath', $Report)) {
|
||||
$arguments.Add($value)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($SessionConfig)) {
|
||||
$arguments.Add('-SessionConfigPath')
|
||||
$arguments.Add($SessionConfig)
|
||||
}
|
||||
$start = [Diagnostics.ProcessStartInfo]::new($pwsh)
|
||||
$start.UseShellExecute = $false
|
||||
$start.CreateNoWindow = $true
|
||||
|
|
@ -127,6 +122,30 @@ function Invoke-Validator(
|
|||
}
|
||||
}
|
||||
|
||||
function Write-ProcessCapture(
|
||||
[string]$Path,
|
||||
[int]$ProcessId,
|
||||
[string]$ProcessInstanceIdentity,
|
||||
[string]$SessionConfigPath,
|
||||
[string]$CommandFingerprint = ('a' * 64)) {
|
||||
$sessionId = [IO.Path]::GetFileName(
|
||||
[IO.Path]::GetDirectoryName($SessionConfigPath))
|
||||
$document = [ordered]@{
|
||||
schemaVersion = 2
|
||||
kind = 'campaign-la-session-process-capture'
|
||||
processId = $ProcessId
|
||||
processInstanceIdentity = $ProcessInstanceIdentity
|
||||
sessionId = $sessionId
|
||||
sessionConfigPath = [IO.Path]::GetFullPath($SessionConfigPath)
|
||||
commandLineFingerprintSha256 = $CommandFingerprint
|
||||
capturedUtc = [DateTime]::UtcNow.ToString('O')
|
||||
}
|
||||
[IO.File]::WriteAllText(
|
||||
$Path,
|
||||
($document | ConvertTo-Json -Depth 4),
|
||||
[Text.UTF8Encoding]::new($false))
|
||||
}
|
||||
|
||||
$quickInfo = [Diagnostics.ProcessStartInfo]::new($pwsh)
|
||||
$quickInfo.UseShellExecute = $false
|
||||
$quickInfo.ArgumentList.Add('-NoProfile')
|
||||
|
|
@ -138,13 +157,33 @@ $goneProcessId = $quick.Id
|
|||
$quick.WaitForExit()
|
||||
$quick.Dispose()
|
||||
|
||||
$sessionRoot = Join-Path $OutputDirectory 'fixture-session'
|
||||
$null = New-Item -ItemType Directory -Path $sessionRoot
|
||||
$sessionConfig = Join-Path $sessionRoot 'session.json'
|
||||
[IO.File]::WriteAllText($sessionConfig, '{}', [Text.UTF8Encoding]::new($false))
|
||||
$syntheticIdentity = if ($IsWindows) {
|
||||
'windows-creation-v1:638000000000000000'
|
||||
} else { 'linux-proc-start-v1:00000000-0000-0000-0000-000000000001:1' }
|
||||
$goneCapture = Join-Path $OutputDirectory 'gone-process.capture.json'
|
||||
Write-ProcessCapture `
|
||||
$goneCapture $goneProcessId $syntheticIdentity $sessionConfig
|
||||
|
||||
$profile = Join-Path $OutputDirectory 'launcher-profiles.json'
|
||||
Write-Profile $profile 'la11-positive-secret-7E477A2D'
|
||||
$positiveStatus = Join-Path $OutputDirectory 'positive.jsonl'
|
||||
Write-Events $positiveStatus (New-GuiEvents)
|
||||
Invoke-Validator `
|
||||
$positiveStatus $profile (Join-Path $OutputDirectory 'positive.validation.json') `
|
||||
$goneProcessId $true
|
||||
$goneCapture $true
|
||||
|
||||
$malformedCapture = Join-Path $OutputDirectory 'malformed-process.capture.json'
|
||||
[IO.File]::WriteAllText(
|
||||
$malformedCapture,
|
||||
'{"schemaVersion":2}',
|
||||
[Text.UTF8Encoding]::new($false))
|
||||
Invoke-Validator `
|
||||
$positiveStatus $profile (Join-Path $OutputDirectory 'malformed.validation.json') `
|
||||
$malformedCapture $false
|
||||
|
||||
foreach ($reason in @('transport', 'reconnect', 'other')) {
|
||||
$events = @(New-GuiEvents)
|
||||
|
|
@ -152,7 +191,7 @@ foreach ($reason in @('transport', 'reconnect', 'other')) {
|
|||
$path = Join-Path $OutputDirectory "reason-$reason.jsonl"
|
||||
$report = Join-Path $OutputDirectory "reason-$reason.validation.json"
|
||||
Write-Events $path $events
|
||||
Invoke-Validator $path $profile $report $goneProcessId $false
|
||||
Invoke-Validator $path $profile $report $goneCapture $false
|
||||
$result = Get-Content -LiteralPath $report -Raw | ConvertFrom-Json
|
||||
if (-not ($result.failures -match 'disconnected reason')) {
|
||||
throw "Disconnected reason '$reason' was not rejected by its exact assertion."
|
||||
|
|
@ -186,7 +225,7 @@ foreach ($case in $secretCases) {
|
|||
$path = Join-Path $OutputDirectory "secret-$case.jsonl"
|
||||
$report = Join-Path $OutputDirectory "secret-$case.validation.json"
|
||||
Write-Events $path $events
|
||||
Invoke-Validator $path $caseProfile $report $goneProcessId $false
|
||||
Invoke-Validator $path $caseProfile $report $goneCapture $false
|
||||
$result = Get-Content -LiteralPath $report -Raw | ConvertFrom-Json
|
||||
if (-not ($result.failures -match 'credential value')) {
|
||||
throw "Credential echo case '$case' was not rejected by recursive scanning."
|
||||
|
|
@ -212,12 +251,14 @@ if ($IsLinux) {
|
|||
[IO.File]::GetUnixFileMode($sourceHost))
|
||||
}
|
||||
|
||||
$sessionConfig = Join-Path $OutputDirectory 'session.json'
|
||||
[IO.File]::WriteAllText($sessionConfig, '{}', [Text.UTF8Encoding]::new($false))
|
||||
$targetReady = Join-Path $OutputDirectory 'target.ready'
|
||||
$targetRelease = Join-Path $OutputDirectory 'target.release'
|
||||
$unrelatedReady = Join-Path $OutputDirectory 'unrelated.ready'
|
||||
$unrelatedRelease = Join-Path $OutputDirectory 'unrelated.release'
|
||||
$unrelatedSessionRoot = Join-Path $OutputDirectory 'unrelated-session'
|
||||
$null = New-Item -ItemType Directory -Path $unrelatedSessionRoot
|
||||
$unrelatedConfig = Join-Path $unrelatedSessionRoot 'session.json'
|
||||
[IO.File]::WriteAllText($unrelatedConfig, '{}', [Text.UTF8Encoding]::new($false))
|
||||
|
||||
function Start-Fixture([string[]]$Arguments) {
|
||||
$start = [Diagnostics.ProcessStartInfo]::new($sameNameHost)
|
||||
|
|
@ -230,7 +271,7 @@ function Start-Fixture([string[]]$Arguments) {
|
|||
$target = Start-Fixture @(
|
||||
'hold-campaign-la-process', '--config', $sessionConfig, $targetReady, $targetRelease)
|
||||
$unrelated = Start-Fixture @(
|
||||
'hold-update-lease', 'session', (Join-Path $OutputDirectory 'unrelated-data'),
|
||||
'hold-campaign-la-process', '--config', $unrelatedConfig,
|
||||
$unrelatedReady, $unrelatedRelease)
|
||||
if ($null -eq $target -or $null -eq $unrelated) {
|
||||
throw 'Could not start process-correlation fixtures.'
|
||||
|
|
@ -252,16 +293,19 @@ try {
|
|||
-SessionConfigPath $sessionConfig -ReportPath $captureReport
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Process capture failed.' }
|
||||
$captured = Get-Content -LiteralPath $captureReport -Raw | ConvertFrom-Json
|
||||
if ([int]$captured.processId -ne $target.Id) {
|
||||
throw 'Process capture did not return the exact correlated PID.'
|
||||
if ([int]$captured.processId -ne $target.Id -or
|
||||
[string]$captured.sessionConfigPath -cne $sessionConfig -or
|
||||
[string]$captured.commandLineFingerprintSha256 -cnotmatch '^[0-9a-f]{64}$') {
|
||||
throw 'Process capture did not return exact sanitized instance evidence.'
|
||||
}
|
||||
|
||||
$liveReport = Join-Path $OutputDirectory 'live-pid.validation.json'
|
||||
Invoke-Validator `
|
||||
$positiveStatus $profile $liveReport $target.Id $false $sessionConfig
|
||||
$positiveStatus $profile $liveReport $captureReport $false
|
||||
$liveResult = Get-Content -LiteralPath $liveReport -Raw | ConvertFrom-Json
|
||||
if (-not ($liveResult.failures -match 'remains alive')) {
|
||||
throw 'A live exact child PID was not rejected by the terminal validator.'
|
||||
if (-not ($liveResult.failures -match 'process instance.*remains alive') -or
|
||||
$liveResult.capturedProcessInstanceExited) {
|
||||
throw 'A live exact child instance was not rejected by the terminal validator.'
|
||||
}
|
||||
|
||||
Set-Content -LiteralPath $targetRelease -Value 'release' -NoNewline
|
||||
|
|
@ -269,7 +313,31 @@ try {
|
|||
Invoke-Validator `
|
||||
$positiveStatus $profile `
|
||||
(Join-Path $OutputDirectory 'unrelated-same-name.validation.json') `
|
||||
$target.Id $true $sessionConfig
|
||||
$captureReport $true
|
||||
|
||||
$reusedCapture = Join-Path $OutputDirectory 'reused-pid.capture.json'
|
||||
$capturedIdentity = [string]$captured.processInstanceIdentity
|
||||
$identitySeparator = $capturedIdentity.LastIndexOf(':')
|
||||
$capturedStartValue = [uint64]::Parse(
|
||||
$capturedIdentity.Substring($identitySeparator + 1),
|
||||
[Globalization.CultureInfo]::InvariantCulture)
|
||||
$reusedPriorIdentity = $capturedIdentity.Substring(0, $identitySeparator + 1) `
|
||||
+ ($capturedStartValue + 1).ToString(
|
||||
[Globalization.CultureInfo]::InvariantCulture)
|
||||
Write-ProcessCapture `
|
||||
$reusedCapture `
|
||||
$unrelated.Id `
|
||||
$reusedPriorIdentity `
|
||||
$sessionConfig `
|
||||
([string]$captured.commandLineFingerprintSha256)
|
||||
$reusedReport = Join-Path $OutputDirectory 'reused-pid.validation.json'
|
||||
Invoke-Validator $positiveStatus $profile $reusedReport $reusedCapture $true
|
||||
$reusedResult = Get-Content -LiteralPath $reusedReport -Raw | ConvertFrom-Json
|
||||
if (-not $reusedResult.capturedPidReused -or
|
||||
-not $reusedResult.capturedProcessInstanceExited -or
|
||||
-not $reusedResult.sessionConfigProcessExited) {
|
||||
throw 'A reused PID was not distinguished from the exited captured instance.'
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Set-Content -LiteralPath $targetRelease -Value 'release' -NoNewline
|
||||
|
|
@ -287,7 +355,10 @@ $summary = [ordered]@{
|
|||
disconnectedReasonNegatives = 3
|
||||
credentialStringFieldNegatives = $secretCases.Count
|
||||
exactPidCapture = $true
|
||||
livePidRejected = $true
|
||||
stableProcessInstanceCapture = $true
|
||||
liveProcessInstanceRejected = $true
|
||||
malformedProcessCaptureRejected = $true
|
||||
injectedPidReuseIgnored = $true
|
||||
unrelatedSameNameIgnored = $true
|
||||
platform = if ($IsWindows) { 'windows' } else { 'linux' }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue