# 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" function Invoke-Wsl { param([Parameter(ValueFromRemainingArguments=$true)][string[]]$Args) $output = & wsl.exe @Args 2>&1 | Where-Object { $_ -notmatch "localhost 代理|localhost proxy" } return @{ Code = $LASTEXITCODE; Output = ($output -join "`n") } } Write-Host "==> [1/6] 检查 WSL 环境" $null = Invoke-Wsl --status if ($LASTEXITCODE -ne 0) { throw "WSL 不可用。请先启用 WSL2(wsl --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 已验证该逃逸路径) $null = Invoke-Wsl -d $DistroName -- sh -c "printf '[network]\ngenerateResolvConf = false\n\n[interop]\nenabled = false\nappendWindowsPath = false\n' > /etc/wsl.conf" $null = Invoke-Wsl -d $DistroName -- sh -c "rm -f /etc/resolv.conf && printf 'nameserver 223.5.5.5\nnameserver 119.29.29.29\n' > /etc/resolv.conf" $null = Invoke-Wsl -d $DistroName -- sh -c "printf '$ApkMirror/main\n$ApkMirror/community\n' > /etc/apk/repositories" # 使 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"