150 lines
17 KiB
Markdown
150 lines
17 KiB
Markdown
# 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 已修复自更新 prepared rename+sync 后的 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 必须在已记录的 installed `Files` 里**(`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`(Windows `FlushFileBuffers`)做目录耐久。
|
|
- **健康确认前不删 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 验证,不以单元测试替代。
|
|
|
|
**追加状态机发现(已由 T-617 关闭):**`prepared` journal 成功写入后,`app → backups/<request-id>` 的 `os.Rename` 可能已经完成,而 `renameDirectory` 在随后的目录 sync 返回错误。旧 `Update` 直接返回,磁盘成为 target 缺失、managed backup 存在、journal 仍为 `prepared`;旧 `recoverLayout` 又无条件删除 journal。T-617 现按真实 managed target/backup 拓扑恢复,并在首次错误时立即走同一受限路径;恢复包装同时保留 `ErrRecoveryRequired` 与底层 sync/rename 原因。无头测试覆盖即时/下次恢复和恢复 fence 再失败,真实 Windows 锁仍待 T-601。
|
|
|
|
### 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)全部到位,失败码体系完整。建议处理顺序:
|
|
|
|
1. **[已关闭 · T-617]** O1/O4:prepared rename+sync 后 target/backup 与 journal 不一致已修复;已补自更新 health timeout 后结构性阻塞再 Recover、五 transaction phase、journal/rename/sync/cleanup 失败注入和双端 health 参数拒绝测试。真实 Windows 锁语义仍归 T-601。
|
|
2. **[准确性 · 装配规划]** O2/O5:文档保持"无头 core 已完成、端到端未接通"表述;可信 Catalog/下载消费、T-502 授权和自更新发布源分别落地,不笼统等待单一任务。
|
|
3. **[后续]** O3:T-502 接入真实授权策略并补端到端授权测试。
|
|
|
|
> 声明:本报告为静态审计与攻击路径推演,逐项核了实现(launch 无参数 `exec.Command` + `ShellExecuteExW` 无 parameters、`containsEntrypoint` + safepath + Lstat、update 的 WaitForExit 不强杀、updater 的 journal/rollback/健康门控、switcher `preSwitchCheck`);因 WSL 无 Go 未执行测试,双 workspace 闸门通过为 Codex 自述。O1 的 Windows 文件锁语义需真机验证。
|
|
|
|
## 交叉复核裁定(定稿)
|
|
|
|
> 复核视角:Claude 全栈开发工程师,对 Codex 补充复核(O4/O5 + 验证段)逐条核验后裁定。
|
|
> 核验方式:枚举 `core/updater/*_test.go` 全部测试函数;检查故障注入 seam、各 transaction phase 的 `Recover` 覆盖、`cmd/softbox` health 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。T-617 已以最小状态机修复和 DirectorySyncer 故障测试关闭此恢复正确性缺陷;它不推翻原有“无命令注入、不强杀、无越界移动”的安全裁定。
|
|
- **接受 O5** 对原 O2 的细化:装配前置不应笼统归为"等 T-502"。可信 Catalog 发布配置、签名清单加载/缓存/目标过滤、下载完成文件受控消费可在 T-502 前独立接入;T-502 只决定授权门禁;自更新另缺可信包下载/验签/版本选择/UI 触发器。按四条分别落任务与验收。
|
|
- **接受验证段**:`go test -count=10` 对并发/恢复代码是恰当的抗 flakiness 实践;`-race` 因 `CGO_ENABLED` 禁用无法运行并如实声明"不得表述为 race detector 已通过",该诚实度正确。
|
|
|
|
### 补充:O4 的低成本落地路径(多数无需新增生产 seam)
|
|
|
|
1. **sync 失败注入——用现有 seam,零生产改动**:`DirectorySyncer` 本就是注入接口。把 `testSyncer` 改为"第 N 次调用返回 error",即可覆盖 `prepared`/`target_backed_up`/`staging_activated`/`launched`/`committed` 各写入点后的 sync 失败及其触发的 rollback/Recover。
|
|
2. **中断 phase 的 Recover——复制现有模式即可**:直接 `writeTransaction(layout, phaseX, ...)` 造出各中间态目录布局后调 `Recover` 断言收敛,与 `TestRecoverRestoresTargetBackedUpTransaction` 同模式,成本低。
|
|
3. **rename / 写入失败注入——优先用文件系统权限技巧**:updater 内部为裸 `os.Rename`/文件写。无头 Linux 测试可用只读父目录、rename 到非空目录等方式强制失败,**不必为此在生产代码加 seam**;仅当需要精确控制"哪一步失败"时,再考虑最小 seam。
|
|
4. **真机边界不变**:Windows 文件锁、杀毒软件、物理断电仍只由 T-601 真机/VM 验证,不以单元测试替代。
|
|
|
|
### 最终处理顺序(定稿)
|
|
|
|
1. **[高优先 · 测试]** O1/O4:按上述路径 1→2→3 补齐自更新故障注入与中断 phase 恢复测试,并补 health CLI 参数拒绝的双端测试。
|
|
2. **[装配规划]** O2/O5:按"可信 Catalog 发布配置 → 下载/安装/更新编排 → T-502 授权接线 → 自更新发布源与触发器"分别落任务;文档保持"无头 core 已完成、端到端未接通"表述。
|
|
3. **[后续]** O3:T-502 接入真实授权策略并补端到端授权测试。
|
|
4. **[真机]** T-601:Windows 文件锁 / 杀毒软件 / 断电故障注入,不由单元测试替代。
|
|
|
|
> 裁定:Codex 本轮补充(O4 自查、O5 细化、验证段)全部成立;原审核在测试覆盖维度不如本轮严谨,予以采纳。随后发现的 prepared rename+sync 恢复不一致已由 T-617 最小定向修复;Phase 4 安全总结论(无注入面、不强杀、健康门控、Phase 3 O1 已闭合)仍成立,不需要回滚既有 Phase 4 实现或扩大范围返工。
|