17 KiB
Phase 4 审查(T-401 ~ T-403:进程检测与启动 / 子软件更新 / 盒子自更新)
审查范围:
87083c3(T-401 进程检测、受控启动与切换临界区复查)、339beaa(T-402 子软件更新编排与自然退出等待)、1300421(T-403 SoftBox 自更新助手与健康确认恢复)。 审查视角:全栈开发工程师 + 安全审计。 审查方式:静态代码审计 + 攻击路径推演(命令注入、TOCTOU、自更新崩溃恢复)。因 WSL 无 Go,未执行测试。 日期:2026-07-19
总体判断
Phase 4 的启动/更新安全设计质量高,未发现可证实的命令注入、强杀或越界移动安全缺陷;自更新恢复状态机存在一项须由 T-617 修复的 durability 后状态不一致。 三个最敏感的点都做对了:启动无命令注入面(调用方给不了路径/参数/工作目录,平台层无 shell、无用户参数、绝对路径)、更新绝不强杀(只请求关闭 + 等自然退出)、自更新健康确认前不删 backup。同时 Phase 3 审查的 O1(切换前不复查运行状态)已被 T-401 闭合。
不过,补充全栈复核发现 T-403 的自动化故障覆盖低于任务验收所要求的范围。因此 O1 不能仅视为普通“优化项”:Windows 真机锁语义仍属于 T-601,但可在无头环境验证的失败注入、崩溃 phase 和恢复契约应作为后续整改的高优先级测试项。O2、O3 仍是准确的架构/装配边界说明。
遗留项闭合确认
| 遗留项 | 状态 | 证据 |
|---|---|---|
| Phase 3 O1 · switch 前不复查 running | 已闭合(T-401) | switcher.go 新增 preSwitchCheck,在 current→backup rename 前执行(第 97-98 行);InstallService.preSwitchCheck 显式 IsRunning,运行则返回 ErrTargetRunning(→ app_running),检查失败返回 ErrTargetStateCheck——正是 Phase 3 裁定要求的"显式复查、不从 rename 失败推断"。覆盖"WaitForExit 后重新启动"竞态 |
逐模块核验
T-401 · 进程检测与受控启动(application/launch + platform/windows)
无命令注入面(核心安全属性):
Request只含AppID——"caller cannot supply a path, arguments or working directory";Command全部由已验证 installed 元数据构造。launchCommand:entrypoint/workdir 经safepath.ValidateRelative+JoinUnder(destination 包含性)+Lstat(拒符号链接/非普通文件);entrypoint 必须在已记录的 installedFiles里(containsEntrypoint),不能启动未随可信安装记录落盘的任意文件;current/workdir 经requireRealDirectory(拒符号链接目录)。- 平台层
Start:exec.Command(command.Entrypoint)无参数 +cmd.Dir,要求绝对路径;startElevated用ShellExecuteExW"runas" 且不传 parameters。无 shell、无用户参数、无 arg 注入。
Fail-closed 门禁链: ResolveCurrent → compat → authorization → running → launch,5 个检查器强制非 nil,任一失败/不满足即拒;稳定错误码枚举(not_installed / launch_target_unsafe / app_incompatible / not_authorized / app_running…)。AuthorizationChecker 是必需边界(真实许可策略留 T-502),不发 allow-all。
Windows 隔离: Toolhelp 检测、RtlGetVersion、无参数进程创建、runas 均在 platform_windows.go;platform_stub.go(!windows)全部返回 ErrUnsupported(fail-closed)。
T-402 · 子软件更新编排(application/update)
- 绝不强杀:running 时只
ConfirmClose(问用户)+WaitForExit(等自然退出);超时 →DeadlineExceeded(exit_wait_timeout),取消 →ctx.Err()(exit_wait_canceled);接口注释明确 confirmer/waiter"must not close or terminate the process"。 - 无降级/无重装:
validateTargetVersion要求新版本 SemVer 严格大于已装版本,package 与App.Packages[arch]精确对应。 - 委托可信安装器:staging/switch/rollback 与切换前第二次 running 复查都交给
InstallService;因此"等待结束后应用重新启动"的竞态由 T-401 的 preSwitchCheck 兜住。 - 5 依赖强制非 nil,
CloseTimeout ∈ [1s, 10min];更新 use case 不改 transaction 或 data/licenses。
T-403 · 盒子自更新(core/updater + cmd/softboxupdater)
- 等父进程自然退出:
WaitForProcessExit(ctx, ParentPID, timeout);超时返回错误,从不 kill。 - 固定 layout,无任意端点:
StagingDir/TargetDir对照固定 layout 校验;RequestID从 staging 规范目录名派生复验(不走 CLI)。 - 只启动 SoftBox.exe + 固定健康标志:
ProductExecutableName="SoftBox.exe"、InternalHealthFlag;StartSelfUpdate只传固定健康 requestID。 - journal 化可回滚:
prepared → target_backed_up → staging_activated → launched → committed,任一失败rollback(errors.Join保留根因),Recover处理中断;DirectorySyncer(WindowsFlushFileBuffers)做目录耐久。 - 健康确认前不删 backup:
WaitForHealth只接受精确 requestID,通过后才removeManagedTree(backup);失败 → rollback 恢复旧版;锁导致 restore 失败则保留 journal/backup 不强杀。
需优化项(按优先级,均非安全缺陷)
O1 · 自更新的"先启动后健康确认"必然要处理运行中的新进程
现状: StartSelfUpdate 先启动新 SoftBox,WaitForHealth 才等确认。若健康超时/取消,rollback 需移动含正在运行的新 SoftBox.exe 的 target 目录;Windows 文件锁可能使 restore 失败,设计选择保留 journal/backup 交下次 Recover(不强杀)。
影响: 这是自更新的固有约束(必须运行新版才能健康检查),处理方式安全(不强杀、可恢复),非缺陷。但"launched 但未 committed、rollback 被锁阻塞"的中间态依赖下次 Recover + 新进程在"未 committed 事务"下的行为收敛。
建议: 重点测试:健康超时后的 Recover 路径、新 SoftBox 在"仅匹配已激活事务下写健康"门禁的正确性、以及锁阻塞 restore 后的可恢复性。真机文件锁语义归 T-601。
O2 · 无生产装配,端到端启动/更新闭环未接通(与 Phase 3 一致)
现状: launch/update/updater 三个 use case 在 core 层建成且测试覆盖,但无生产 cmd 装配(无可信 Catalog/许可证/下载完成文件来源);当前有意不注入 allow-all 授权或伪造端到端按钮。
影响: 与 Phase 3 结论一致——无头 core 的启动/更新/自更新边界已完成;端到端编排未接通。M3 定义含"启动",故 M3 仍需端到端装配(依赖真实发布配置 + T-502 授权)才算闭环。
建议: 在 current-state.md 保持准确表述;端到端装配随发布配置与 T-502 授权落地。
O3 · 授权门禁在位但策略待 T-502
现状: launch 服务强制 AuthorizationChecker(不可绕过),但真实许可验证是 T-502。
影响: 架构正确(把授权做成必需边界);当前不发 allow-all 是好纪律,意味着尚无生产启动路径。非缺陷。
建议: T-502 落地时把真实许可策略接入该边界,并补"未授权拒绝启动/试用可启动"的端到端测试。
O4 · T-403 的故障注入与恢复测试覆盖不足
**现状:**现有 core/updater/updater_test.go 覆盖成功激活、健康失败后的顺利回滚、父进程等待失败、部分固定布局拒绝、target_backed_up phase 的恢复、健康文件定位与 request ID 拒绝。但它没有以可控 seam 覆盖 rename、transaction 写入、目录同步、backup/journal 清理失败,也没有覆盖 staging_activated、launched、committed 三个中断 phase 的 Recover。cmd/softbox 的内部 health flag 参数处理也没有专门测试;现有 CLI 测试只覆盖 SoftBoxUpdater 的重复参数拒绝。
**影响:**这不是已证实的实现安全漏洞:现有代码在恢复被 Windows 文件锁阻止时会 fail closed、保留 journal/backup,且不强杀进程。但它与 T-403 验收中“每个 rename/journal 故障和崩溃注入均有稳定错误、可恢复时旧 app 恢复”的测试证据不相符。没有这些测试,无法充分证明 O1 中“锁阻塞后等待下次 Recover”的状态机在所有持久化边界都可预测。
**建议:**单列整改任务,先为 updater 的文件操作和目录同步提供最小失败注入 seam,补齐健康超时→rollback 被锁阻塞→后续 Recover、各 transaction phase 崩溃恢复、journal/rename/sync/cleanup 失败与 health CLI 参数拒绝的双端测试。真实 Windows 文件锁、杀毒软件和断电结果仍只由 T-601 真机/VM 验证,不以单元测试替代。
追加状态机发现:prepared journal 成功写入后,app → backups/<request-id> 的 os.Rename 可能已经完成,而 renameDirectory 在随后的目录 sync 返回错误。当前 Update 直接返回 failBeforeActivation;磁盘成为 target 缺失、managed backup 存在、journal 仍为 prepared。现有 recoverLayout 对 prepared 无条件删除 journal,因而不会把 old app 恢复到 target。该情形不是 Windows 真机锁的未知行为,而是可由既有 DirectorySyncer seam 在无头测试复现的恢复一致性缺陷。T-617 必须修复此拓扑判断并覆盖即时/下次恢复;若恢复仍失败,必须保留 journal/backup 与 ErrRecoveryRequired。
O5 · 端到端装配依赖应拆开跟踪
**现状:**O2 正确指出 launch/update/updater 目前只完成无头 core 边界;production cmd 尚未装配可信 Catalog、许可证或 completed-download 消费源。
**补充:**不应把所有装配前置条件笼统归为“等待 T-502”。可信 softbox-catalog 发布配置、已签名 Catalog 的加载/缓存/目标过滤和下载完成文件的受控消费,可在 T-502 前独立接入;T-502 决定的是授权门禁下的实际启动资格。自更新还另缺可信 self-update 包的下载、验签、版本选择和 UI 触发器。
**建议:**后续按“可信 Catalog 发布配置 → 下载/安装/更新编排 → T-502 授权接线 → 自更新发布源与触发器”分别落任务和验收,避免将目前的 fail-closed 空壳误表述为端到端闭环。
补充验证(2026-07-19)
- 复核环境实际运行了
go -C core vet ./...、go -C core test -count=10 ./updater ./application/launch ./application/update ./application/install ./installer与./scripts/verify_phase0.ps1;核心相关包、双端 UI/platform 测试、双端 Windows amd64 构建以及治理校验均通过。 - 尝试运行
go test -race时,当前环境因CGO_ENABLED被禁用而拒绝执行;这不影响项目既定验证的通过结论,但不应把本次复核表述为 race detector 已通过。 - 本节是对本报告静态审计结论的补充核验,不改变原报告“未在静态攻击路径中发现命令注入、强杀或越界移动缺陷”的判断。
结论
Phase 4 的核心安全属性(启动无注入面、更新不强杀、自更新可回滚且健康门控、切换临界区复查闭合 Phase 3 O1、Windows 隔离 + fail-closed stub)全部到位,失败码体系完整。建议处理顺序:
- [最高优先级 · 恢复修复与测试] O1/O4:T-617 先修 prepared rename+sync 后 target/backup 与 journal 不一致,再补自更新健康超时后的 Recover/锁阻塞恢复、新进程健康门禁、transaction phase 崩溃恢复与 journal/rename/sync/cleanup 失败注入测试;真机锁语义归 T-601。
- [准确性 · 装配规划] O2/O5:文档保持"无头 core 已完成、端到端未接通"表述;可信 Catalog/下载消费、T-502 授权和自更新发布源分别落地,不笼统等待单一任务。
- [后续] O3:T-502 接入真实授权策略并补端到端授权测试。
声明:本报告为静态审计与攻击路径推演,逐项核了实现(launch 无参数
exec.Command+ShellExecuteExW无 parameters、containsEntrypoint+ safepath + Lstat、update 的 WaitForExit 不强杀、updater 的 journal/rollback/健康门控、switcherpreSwitchCheck);因 WSL 无 Go 未执行测试,双 workspace 闸门通过为 Codex 自述。O1 的 Windows 文件锁语义需真机验证。
交叉复核裁定(定稿)
复核视角:Claude 全栈开发工程师,对 Codex 补充复核(O4/O5 + 验证段)逐条核验后裁定。 核验方式:枚举
core/updater/*_test.go全部测试函数;检查故障注入 seam、各 transaction phase 的Recover覆盖、cmd/softboxhealth flag 测试存在性;因 WSL 无 Go,未执行测试。 日期:2026-07-19
裁定结论
Codex 本轮补充成立且比原审核更严谨,全部接受。 O4 是 Codex 对自己 T-403 测试覆盖的诚实自查,其每一条具体主张均经核实属实。原审核 O1 只写了"建议重点测试",未实际读 updater_test.go、未枚举具体缺口;Codex 做了该具体核查并定位到缺口,这部分比原审核更扎实。
已确认的事实(可作为后续任务前提)
| 项 | 结论 | 证据 |
|---|---|---|
| updater 现有覆盖范围 | 与 O4 所述一致 | 8 个测试:成功激活、健康失败回滚、父进程等待失败、跨 layout/staging symlink 拒绝、target_backed_up 单 phase Recover、health locator、requestID 拒绝 |
| 无文件系统故障注入 seam | 属实 | 全部用永远成功的 testSyncer{};无任何测试令 rename / transaction 写入 / 目录 sync / 清理失败(唯一失败路径来自 HealthWaiter) |
| 中断 phase 的 Recover 未测全 | 属实 | 仅 TestRecoverRestoresTargetBackedUpTransaction 覆盖 target_backed_up;staging_activated/launched/committed 无 Recover 测试(第 194 行的 phaseStagingActivated 是 health-ack 测试的 setup,非 Recover 用例) |
| cmd health flag 无专门测试 | 属实 | app-modern/cmd/softbox/ 只有 catalog_bootstrap_test.go;update_health.go 无对应测试文件 |
采纳
- 接受 O4,并同意其定级:这是测试覆盖缺口,不是已证实的实现缺陷(代码在锁阻塞时 fail closed、保留 journal/backup、不强杀)。但缺少故障注入与中断 phase 恢复测试,意味着该状态机的持久化边界行为"构造上正确"而未被测试证明,且后续回归无法拦截。对自更新器(失效即"盒子自己更新不动")值得列高优先级。
- **T-617 追加发现(2026-07-19):**prepared journal 后首次 rename 已完成但 directory sync 失败时,实际 target/backup 拓扑与
phasePrepared的旧恢复假设不一致;Recover不能再无条件删除该 journal。此项是由既有DirectorySyncerseam 可复现的恢复正确性缺陷,与 O4 的测试覆盖缺口不同;T-617 需作最小状态机修复并以故障测试证明。它不推翻原有“无命令注入、不强杀、无越界移动”的安全裁定。 - 接受 O5 对原 O2 的细化:装配前置不应笼统归为"等 T-502"。可信 Catalog 发布配置、签名清单加载/缓存/目标过滤、下载完成文件受控消费可在 T-502 前独立接入;T-502 只决定授权门禁;自更新另缺可信包下载/验签/版本选择/UI 触发器。按四条分别落任务与验收。
- 接受验证段:
go test -count=10对并发/恢复代码是恰当的抗 flakiness 实践;-race因CGO_ENABLED禁用无法运行并如实声明"不得表述为 race detector 已通过",该诚实度正确。
补充:O4 的低成本落地路径(多数无需新增生产 seam)
- sync 失败注入——用现有 seam,零生产改动:
DirectorySyncer本就是注入接口。把testSyncer改为"第 N 次调用返回 error",即可覆盖prepared/target_backed_up/staging_activated/launched/committed各写入点后的 sync 失败及其触发的 rollback/Recover。 - 中断 phase 的 Recover——复制现有模式即可:直接
writeTransaction(layout, phaseX, ...)造出各中间态目录布局后调Recover断言收敛,与TestRecoverRestoresTargetBackedUpTransaction同模式,成本低。 - rename / 写入失败注入——优先用文件系统权限技巧:updater 内部为裸
os.Rename/文件写。无头 Linux 测试可用只读父目录、rename 到非空目录等方式强制失败,不必为此在生产代码加 seam;仅当需要精确控制"哪一步失败"时,再考虑最小 seam。 - 真机边界不变:Windows 文件锁、杀毒软件、物理断电仍只由 T-601 真机/VM 验证,不以单元测试替代。
最终处理顺序(定稿)
- [高优先 · 测试] O1/O4:按上述路径 1→2→3 补齐自更新故障注入与中断 phase 恢复测试,并补 health CLI 参数拒绝的双端测试。
- [装配规划] O2/O5:按"可信 Catalog 发布配置 → 下载/安装/更新编排 → T-502 授权接线 → 自更新发布源与触发器"分别落任务;文档保持"无头 core 已完成、端到端未接通"表述。
- [后续] O3:T-502 接入真实授权策略并补端到端授权测试。
- [真机] T-601:Windows 文件锁 / 杀毒软件 / 断电故障注入,不由单元测试替代。
裁定:Codex 本轮补充(O4 自查、O5 细化、验证段)全部成立;原审核在测试覆盖维度不如本轮严谨,予以采纳。随后发现的 prepared rename+sync 恢复不一致由 T-617 进行最小定向修复;Phase 4 安全总结论(无注入面、不强杀、健康门控、Phase 3 O1 已闭合)仍成立,不需要回滚既有 Phase 4 实现或扩大范围返工。