ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Roc 归档路径安全校验:为 bundle 与 unbundle 收敛出一套共享规则集

Roc 归档路径安全校验:为 bundle 与 unbundle 收敛出一套共享规则集 Roc 归档路径安全校验为 bundle 与 unbundle 收敛出一套共享规则集【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc本文基于 Roc 语言项目GitHub_Trending/ro/roc中的改进提案 projects/small/bundle-unbundle-shared-path-rules.md讲解roc编译器的打包层bundle归档写入端与解包层unbundle归档读取端各自维护一份归档路径安全规则所造成的安全校验器漂移问题。读完本文你将理解 tar 归档路径校验在打包/解包两端为什么必须保持写端 ⊆ 读端的包含关系、Roc 仓库中两套同名校验函数已经出现哪些具体分歧以及提案给出的归一化设计方案、成功判据与配套测试策略。背景为什么归档路径校验是一个安全相关的校验器roc bundle会把一个 Roc 包及其全部依赖压缩成 zstd 压缩的 tar 归档.tar.zst文件扩展名与流式缓冲区大小这两个磁盘格式常量定义在 src/unbundle/format.zig/// File extension for a bundled Roc package: a zstd-compressed tar archive. pub const TAR_EXTENSION .tar.zst; /// Size of the buffer used for streaming bundle/unbundle operations, in bytes. pub const STREAM_BUFFER_SIZE: usize 64 * 1024;src/unbundle是更低层、兼容 wasm 的模块src/bundle是依赖它的高层写入端。归档中的文件路径在提取时会映射到宿主文件系统因此路径规则本质上是一组安全相关的检查目录穿越..、绝对路径、Windows 保留设备名CON、NUL、COM1…、保留字符:*?|等任何一个漏洞都可能让恶意构造的归档在解包时写出目标目录之外的文件。问题的核心是写入端和读取端各维护了一份这套规则的副本而且两份副本已经互相不一致。对于这样一个安全校验器写入端与读取端可以独立漂移而仓库中没有任何交叉检查机制能发现这种漂移。问题两套同名校验函数的具体分歧提案逐条列出的分歧均可在当前源码中逐一对应验证。两个pathHasUnbundleErr同名不同义两个模块各有一个公开函数pathHasUnbundleErr但检查集不同unbundle 侧src/unbundle/unbundle.zig#L296-L397单趟完成全部检查——空路径、超长、绝对路径、./..组件、Windows 保留名、组件以空格/句点结尾、保留字符含 Unix 下的反斜杠。bundle 侧src/bundle/bundle.zig#L415-L483一个更小、不同的检查集——只做空路径、超长对照命名常量TAR_PATH_MAX_LENGTH、绝对路径、./..组件保留名与保留字符检查被拆在另一个函数pathHasBundleErr里src/bundle/bundle.zig#L345-L411。更微妙的是pathHasBundleErr开头的注释声称它会执行wed do on unbundle的校验然后调用的是bundle 自己的本地版本这个较弱的函数src/bundle/bundle.zig#L348-L352// Start by doing the validation checks wed do on unbundle. // If unbundling would fail, then bundling should too! if (pathHasUnbundleErr(path)) |err| { return err; }从源码结构看这里的意图是unbundle 会拒绝的路径 bundle 也必须拒绝但实际调用的函数并不覆盖 unbundle 的全部规则——注释与实现已经脱节。逐条对照当前仓库中的分歧清单分歧项bundle 侧写入端unbundle 侧读取端WINDOWS_RESERVED_NAMES22 个条目逐字重复一份bundle.zig#L302-L309逐字重复一份unbundle.zig#L286-L293路径最大长度命名常量TAR_PATH_MAX_LENGTH: usize 255bundle.zig#L33裸字面量255unbundle.zig#L304PathValidationReason10 个变体声明一份bundle.zig#L312-L323声明一份unbundle.zig#L51-L62PathValidationError、ErrorContext各声明一次bundle.zig#L326-L329、bundle.zig#L107-L110各声明一次unbundle.zig#L279-L282、unbundle.zig#L45-L48UnbundleError错误集13 个成员bundle.zig#L91-L10419 个成员unbundle.zig#L22-L42PathValidationReason的 10 个变体在两侧逐字相同包括携带载荷的windows_reserved_char: u8pub const PathValidationReason union(enum) { empty_path, path_too_long, windows_reserved_char: u8, absolute_path, path_traversal, current_directory_reference, windows_reserved_name, contained_backslash_on_unix, component_ends_with_space, component_ends_with_period, };编码方式分歧反斜杠规则只在一侧存在同一保留字符规则在两侧用了两种编码且内容不一致bundle字符数组 inline for循环逐字节比对bundle.zig#L290-L299、bundle.zig#L355-L364。数组为0 : * ? |共 8 个字符不含反斜杠unbundleinlineswitch直接分支处理unbundle.zig#L374-L394。同样的 8 个字符之外还多了一条分支——在非 Windows 平台上遇到\返回contained_backslash_on_unix\\ { if (builtin.os.tag ! .windows) { return PathValidationError{ .path path, .reason .contained_backslash_on_unix, }; } },由此形成一个真实的接受/拒绝不对称Unix 上形如foo\bar的路径在 unbundle 侧会被contained_backslash_on_unix拒绝而在 bundle 侧pathHasUnbundleErr的组件扫描把\当作分隔符之一处理bundle.zig#L446pathHasBundleErr的保留字符数组又不含\唯一的兜底是 bundle.zig#L346 的std.debug.assert(std.mem.find(u8, path, \\) null)——该断言只在调试构建中生效发布构建中会被编译掉。从源码结构看可以推断存在这样的路径形状bundle 接受、unbundle 拒绝恰好违反了这个模块应当保证的方向。绝对路径判断的编码同样不同bundle 侧调用标准库std.fs.path.isAbsolutePosix(path) or std.fs.path.isAbsoluteWindows(path)bundle.zig#L433unbundle 侧手写为首字符是/或\加上长度 ≥2 且第二个字符是:unbundle.zig#L311-L323。后者把a:b这类任意单字符 冒号都视为绝对路径两侧在驱动字母大小写等边缘形状上的判定口径不同具体是否分歧取决于标准库isAbsoluteWindows的判定细则这正是提案要求合并时逐条对账并留下注释的原因。没有任何交叉检查提案特别指出两个目录之间没有共享规则模块也没有任何测试断言bundle 接受的每条路径 unbundle 也接受——即上面列出的每一处漂移仓库现状都无从发现。已有的共享层合并方向已经存在提案的背景部分强调合并的方向并不需要新立范式因为这两个目录已经是一个带共享层的依赖对src/unbundle/format.zig持有TAR_EXTENSION与STREAM_BUFFER_SIZEbundle.zig通过const format import(unbundle).format;bundle.zig#L23引用它们并直接导出STREAM_BUFFER_SIZE format.STREAM_BUFFER_SIZE、TAR_EXTENSION format.TAR_EXTENSIONbundle.zig#L35-L36。unbundle 是更底层、wasm 兼容的一侧bundle 依赖它。因此共享常量放 unbundle、bundle 导入这一整合方向已经确立——路径规则只是当时没有跟着搬过去的那部分。解决方案设计把单一规则集放进 unbundle提案给出的方案分四步其核心是不变式是读取端永远不得比写入端更严格reader may never be stricter than the writer即写端接受的集合必须是读端接受集合的子集writer ⊆ reader。第 1 步规则集整体迁入 unbundle将唯一一份规则集移入src/unbundle放入format.zig或新建path_validation.zig内容包括WINDOWS_RESERVED_NAMESTAR_PATH_MAX_LENGTH保留字符谓词reserved-character predicatePathValidationReason、PathValidationError、ErrorContext一个pathHasUnbundleErr。第 2 步bundle 导入并删除本地副本bundle全部从共享层导入并删除自己的本地副本。仅当某些检查经审查确认真正只属于写入时刻时才留在bundle.zig——而且必须写成共享校验器之上的增量附加而不是再开一个平行校验器。第 3 步合并时逐条对账现存分歧对账对象就是前文列出的三处实质分歧反斜杠规则bundle 的字符数组不含\而 unbundle 的 switch 在 Unix 上拒绝\合并后需要一条统一的反斜杠判定bundle 两个本地函数的检查集拆分pathHasUnbundleErr与pathHasBundleErr哪些检查归共享校验器哪些如有保留为写端增量两侧错误集成员归属bundle 侧 13 个成员与 unbundle 侧 19 个成员的UnbundleError确定每一侧实际需要哪些。对账的纪律是每一处差异要么消失要么留下注释说明为什么写入端比读取端更严格。读取端更严格则是必须消除的缺陷因为它意味着会出现我们自己打包出来的归档我们自己拒绝解包的情况。第 4 步加入 round-trip 属性测试针对一组有效与无效的路径形状语料corpus必须包含上述分歧案例断言bundle 接受 ⇒ unbundle 接受writer ⊆ reader两侧都拒绝所有攻击形状穿越..、绝对路径、保留名、保留字符、超长路径。成功判据全部满足才算完成提案给出的是硬性判据the project is not done until all do可逐条用于验收全仓库只存在一个pathHasUnbundleErr且位于 unbundle 一侧。验收命令继承自提案原文grep -rn fn pathHasUnbundleErr|WINDOWS_RESERVED_NAMES|PathValidationReason src/bundle src/unbundle应显示单一处定义均在 unbundle。不再存在裸255路径长度字面量两侧都使用共享常量TAR_PATH_MAX_LENGTH。writer-⊆-reader 属性测试在仓库内且为绿色覆盖反斜杠与保留名案例。任何有意保留的写端专属严格性都在共享校验器调用点处以代码注释说明原因。正确性与性能的理想形态正确性理想读取端安全规则与写入端是同一段代码因此我们绝不产生自己会拒绝解包的归档由构造保证holds by construction未来任何规则变更一次性原子地落在两侧不存在改了一侧忘了另一侧的窗口。性能理想中性。检查内容不变只是从两份实现变成一份。至于inline for数组编码与switch编码选哪种跟随合并后保留的实现即可——两侧都不是热路径bundling 期间按路径做校验。需要补齐的测试提案列出的测试清单与成功判据呼应writer-⊆-reader 属性语料对有效/无效路径形状语料断言包含关系分歧案例反斜杠、保留名必须入语料攻击形状拒绝的钉死测试在两个入口点bundle 与 unbundle 的校验入口分别固定攻击形状必被拒绝的行为防止任一侧将来悄悄放行PathValidationReason变体覆盖测试用 comptime 枚举方式保证 10 个变体中每一个至少有语料中的一个案例触达避免某个错误原因永远测不到。相关改进提案同一projects/small/目录下cache-and-identity-residuals.md 把相同的消除残留副本、以单一来源定义组合事实的思路应用到缓存边界cache boundary可对照阅读。提案文档全文见 projects/small/bundle-unbundle-shared-path-rules.md涉及的关键源码为 src/bundle/bundle.zig、src/unbundle/unbundle.zig 与 src/unbundle/format.zig。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表