Files
maka/experiments/windows-sandbox/adversarial-matrix-smoke.ps1
agent-radar 7be535059e
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 19:05:14 +08:00

343 lines
13 KiB
PowerShell

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
param(
[Parameter(Mandatory = $true)]
[string]$LauncherPath
)
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $false
$launcher = (Resolve-Path -LiteralPath $LauncherPath).Path
$launcherDirectory = Split-Path -Parent $launcher
$tempRoot = if ($env:RUNNER_TEMP) { $env:RUNNER_TEMP } else { $env:TEMP }
$workRoot = Join-Path $tempRoot "maka-phase4-adversarial-$PID"
$ledgerRoot = Join-Path ([IO.Path]::GetTempPath()) 'maka-sandbox-acl-ledgers'
$registrySubkey = "Software\Maka\SandboxPhase4\$PID"
$registryPath = "HKCU:\$registrySubkey"
$registryValueName = 'HostSecret'
$pipeShortName = "maka-phase4-host-$PID"
$pipeName = "\\.\pipe\$pipeShortName"
$hostSecretName = 'MAKA_PHASE4_HOST_SECRET'
$listener = $null
$pipe = $null
$quarantinedSid = $null
$quarantineRoot = $null
$quarantinePath = $null
function Write-LaunchRequest {
param(
[string]$Name,
[string[]]$Arguments,
[string[]]$ReadRoots,
[string[]]$WriteRoots,
[string[]]$ExactReadRoots,
[string[]]$ExactWriteRoots
)
$request = [ordered]@{
version = 1
requestId = $Name
executable = $launcher
arguments = $Arguments
cwd = Split-Path -Parent $launcher
readRoots = $ReadRoots
writeRoots = $WriteRoots
exactReadRoots = $ExactReadRoots
exactWriteRoots = $ExactWriteRoots
network = 'restricted'
environment = @{ MAKA_PHASE4_ALLOWED = 'allowed' }
timeoutMs = 120000
}
$path = Join-Path $workRoot "$Name.json"
$requestJson = $request | ConvertTo-Json -Depth 5
[IO.File]::WriteAllText($path, $requestJson, [Text.UTF8Encoding]::new($false))
return $path
}
function Invoke-ExpectedAdmissionFailure {
param(
[string]$RequestPath,
[string]$Pattern,
[string]$Description
)
$result = Invoke-Launcher @('--appcontainer', $RequestPath)
$output = $result.Output
$exitCode = $result.ExitCode
$rendered = $output -join "`n"
if ($exitCode -eq 0 -or $rendered -notmatch $Pattern) {
throw "$Description did not fail closed: exit=$exitCode output=$rendered"
}
$global:LASTEXITCODE = 0
}
function Invoke-Launcher {
param([string[]]$Arguments)
$previousErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$output = & $launcher @Arguments 2>&1
return [pscustomobject]@{
ExitCode = $LASTEXITCODE
Output = @($output)
}
} finally {
$ErrorActionPreference = $previousErrorActionPreference
}
}
function Get-AppContainerSid {
param([string]$RequestId)
$sid = (& $launcher --appcontainer-sid $RequestId 2>&1) -join ''
if ($LASTEXITCODE -ne 0 -or $sid -notmatch '^S-1-15-2-') {
throw "Unable to resolve AppContainer SID for ${RequestId}: $sid"
}
return $sid
}
function Get-Sha256Hex {
param([string]$Value)
$sha = [Security.Cryptography.SHA256]::Create()
try {
return -join ($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($Value)) | ForEach-Object {
$_.ToString('x2')
})
} finally {
$sha.Dispose()
}
}
function Get-AclText {
param([string]$Path)
return (& icacls.exe $Path 2>&1) -join "`n"
}
New-Item -ItemType Directory -Path $workRoot -Force | Out-Null
New-Item -ItemType Directory -Path $ledgerRoot -Force | Out-Null
try {
$outsideRoot = Join-Path $workRoot 'outside'
$allowedRoot = Join-Path $workRoot 'allowed'
New-Item -ItemType Directory -Path $outsideRoot, $allowedRoot | Out-Null
$deniedPath = Join-Path $outsideRoot 'host-secret.txt'
$allowedReadPath = Join-Path $allowedRoot 'read.txt'
$allowedWritePath = Join-Path $allowedRoot 'write.txt'
[IO.File]::WriteAllText($deniedPath, 'must-not-be-readable')
[IO.File]::WriteAllText($allowedReadPath, 'allowed-read')
[IO.File]::WriteAllText($allowedWritePath, 'seeded')
$listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, 0)
$listener.Start()
$port = ([Net.IPEndPoint]$listener.LocalEndpoint).Port
$pipe = [IO.Pipes.NamedPipeServerStream]::new(
$pipeShortName,
[IO.Pipes.PipeDirection]::InOut,
1,
[IO.Pipes.PipeTransmissionMode]::Byte,
[IO.Pipes.PipeOptions]::Asynchronous
)
New-Item -Path $registryPath -Force | Out-Null
Set-ItemProperty -Path $registryPath -Name $registryValueName -Value 'registry-secret'
[Environment]::SetEnvironmentVariable($hostSecretName, 'environment-secret', 'Process')
$probeInputPath = Join-Path $allowedRoot 'adversarial-probe.json'
$probeJson = [ordered]@{
deniedPath = $deniedPath
allowedReadPath = $allowedReadPath
allowedWritePath = $allowedWritePath
loopbackPort = $port
pipeName = $pipeName
environmentSecretName = $hostSecretName
registrySubkey = $registrySubkey
registryValueName = $registryValueName
parentPid = $PID
} | ConvertTo-Json
[IO.File]::WriteAllText($probeInputPath, $probeJson, [Text.UTF8Encoding]::new($false))
$probeRequest = Write-LaunchRequest -Name "phase4-adversarial-$PID" `
-Arguments @('--adversarial-probe', $probeInputPath) `
-ReadRoots @($probeInputPath, $allowedReadPath, $launcherDirectory) `
-WriteRoots @($allowedWritePath) `
-ExactReadRoots @($probeInputPath, $allowedReadPath) `
-ExactWriteRoots @($allowedWritePath)
$result = Invoke-Launcher @('--appcontainer', $probeRequest)
$output = $result.Output
$exitCode = $result.ExitCode
$rendered = $output -join "`n"
$requiredEvidence = @(
'"fileDenied":true',
'"allowedRead":true',
'"allowedWrite":true',
'"tcpDenied":true',
'"namedPipeDenied":true',
'"environmentDenied":true',
'"registryDenied":true',
'"parentTokenDenied":true',
'"descendantAppContainer":true',
'"descendantInJob":true'
)
$missingEvidence = @($requiredEvidence | Where-Object { $rendered -notmatch [regex]::Escape($_) })
$descendantEvidence =
$rendered -match '"descendantSpawnDenied":true' -or
($rendered -match '"descendantAppContainer":true' -and
$rendered -match '"descendantInJob":true')
if (-not $descendantEvidence) {
$missingEvidence += 'descendantSpawnDenied or descendantAppContainer+descendantInJob'
}
$missingEvidence = @($missingEvidence | Where-Object {
$_ -notin @('"descendantAppContainer":true', '"descendantInJob":true')
})
if ($exitCode -ne 0 -or $missingEvidence.Count -gt 0) {
throw "Packaged adversarial probe failed: exit=$exitCode missing=$($missingEvidence -join ', ') output=$rendered"
}
# Recursive roots fail admission when any entry redirects to another tree.
$junctionRoot = Join-Path $workRoot 'junction-root'
New-Item -ItemType Directory -Path $junctionRoot | Out-Null
New-Item -ItemType Junction -Path (Join-Path $junctionRoot 'escape') -Target $outsideRoot | Out-Null
$junctionRequest = Write-LaunchRequest -Name "phase4-junction-$PID" `
-Arguments @('--self-probe') -ReadRoots @($junctionRoot) -WriteRoots @() `
-ExactReadRoots @() -ExactWriteRoots @()
Invoke-ExpectedAdmissionFailure -RequestPath $junctionRequest -Pattern 'reparse point' `
-Description 'Junction alias admission'
# Recursive roots also reject a file whose content is reachable through a
# second hard-link name outside the admitted tree.
$hardLinkRoot = Join-Path $workRoot 'hardlink-root'
New-Item -ItemType Directory -Path $hardLinkRoot | Out-Null
$hardLinkOutside = Join-Path $outsideRoot 'hardlink-source.txt'
[IO.File]::WriteAllText($hardLinkOutside, 'hardlink-secret')
New-Item -ItemType HardLink -Path (Join-Path $hardLinkRoot 'alias.txt') `
-Target $hardLinkOutside | Out-Null
$hardLinkRequest = Write-LaunchRequest -Name "phase4-hardlink-$PID" `
-Arguments @('--self-probe') -ReadRoots @($hardLinkRoot) -WriteRoots @() `
-ExactReadRoots @() -ExactWriteRoots @()
Invoke-ExpectedAdmissionFailure -RequestPath $hardLinkRequest -Pattern 'multi-link' `
-Description 'Hard-link alias admission'
# An unsettled identity is never interpreted or reused by later launches.
# The synthetic quarantined record carries a live ACE so this verifies both
# preservation-for-inspection and fresh-identity isolation. The test removes
# its own synthetic residue in finally.
$quarantineRequestId = "phase4-unsettled-$PID"
$quarantinedSid = Get-AppContainerSid $quarantineRequestId
$quarantineRoot = Join-Path $workRoot 'quarantined-root'
New-Item -ItemType Directory -Path $quarantineRoot | Out-Null
& icacls.exe $quarantineRoot /grant "*$quarantinedSid`:(OI)(CI)RX" /T /Q | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'Unable to seed quarantined AppContainer ACE' }
$quarantinePath = Join-Path $ledgerRoot "$(Get-Sha256Hex $quarantineRequestId).json.quarantined"
[ordered]@{
version = 2
requestId = $quarantineRequestId
appContainerSid = $quarantinedSid
roots = @([ordered]@{
path = $quarantineRoot
read = $true
write = $false
readRecursive = $true
writeRecursive = $false
})
} | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $quarantinePath -Encoding utf8
$recoveryRequestId = "phase4-quarantine-recovery-$PID"
$recoverySid = Get-AppContainerSid $recoveryRequestId
if ($recoverySid -eq $quarantinedSid) {
throw 'A later launch reused the quarantined AppContainer identity'
}
$recoveryRequest = Write-LaunchRequest -Name $recoveryRequestId `
-Arguments @('--self-probe') -ReadRoots @($allowedReadPath) -WriteRoots @() `
-ExactReadRoots @($allowedReadPath) -ExactWriteRoots @()
$recoveryResult = Invoke-Launcher @('--appcontainer', $recoveryRequest)
if ($recoveryResult.ExitCode -ne 0) {
throw "Launch after quarantined state failed: $($recoveryResult.Output -join "`n")"
}
if (-not (Test-Path -LiteralPath $quarantinePath)) {
throw 'A later launch interpreted or deleted quarantined recovery evidence'
}
if ((Get-AclText $quarantineRoot) -notmatch [regex]::Escape($quarantinedSid)) {
throw 'A later launch removed authority whose Job was not proven empty'
}
Write-Host "Phase 4 adversarial matrix verified: $rendered"
} finally {
$cleanupErrors = [System.Collections.Generic.List[string]]::new()
if ($listener) {
try { $listener.Stop() } catch { $cleanupErrors.Add("listener stop: $($_.Exception.Message)") }
}
if ($pipe) {
try { $pipe.Dispose() } catch { $cleanupErrors.Add("pipe dispose: $($_.Exception.Message)") }
}
try {
[Environment]::SetEnvironmentVariable($hostSecretName, $null, 'Process')
} catch {
$cleanupErrors.Add("environment cleanup: $($_.Exception.Message)")
}
if (Test-Path -LiteralPath $registryPath) {
try {
Remove-Item -LiteralPath $registryPath -Recurse -Force -ErrorAction Stop
} catch {
$cleanupErrors.Add("registry cleanup: $($_.Exception.Message)")
}
}
if ($quarantineRoot -and $quarantinedSid) {
$aclCleanupOutput = & icacls.exe $quarantineRoot /remove "*$quarantinedSid" /T /L /Q 2>&1
$aclCleanupExitCode = $LASTEXITCODE
if ($aclCleanupExitCode -ne 0) {
$cleanupErrors.Add(
"quarantined ACE cleanup failed: exit=$aclCleanupExitCode output=$($aclCleanupOutput -join ' ')"
)
}
}
if ($quarantinePath) {
if (Test-Path -LiteralPath $quarantinePath) {
try {
Remove-Item -LiteralPath $quarantinePath -Force -ErrorAction Stop
} catch {
$cleanupErrors.Add("quarantine ledger cleanup: $($_.Exception.Message)")
}
}
if (Test-Path -LiteralPath $quarantinePath) {
$cleanupErrors.Add("quarantine ledger still exists: $quarantinePath")
}
}
if ($quarantineRoot -and (Test-Path -LiteralPath $quarantineRoot)) {
try {
if ((Get-AclText $quarantineRoot) -match [regex]::Escape($quarantinedSid)) {
$cleanupErrors.Add("quarantined ACE still exists: $quarantinedSid")
}
} catch {
$cleanupErrors.Add("quarantined ACE verification: $($_.Exception.Message)")
}
}
if (Test-Path -LiteralPath $workRoot) {
try {
Remove-Item -LiteralPath $workRoot -Recurse -Force -ErrorAction Stop
} catch {
$cleanupErrors.Add("work root cleanup: $($_.Exception.Message)")
}
}
if (Test-Path -LiteralPath $workRoot) {
$cleanupErrors.Add("work root still exists: $workRoot")
}
if ($cleanupErrors.Count -gt 0) {
throw "Phase 4 adversarial matrix cleanup failed: $($cleanupErrors -join '; ')"
}
}
$global:LASTEXITCODE = 0
exit 0