agent-Specialization/scripts/setup-wsl-sandbox.ps1

109 lines
6.6 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# setup-wsl-sandbox.ps1 — 创建 astrion WSL2+bwrap 专用沙箱发行版
#
# 用法PowerShell普通用户即可无需管理员
# powershell -ExecutionPolicy Bypass -File scripts\setup-wsl-sandbox.ps1
# 可选参数:
# -DistroName 发行版名称(默认 astrion-sandbox需与 HOST_SANDBOX_WSL_DISTRO 一致)
# -InstallDir 发行版 VHDX 安装目录(默认 ~\.astrion\wsl-sandbox
# -RootfsUrl Alpine minirootfs 下载地址(默认阿里云镜像)
# -ApkMirror apk 软件源镜像(默认阿里云)
#
# 设计依据wsl2-sandbox-poc-report.md
# - 必须是专用发行版并关闭 interop否则沙箱内可经 cmd.exe 逃逸到宿主机;
# - 固化公共 DNS规避 localhost 代理导致的 WSL NAT DNS 失效;
# - 内置国内 apk 镜像,避免官方源大包下载卡死。
param(
[string]$DistroName = "astrion-sandbox",
[string]$InstallDir = (Join-Path $env:USERPROFILE ".astrion\wsl-sandbox"),
[string]$RootfsUrl = "https://mirrors.aliyun.com/alpine/v3.21/releases/x86_64/alpine-minirootfs-3.21.3-x86_64.tar.gz",
[string]$ApkMirror = "https://mirrors.aliyun.com/alpine/v3.21"
)
$ErrorActionPreference = "Stop"
$env:WSL_UTF8 = "1"
# 控制台与输出流统一 UTF-8后端以管道读取本脚本输出默认 GBK 会导致中文乱码
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
function Invoke-Wsl {
# PS 5.1 下 ValueFromRemainingArguments 会把 -d/-e 等 wsl 参数误绑定到函数通用参数
# -e 前缀匹配 -ErrorAction/-ErrorVariable 报二义性错误),故改为单个数组传参,
# 调用处一律Invoke-Wsl @('-d', $Name, '-e', 'true')
param([string[]]$WslArgs)
# PS 5.1 坑:全局 EAP=Stop 时原生命令写 stderr如 wsl 的 localhost 代理警告)
# 会直接抛 NativeCommandError 终止脚本Where 过滤来不及生效。
# 函数内降为 Continue函数作用域不影响全局 Stop 对其它命令的保护)。
$ErrorActionPreference = 'Continue'
$output = & wsl.exe @WslArgs 2>&1 | Where-Object { $_ -notmatch "localhost 代理|localhost proxy" }
return @{ Code = $LASTEXITCODE; Output = ($output -join "`n") }
}
Write-Host "==> [1/6] 检查 WSL 环境"
$wslStatus = Invoke-Wsl @('--status')
if ($wslStatus.Code -ne 0) {
throw "WSL 不可用。请先启用 WSL2wsl --install --no-distribution 或安装 Docker Desktop 后重试)。"
}
Write-Host "==> [2/6] 检查发行版 '$DistroName' 是否已存在"
$probe = Invoke-Wsl @('-d', $DistroName, '-e', 'true')
if ($probe.Code -eq 0) {
Write-Host " 发行版已存在跳过导入如需重建wsl --unregister $DistroName"
} else {
Write-Host "==> [3/6] 下载 Alpine rootfs"
$rootfs = Join-Path $env:TEMP "astrion-alpine-minirootfs.tar.gz"
if (-not (Test-Path $rootfs)) {
Invoke-WebRequest -Uri $RootfsUrl -OutFile $rootfs -UseBasicParsing
}
Write-Host " rootfs: $rootfs ($([math]::Round((Get-Item $rootfs).Length/1MB,1)) MB)"
Write-Host "==> [4/6] 导入为 WSL2 发行版"
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
$null = Invoke-Wsl @('--import', $DistroName, $InstallDir, $rootfs, '--version', '2')
$check = Invoke-Wsl @('-d', $DistroName, '-e', 'true')
if ($check.Code -ne 0) { throw "发行版导入失败: $($check.Output)" }
}
Write-Host "==> [5/6] 写入沙箱配置(关闭 interop / 固定 DNS / 国内镜像)"
# 关闭 Windows 互操作:防止沙箱内经 cmd.exe 逃逸宿主机PoC 已验证该逃逸路径)
$conf = Invoke-Wsl @('-d', $DistroName, '--', 'sh', '-c', "printf '[network]\ngenerateResolvConf = false\n\n[interop]\nenabled = false\nappendWindowsPath = false\n' > /etc/wsl.conf")
if ($conf.Code -ne 0) { throw "写入 wsl.conf 失败: $($conf.Output)" }
# 坑:首次导入的发行版内 /etc/resolv.conf 是 WSL 生成的 bind mount直接 rm 会 EBUSY 失败
# 旧脚本未检查返回码被静默吞掉terminate 后 generateResolvConf=false 生效,
# 不再自动生成 resolv.conf → 文件缺失 → DNS 完全失效)。必须先 umount 再重建真实文件。
$resolv = Invoke-Wsl @('-d', $DistroName, '--', 'sh', '-c', "umount /etc/resolv.conf 2>/dev/null; rm -f /etc/resolv.conf; printf 'nameserver 223.5.5.5\nnameserver 119.29.29.29\n' > /etc/resolv.conf && cat /etc/resolv.conf")
if ($resolv.Code -ne 0) { throw "写入 resolv.conf 失败: $($resolv.Output)" }
$apkRepo = Invoke-Wsl @('-d', $DistroName, '--', 'sh', '-c', "printf '$ApkMirror/main\n$ApkMirror/community\n' > /etc/apk/repositories")
if ($apkRepo.Code -ne 0) { throw "写入 apk 镜像源失败: $($apkRepo.Output)" }
# 使 wsl.conf 生效
$null = Invoke-Wsl @('--terminate', $DistroName)
Start-Sleep -Seconds 2
Write-Host "==> [6/6] 安装沙箱工具链bubblewrap / bash / python3 / git"
$apk = Invoke-Wsl @('-d', $DistroName, '--', 'sh', '-c', "apk update && apk add bubblewrap bash ncurses-libs python3 git")
if ($apk.Code -ne 0) { throw "apk 安装失败: $($apk.Output)" }
# 与 Windows 端 Git for Windows 默认的 core.autocrlf=true 对齐:
# Windows 检出的工作区文件是 CRLF沙箱 git 不开 autocrlf 会把全部文件误判为已修改
# (全量 +/-),且全量内容读取+xdiff 会让 git 命令慢到触发执行超时。
$null = Invoke-Wsl @('-d', $DistroName, '-e', 'git', 'config', '--system', 'core.autocrlf', 'true')
# 9P 挂载的 inode/ctime 语义与 Windows git 写入的索引不匹配,会导致沙箱 git 对全部
# 文件做内容重读(全树 status/diff >20s放宽 stat 校验后只需秒级。
$null = Invoke-Wsl @('-d', $DistroName, '-e', 'git', 'config', '--system', 'core.checkStat', 'minimal')
$null = Invoke-Wsl @('-d', $DistroName, '-e', 'git', 'config', '--system', 'core.trustctime', 'false')
# 验收
$verify = Invoke-Wsl @('-d', $DistroName, '--', 'sh', '-c', "bwrap --version && bash -c 'echo bash-ok' && python3 --version && git --version")
Write-Host $verify.Output
$escape = Invoke-Wsl @('-d', $DistroName, '--', 'bwrap', '--die-with-parent', '--new-session', '--unshare-all', '--ro-bind', '/', '/', '--proc', '/proc', '--dev', '/dev', '--', 'bash', '-c', "cmd.exe /c echo escape 2>&1 || echo interop-blocked")
if ($escape.Output -match "interop-blocked|not found") {
Write-Host " interop 逃逸封堵验证: OK"
} else {
Write-Warning " interop 可能未关闭,请检查 /etc/wsl.conf 后执行 wsl --terminate $DistroName"
}
Write-Host ""
Write-Host "完成。沙箱发行版 '$DistroName' 就绪。"
Write-Host "若使用了非默认名称,请设置环境变量 HOST_SANDBOX_WSL_DISTRO=$DistroName"