72 lines
8.3 KiB
Markdown
72 lines
8.3 KiB
Markdown
---
|
|
id: T-612
|
|
title: 在 ZIP 打开前限制包大小与中央目录元数据
|
|
phase: 1
|
|
deps: [T-605]
|
|
status: TODO
|
|
created: 2026-07-18
|
|
issue: null
|
|
context_ref: 320b83d929fde7d51fc5bb3e9fbdaac7beb5101e
|
|
claim_branch: null
|
|
work_branch: agent/codex/T-612
|
|
write_paths:
|
|
- docs/tasks/T-612.md
|
|
- core/installer/
|
|
- docs/api.md
|
|
- docs/04-architecture.md
|
|
- docs/05-coding-rules.md
|
|
- docs/review/phase1-security-review.md
|
|
- docs/00-ai-start-here.md
|
|
- docs/06-tasks.md
|
|
- docs/current-state.md
|
|
---
|
|
|
|
## 问题 / 背景
|
|
|
|
`Extractor.ExtractFile` 当前直接调用 `zip.OpenReader(zipPath)`,而现有 `preflight` 对 `len(archive.File)`、展开大小和压缩比的检查发生在标准库已经读取并解析 ZIP 中央目录之后。攻击者若能让已验证的发布包携带巨大或伪造的中央目录元数据,仍可在协议级检查开始前触发不受本模块限制的元数据读取/分配。根据 `docs/review/phase1-security-review.md` 定稿,这属于“签名内容管线的纵深防御缺口”,不是未签名网络输入可直接利用的路径,但仍阻断 T-302 正式安装整合。
|
|
|
|
T-301 已把 known-total 下载限定为精确长度,并在 final `.download` 发布前核对普通文件身份与实际长度;Catalog `packages[arch].size` 也是签名内容。当前 `Extractor` 只有测试调用方,T-302 尚未把 Catalog、下载完成文件、SHA-256 与解压器编排为生产链路。因此本任务应先把三者对账所需的 installer API 和 fail-closed 边界落实,不得把测试契约误写成生产安装已接线。
|
|
|
|
## 方案
|
|
|
|
1. 扩展 `installer.Limits`,增加 ZIP 原始包大小和中央目录字节数两个显式硬上限;默认原始包上限与 T-301 `DefaultMaxUnknownBytes` 的 4 GiB 上限一致,中央目录采用独立、明显更小的暂定安全上限。`NewExtractor` 必须拒绝零值、负值、溢出或彼此矛盾的配置;现有条目数、展开体积和压缩比硬上限保留。
|
|
2. 把 `Extractor.ExtractFile` 的调用契约改为显式接收已验签 Catalog 的 `expectedPackageSize`。在任何 ZIP 解析前,它必须:
|
|
- 打开并只使用同一个普通文件句柄,以该句柄的真实长度同时核对 `expectedPackageSize`、`MaxArchiveBytes` 与后续 ZIP reader 的 size;不允许先扫描路径再按同一路径重新打开,避免本地替换窗口。
|
|
- 拒绝未知、非正或不匹配的预期大小,以及符号链接/非普通包文件;返回可由 `errors.Is` 识别的稳定 installer 错误,且不创建 staging。
|
|
- 从文件尾部以固定上界搜索 EOCD(最多 ZIP 规范允许的 65,535 字节 comment 加 EOCD 固定字段),不把整个文件或整个中央目录读入内存。校验单磁盘语义、EOCD 位置、中央目录 offset/size/entries 的无溢出区间关系和中央目录必须在 EOCD 前结束。
|
|
- 支持并严格校验 ZIP64 locator/ZIP64 EOCD 的对应字段;不因合法 ZIP64 仅因 32 位 EOCD 哨兵值而误拒绝。无法完整证明一致性的 EOCD、跨盘 archive、截断、伪造 offset 或不支持的结构一律按无效 archive 拒绝。
|
|
- 在构造 `zip.Reader` 前以 EOCD 声明条目数和中央目录字节数分别执行 `MaxEntries` 与 `MaxCentralDirectoryBytes` 检查;再使用已经扫描的同一打开句柄和受核对的实际长度创建 `zip.NewReader`。现有完整 `preflight` 必须保留,作为对标准库解析结果、ZIP entry 语义和展开数据的第二道检查。
|
|
3. 冻结错误及 API 文档:大小不一致、原始包过大、中央目录过大、声明条目过多和格式错误要有可测试的 stable sentinel/错误链;调用方只能将来自已验签 Catalog 的 exact `size` 传入。T-302 必须重新取得 Catalog 身份并把已完成 `.download` 的实际长度、Catalog `size` 与本 API 的预扫描结果串成同一条安装链。
|
|
4. 添加攻击回归,至少覆盖:正常经典 ZIP;真实包大小与 expected size 不一致;原始文件超限;经典 EOCD 伪造 entries 或 central-directory size;越界/截断/跨盘 EOCD;ZIP64 成功路径及不一致 locator/record;以及被预扫描拒绝时 destination 不存在。测试要断言错误发生在 `zip.NewReader`/旧 `preflight` 之前能够确定的 sentinel 上,而非退化为“解析后才失败”。保留并回归现有路径逃逸、压缩比、展开量、CRC 和 staging 清理测试。
|
|
5. 同步 API、架构和编码规则:把“先对账已验证 Catalog size、已完成普通下载文件长度,再作有界 EOCD/中央目录预扫描,最后才构造 ZIP reader”列为安装安全顺序;明确限额仍是暂定值,T-302 按真实包分布复核。Phase 1 审核记录和项目当前状态必须如实标记该阻断项已关闭或仍待 T-302 接线。
|
|
|
|
## 验收要点
|
|
|
|
- `Limits` 同时包含并验证 `MaxArchiveBytes`、`MaxCentralDirectoryBytes`、现有 `MaxEntries`、展开大小与压缩比;默认原始包上限与 T-301 4 GiB unknown-total 限制一致,中央目录限制独立且不允许绕过。
|
|
- `ExtractFile` 在任何 `zip.Reader` 构造前,以同一普通文件句柄验证 expected Catalog size 与真实文件长度相等、且不超原始包上限;不匹配/未知/非普通输入 fail closed,不创建 destination。
|
|
- EOCD 预扫描只读取固定上界尾部和 ZIP64 所需固定记录,不分配与 archive 长度或声明目录大小成比例的内存;经典与 ZIP64 的 entries、offset、size、磁盘号、边界和溢出均被验证。
|
|
- EOCD 声明 entries 超 `MaxEntries` 或中央目录超 `MaxCentralDirectoryBytes` 时,返回对应稳定错误且不进入标准库中央目录解析;损坏、截断、跨盘或不一致的 ZIP64 结构返回 `ErrInvalidArchive` 错误链。
|
|
- 有效的经典 ZIP 和 ZIP64 元数据路径仍能完成现有安全提取;现有路径、entrypoint、类型、重复名、CRC、展开体积和压缩比防线不回退。
|
|
- `docs/api.md`、`04-architecture.md`、`05-coding-rules.md` 和审核记录准确写明 size 三方对账、同句柄预扫描、ZIP64 策略、临时限额与“生产 T-302 接线尚未完成”的边界。
|
|
- Go 1.20.14 + `GOWORK=off` 下 `go vet ./installer`、`go test -count=10 ./installer` 通过;完整 `./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py`、`python scripts/validate_harness_governance.py` 与提交前差异检查通过。
|
|
|
|
## 边界(不改什么)
|
|
|
|
- 不实现 T-302 的下载→SHA-256→Catalog 身份→app.json→安装编排,不把 `core/downloader` 或 `cmd` 误改成已有生产调用方;本任务只提供 T-302 必须使用的 Extractor 契约和测试证据。
|
|
- 不改 Catalog 签名、SHA-256、下载队列协议、manifest/schema 或 package 格式;不从 ZIP 内部字段放宽 Catalog 的预期大小。
|
|
- 不移除既有中央目录语义 `preflight`,不降低 Windows 安全路径、entrypoint、压缩比、展开量、CRC、全新 staging 或失败清理防线。
|
|
- 不在本任务实现 payload 文件 `Sync()`、目录/journal 的断电耐久顺序或 Windows VM 故障注入;这是审核顺序下一项的独立任务。
|
|
- 不引入第三方 ZIP 解析库,不读取完整 archive/中央目录到缓冲区,不改 Go/Gio 版本、workspace、UI、用户的 `soft_quay.code-workspace` 或无关任务。
|
|
|
|
## 协作约束
|
|
|
|
- 按仓库当前规则由单 Agent 串行执行,不启动子 Agent。
|
|
- 本任务只允许修改 frontmatter 中的 `write_paths`;若需要生产安装编排、下载队列协议变化、SHA-256 再校验或断电耐久实现,必须停止并另立任务,不得在 T-612 内扩边。
|
|
- T-612 完成、完整验证并提交前,不落成或领取下一项 Phase 1 耐久性整改或 T-302。
|
|
|
|
## 执行记录
|
|
|
|
- 2026-07-18:根据 `docs/review/phase1-security-review.md` 最终处理顺序第 2 项落成任务;现有全局最大任务为 T-611,因此取 T-612,依赖已完成的 T-605。
|
|
- 2026-07-18:代码图确认 `ExtractFile` 直接调用 `zip.OpenReader`,随后才在 `preflight` 用 `len(archive.File)` 检查条目数;其调用方目前均为 installer 测试。T-301 已对 known-total 下载以已验签 Catalog size 严格限长并发布普通 final 文件,但尚无生产安装编排。因此本任务通过显式 `expectedPackageSize` 和同句柄预扫描建立正确接口,不宣称已完成 T-302 接线。
|
|
- 2026-07-18:默认 `MaxArchiveBytes` 将与 downloader `DefaultMaxUnknownBytes` 的 4 GiB 保持一致;中央目录单独限额及 ZIP64 兼容性必须以测试和文档冻结,真实包采样后的阈值复核仍留给 T-302。
|