漏洞
7‑Zip NTFS 解析器的一字节缓冲区(CVE-2026-48095)
CVE-2026-48095 让过大的 NTFS 簇指数与压缩属性值在 7‑Zip 中相加,使 32 位位移在已测构建中把输入分配压成 1 字节,随后一枚区段即可送入 256 MiB;所有 26.00 及更早解析器副本都应优先替换为当前官方 26.02,无法直接升级时至少使用 26.01 或经同一源码不变量核验的回移版本。

文章导航
默认处置很明确:找出所有能够接触不可信文件的 7-Zip 可执行文件、库、插件和私有副本,把 26.00 及更早对象替换为当前官方 26.02;暂时无法替换的工作节点停止递归测试与提取,把 NTFS 内容送往已修复的隔离服务。回退目标只能是另一份已核修复对象,不能回到 26.00。
漏洞发生在解压之前。引导扇区给出整卷簇刻度,非驻留属性给出压缩单元刻度;两个值分别通过 26.00 的局部检查,直到 CMftRec::GetStream() 创建 CInStream 才进入同一条 32 位位移。已测 x86/x64 构建把输入容量算成 1 字节,区段读取随后仍按卷刻度请求 256 MiB。
本文固定复核 26.00、首个修复版 26.01 与截至 2026 年 7 月 26 日仍为官方最新版的 26.02。源码能够证明错误算术、读取关系和修复控制流;具体主机是否暴露、崩溃或失陷,仍须由实际加载对象、输入来源、进程状态和解析器后行为分别确认。
1 三项数值解释整条失效链
1.1 卷刻度、压缩刻度与区段读取在同一数据流中相遇
第一步来自引导扇区。26.00 的 CHeader::Parse() 第 122–134 行把扇区指数与每簇扇区指数相加,只拒绝 ClusterSizeLog > 30。公开案例选择 28,一枚簇因此代表 2^28 字节,也就是 256 MiB。
第二步来自 MFT 的非驻留 $DATA 属性。第 513–520 行从属性偏移 0x22 读取 CompressionUnit,第 424–430 行允许 0 或 4。值 4 表示一枚压缩单元包含 16 个簇。
第三步发生在数据流构造与读取。CMftRec::GetStream() 第 1218–1234 行把簇指数写入 BlockSizeLog,再用属性值调用 InitAndSeek()。第 680–697 行执行 (UInt32)1 << (28 + 4);指数等于 32,C++ 对这次 32 位位移没有定义,公开研究所测构建得到 1 并据此分配 _inBuf。随后第 934–947 行把一枚物理簇换算成 256 MiB,直接交给 ReadStream_FALSE() 写入该缓冲区。

GetStream() 让两者相遇,区段读取继续沿卷刻度产生写入长度。一个无害算例能校准单位:512 字节扇区的指数为 9,每簇 8 个扇区再加 3,得到 ClusterSizeLog = 12 与 4 KiB 簇;再加 CompressionUnit = 4,压缩单元指数为 16,容量为 64 KiB。公开案例把第一项改成 28,于是同一结构得到 32 位非法位移,而一枚区段仍保留 256 MiB 的物理读取尺度。
LZNT1 位于这次读取之后。数据必须先经物理区段装入 _inBuf,代码才会调用 Lznt1Dec();压缩令牌是否有效无法保护已经过小的输入对象。x86 会继续遇到过小输出缓存,x64 则先请求 8 GiB 输出缓存;资源不足可使 x64 提前停止,源码中的输入容量/读取长度关系依然失效。
1.2 修复边界与唯一处置图
代码与处置。受影响范围是 7-Zip 26.00 及更早版本。首个修复版 26.01 的提交 8c63d71ff886bda90c86db28466287f977374237 在同一源码第 122–134 行把上限从 30 收紧到 21;截至 2026 年 7 月 26 日,官方当前版本 26.02 的提交 f9d78aff31a5f2521ae7ddbdc97c4a8855808959 仍在第 122–134 行执行该拒绝。等价回移必须证明同一控制流属性:ClusterSizeLog > 21 在 MFT 寻址、CInStream 构造、分配和读取之前退出,而且所有抵达 GetStream() 的路径都继承这份已核头部状态。容量感知的 offs + compressed <= _inBuf capacity 检查属于必要纵深防御,不能用来放行超出上游支持范围的几何。
| 负责人 | 对象 | 动作 | 验收证据 | 失败处理与退出条件 |
|---|---|---|---|---|
| 解析器/桌面负责人 | 所有可执行文件、7z.dll、插件、容器与私有副本 | 优先部署 26.02;受限环境使用 26.01 或已证等价回移 | 实际加载路径、架构、摘要与可信软件包/源码属性相互对应 | 兼容性失败时切到另一组已核修复节点;全部可达副本核验后退出 |
| 接收入口/归档服务负责人 | 偏移 3 命中 8 字节 NTFS 签名的输入及格式含混文件 | 把内容送往已修复隔离服务;暂停易损节点递归测试与提取 | 处理器选择、路由结果、工作节点加载摘要和例外到期日 | 隔离容量不足时限流或停用相关功能;不得回落到 26.00 |
| 验证负责人 | 簇指数 20、21、22 的无害仅头部样本与常规 NTFS 语料 | 确认 20/21 可进入下一有效阶段,22 在头部拒绝,MFT、分配和读取均未发生 | 确切分支记录、无分配/读取探针、常规列出/测试/提取摘要 | 22 抵达下游或正常语料回归即停止推广;修复并重测后退出 |
| 事件响应负责人 | 可疑镜像、父容器、崩溃与解析器后行为 | 先保存原件、逻辑/分配大小、摘要和来源,再关联进程、模块、子进程、文件与网络遥测 | 对象到任务、进程、加载模块和后续行为的可追溯时间线 | 有解析器后行为则按失陷处置;证据边界与留存窗口说明完整后关闭 |
公开时间线固定了版本边界:2026 年 4 月 24 日私下报告,4 月 27 日发布 26.01,5 月 22 日公开 GHSL-2026-140,6 月 5 日发布 CNA 记录,6 月 25 日发布 26.02。CNA 将 26.00 及更早版本列为受影响,CVSS 3.1 为 8.8;最早易损版本和全部下游集成仍未由公开记录穷尽,因此盘点对象必须落到实际二进制文件和运行时加载身份。
证据强度按对象分层。固定源码证明算术、写入关系与修复分支;公开优化版 Windows 调试记录证明一次由可控区段字节抵达受损间接分派的路径;特定资产是否只发生交付、已经执行易损解析,或出现代码执行,需要分别由内容、任务、崩溃和解析器后行为确认。安全回归在拟写入末端超过记录容量时由读取桩中止,无需制造堆破坏。
2 引导扇区为整卷写下同一把刻度
2.1 一个字节要通过多重检查,才会变成指数
CHeader::Parse() 收到第一个扇区的字节数组,但它不会只比对一次魔数就宣布这是 NTFS。函数先要求末尾的 0x55AA 标记,接受预期的跳转指令形式,再检查偏移 3 处 8 字节的 NTFS OEM 标识符。随后,它从偏移 11 的 16 位值推导扇区大小指数,只允许指数 9 至 12,也就是 512 字节到 4096 字节的扇区。
偏移 13 的字节接着描述每个簇含有多少扇区。NTFS 为该字段准备了两种形式:小于等于 0x80 的值被当作 2 的幂次扇区数量,交给 GetLog();高于 0x80 的值采用负幂约定,7-Zip 用 0x100 - v 把它换成指数。无论哪一种,解析器保存的都是指数,不是直接字节数。
卷级数值来自一次加法:ClusterSizeLog = SectorSizeLog + sectorsPerClusterLog。这种表示很高效,后续把簇数乘以簇大小时只需左移;代价是解析器必须理解最终承接每次位移的类型宽度。头部在这里接受的指数不会只用于展示,它会成为整个处理器的算术参数。
26.00 标签只在结果大于 30 时拒绝。这个检查能避免部分辅助函数立刻用 UInt32 执行更大的簇大小位移,却仍把 28、29、30 交给下游。公开案例选择 28,是因为它与受支持的压缩单元指数 4 相加后,在 GetCuSize() 中恰好等于 32。引导判断在本地规则下合法,在完整执行中却不安全。
簇值之后还有许多头部检查:字节 14 至 20 必须全为零,介质字节必须代表固定磁盘,FAT 扇区字段为零,保留字节受约束,64 位扇区数量也有上限。解析器随后计算 NumClusters,读取 $MFT 所在的物理簇,并按各自正数计数或负幂编码推导主文件表记录与索引记录大小。
抵达数据流构造器需要一份足够完整的 NTFS 结构;普通变异往往停在引导记录或第一条文件记录。防守方因此可以先提取少量早期字段,在完整归档操作之前完成几何分诊。
旧上限仍然让簇指数支配许多后续运算。CHeader::ClusterSize() 返回 (UInt32)1 << ClusterSizeLog;物理卷大小来自 NumClusters << ClusterSizeLog;簇位置通过同类位移变成字节偏移;MFT 记录大小还可能把另一个对数加到簇指数;区段验证也会用它乘除已分配大小。
因此,代码审查可以把引导扇区看作一次能力授予:CHeader::Parse() 一旦接受几何,所有下游辅助函数都会默认“一个簇”能在各自整数类型与分配模型中安全表达。成功的解析器闸门必须让输出值满足这些接收端,光满足眼前几个字节的语法还不够。
2.2 256 MiB 簇同时改变偏移、读取、缓存与程序预期
当 ClusterSizeLog = 28,编号 1 的簇从镜像起点后 256 MiB 才开始,两枚簇跨度就是 512 MiB。公开验证利用稀疏文件行为,让逻辑磁盘能够容纳这些遥远位置,又无需在研究者文件系统中占用同等物理空间。现场遇到这类镜像时,逻辑大小与已分配大小应当分别记录。
外层稀疏交付不代表 NTFS 内部被解析的簇也一定稀疏。运行列表仍会告诉 7-Zip 哪些虚拟簇映射到哪些物理簇。DataParseExtents() 还原这些关系,检查连续性与分配,再生成一组 CExtent。稍后 CInStream::Read() 用引导导出的指数把区段位置重新换算成字节偏移与字节长度。
256 MiB 远离受支持 NTFS 的常规尺度。Microsoft 当前 Windows 文档把 2 MiB 列为 Windows 10 1709 及以后版本、Windows Server 2019 及以后版本的最大簇;更早受支持系统的上限为 64 KiB。即使尚未解析压缩属性,声明 256 MiB 簇的卷也已经是强异常。记录时应写成“解析器检测到的几何”,不能声称 Windows 成功挂载过它。
压缩又带来第二套单位。Microsoft 文档用簇解释 NTFS 压缩单元,并对正常产品行为给出上限;在 7-Zip 属性模型中,CompressionUnit = 4 表示 16 个簇。常见 4 KiB 簇下,这就是熟悉的 64 KiB LZNT1 单元;套在特制的 256 MiB 几何上,同一指数会描述理论上的 4 GiB 单元,并把字节数计算推过类型宽度。
公开验证中的读取无需填满整个理论压缩单元。区段循环会逐段处理单元中的物理连续段;一个物理簇已有 256 MiB,所以当 numChunks 为 1,compressed = (size_t)numChunks << BlockSizeLog 就会得到 256 MiB。这个长度已经足以暴露它与 1 字节输入缓冲区的巨大分歧。
明确单位能够避免两类常见误读。CompressionUnit 是以簇为底的指数;BlockSizeLog 已经包含扇区大小与每簇扇区数,表示卷簇的字节指数。易损表达式把这两个来自不同结构的刻度相加,记录大小与 LZNT1 数据块大小没有参与。

这个早期异常适合用轻量预筛器处理:识别 NTFS 签名,解析扇区大小与每簇扇区数编码,在受检算术中得到 ClusterSizeLog,并在遍历 MFT 前隔离大于 21 的值。截断、非法编码、溢出和不受支持几何都应带原因拒绝,同时保留原始字段与推导指数;预筛器不寻址攻击者声明的位置,也不再造完整 NTFS 实现。
解析器内部则不能只记住一个常量。每个指数都要携带单位与安全取值域,每次位移明确左操作数类型,并在窄化或位移前用较宽类型求和;语言位宽与产品内存策略是两道不同闸门,分配容量与后续读取末端还必须形成可断言的关系。
记录大小字段也使用双重编码:正数低字节描述簇数,需要纳入 ClusterSizeLog;负数形式直接描述 2 的幂次字节大小。7-Zip 随后把 MftRecordSizeLog 上限设为 12,并要求它至少等于扇区指数。独立记录这些取值域,可以防止簇修复被机械套到无关的记录规则上。
观测记录必须给数值带上单位与表达式:原始字节、以扇区或字节为底的指数、簇数、字节数,以及实际操作数类型。孤立的“28”没有含义;把这些字段保存在一份不可变尺度记录中,代码复核、运行诊断和事件分析才能共享同一种解释。
取值域证明可以在不运行镜像的情况下完成:枚举受支持扇区指数与两类每簇扇区编码,求出簇指数区间,再叠加压缩单元区间并证明结果始终低于 32;物理偏移、记录大小、区段总量、缓存数量和分配类型也逐项标出最窄宽度。格式取值域或接收端类型一旦改变,CI 中的旧不等式应立即失败,迫使维护者重新完成证明。
3 字段交接把卷状态带入分配器
第一章已经固定输入、交接、分配和读取的源码行;本章沿对象生命周期展开这条路径,说明一份局部自洽的 NTFS 结构如何把两个分别受检的值送进同一数据流。
3.1 属性解析器只持有压缩元数据
引导扇区只为整卷解析一次,文件内容的描述随后出现在 MFT 记录中,每条记录又含有一系列带类型的属性。CAttr::Parse() 从属性类型与长度开始,检查 8 字节对齐,读取驻留标志,验证可选的 UTF-16 名称,再区分驻留与非驻留布局。本案所需的压缩值只存在于非驻留形式。
非驻留属性不把文件字节直接放在记录内。它描述虚拟簇编号、已分配大小、逻辑大小、已初始化大小、运行列表偏移,必要时还有打包大小。在偏移 0x22,7-Zip 读取一个字节并保存到 CompressionUnit。解析步骤会确认头部足够长,但此时尚未判断带着这个值能否安全创建数据流。
后续判定函数 IsCompressionUnitSupported() 只接受 0 或 4。0 在该实现中对应未压缩/稀疏处理路径,4 是公开研究采用的 NTFS 压缩数据流情况。检查把属性值限制在一个很小的已知集合;函数参数中没有 ClusterSizeLog,因此无法校验两项指数之和。
属性解析器掌握本地布局与压缩字段,卷头掌握簇尺度。两处局部验证都可成功,组合值随后违反机器级位移约束。参与跨结构算术的字段需要在结构相遇时执行组合检查。
多个属性可以共同表示一条命名数据流。CMftRec 把条目归入 CDataRef 范围,按虚拟簇起点排序非驻留分段,并保留每段运行列表数据。数据流构造器统计非驻留条目数量并要求同组成员一致;这些结构检查帮助特制镜像深入解析器,组合指数仍未受限。
DataParseExtents() 再把运行列表变成虚拟至物理映射。它检查最后一枚虚拟簇是否与已分配大小对齐,分配是否是簇大小的整倍数,分段是否按序衔接,物理位置是否落在卷内;还会根据非空区段重算打包大小。镜像必须先讲出一段自洽的存储故事,才有机会进入不安全缓冲区。
这个过程给检测带来一个重要区分:文件可以在某个实现最敏感的语义层上有害,同时保持足够的内部一致,连续通过许多格式检查。只把镜像叫作“损坏文件”会丢掉真正有用的特征——有效处理器签名、26.00 能接受的头部字段、可解析的 MFT 记录、受支持的压缩值,以及最终导向超大物理读取的区段映射。
它也解释了扩展名过滤器为何无法提供充分保证。危险值位于卷和属性结构内部,给外层文件改名不会改变这些字段;若容器网关只相信发送方声明的 MIME 或只看文件名,甚至不会检查它们。已选解析器与推导出的字段比外层标签更接近真实风险。
3.2 数据流构造是不安全加法第一次落地的会合点
CMftRec::GetStream() 完成决定性交接。它从卷头参数接收 clusterSizeLog,再在文件记录内找到目标数据引用组。若数据驻留且只有一段,函数可以直接暴露内存缓冲区;只要存在非驻留部分或同组有多个分段,它就会进入创建 CInStream 的路径。
函数先要求组内每一项都是非驻留,并确认第一项属性的压缩单元受支持;随后新建 CInStream,用同一个簇指数重建区段,复制逻辑大小与已初始化大小,保留底层镜像数据流,设置 ss->BlockSizeLog = clusterSizeLog。走到这里,它才调用 ss->InitAndSeek(attr0.CompressionUnit)。
一行赋值把卷层判断带入新对象,而对象其余配置来自文件层属性。公开案例里 BlockSizeLog 是 28,调用参数是 4,两者在交接中都没有转换。这个承担构造职责的方法保存第二个值,立刻计算 _chunkSizeLog = BlockSizeLog + CompressionUnit。
类布局展示了这里汇聚的职责:CInStream 跟踪虚拟/物理位置、稀疏模式状态、当前剩余字节、输入/输出缓冲区、源数据流、已还原区段、两枚缓存标记、逻辑文件大小、已初始化大小、块指数与压缩指数。它同时扮演区段读取器、解压缓存和交给归档框架的 IInStream。
GetCuSize() 很短:return (UInt32)1 << (BlockSizeLog + CompressionUnit);。转型把左操作数固定为 32 位,平台指针宽度不会参与这次表达式。x64 的输入分配因此与 x86 一样执行未定义位移。
CMftRec::GetStream() 把这次跨结构交接写得很清楚:它将卷中已经验证的 clusterSizeLog 复制到 CInStream::BlockSizeLog,挂接还原后的区段和源接口,再以属性中的 attr0.CompressionUnit 调用 InitAndSeek()。两项操作数都能沿真实赋值和函数参数追溯到磁盘字节,不必借助分配器症状来猜它们在哪里相遇。
分配完成后,方法清空两枚缓存标记,重置稀疏与位置状态,查看第一条区段,用 e.Phy << BlockSizeLog 把物理簇换成字节位置,再寻址源数据流。成功返回会让 GetStream() 认定对象已经就绪;公开的 IInStream 接口不携带内部输入容量,归档层看不到它与未来区段长度的分歧。
所以错误状态在第一次读取之前已经定型:目标对象已存在,输入分配已定大小,源位置已选中,调用者也持有数据流接口;解压仍在下游。安全验证测试框架可以停在这里,对照已记录指数、返回的容量、输出缓存申请与第一条区段尺度,不允许任何字节进入声明容量之外。
代码复核沿 clusterSizeLog 参数、CompressionUnit 字段、区段向量和源接口的所有权追踪真实交接。补丁证明需要覆盖两项关系:32 位位移指数始终小于 32,每次写入 _inBuf 的累计末端始终落在记录容量内。
当前对象记住缓冲区指针与多个指数,读取调用却没有收到明确输入缓冲区容量。Alloc() 执行时容量信息真实存在,之后却退化为隐式假设。若把容量作为字段或类跨度视图一起传递,生产端就能在本地执行约束,未来几何改动也不必完全依赖远处的头部检查。
构造还必须保持事务式所有权。CMyComPtr、区段容器与字节缓冲区只有在解析、预留和寻址全部成功后才向调用者发布;任一步失败都保持 *destStream 为空,清除缓存标记、源指针和游标,并让重试从干净状态开始。故障注入分别核对引用计数、单次回收和对象可见性,避免安全补丁在异常路径留下残余接口。
头部拒绝、不受支持几何、容量错误、分配失败、取消和源损坏还要以不同内部原因传回归档调用者,不能改走半初始化数据流或未经检查的第二入口。即使界面最后只显示简短错误,列出、测试和提取三条入口都应在失败后恢复文件位置、缓存状态和引用所有权。
4 位移 32 次,把一个压缩单元折成 1 字节
4.1 在分配器看见结果之前,语言已经停止定义它
易损表达式短得足以在一份很长的解析器中被忽略:(UInt32)1 << (BlockSizeLog + CompressionUnit)。它表达的数学意图很直白:压缩单元包含 2^CompressionUnit 个簇,每个簇含 2^BlockSizeLog 字节,把指数相加后左移 1,就应得到所需字节容量。
这个恒等关系只有落在所选机器类型可表示的取值域内才成立。C++ 位移规则考察经整型提升后的左操作数;这里它被明确写成 UInt32,在相关构建中有 32 个值位。右操作数若为负,或大于等于左操作数宽度,行为未定义。28 加 4 正好抵达第一个被排除的值。
未定义行为不保证会回绕成 0、1 或其他固定结果。编译器可能发出由处理器掩码位移计数的指令,可能在优化时假定非法情况永不会出现,也可能因周边代码变化产生新结果。公开研究只记录所测优化后 x86 与 x64 构建的真实表现:指令层面的有效位移计数发生环绕,表达式返回 1。
观察到的 1 直接进入 UInt32 cuSize,再交给 _inBuf.Alloc(cuSize)。后面没有最小大小修正、乘法或容量检查;输入缓冲区从此相信完整压缩单元只有 1 字节。错误把一个巨大的逻辑单元变成极小物理对象,却没有同步改变即将向它写入数据的区段元数据。
Clang 未定义行为检查器可以在算式原位指出指数。公开公告记录的所测源码在 NtfsHandler.cpp:687 出现诊断。这个信号特别有价值,因为它在堆布局或操作系统行为把症状复杂化之前,就指向第一项非法操作。发布版的崩溃也许发生得很晚,UBSan 构建会把根因钉在算术起点。
CNA 同时把问题映射为 CWE-190 与 CWE-787。前者描述导致分配过小的不安全数值行为,后者描述随后越过分配范围的写入。把它们视为前后相接的两个阶段,能够扩大复核覆盖率:数值运行时检查器捕捉破损指数,地址运行时检查器与容量断言捕捉生产端与接收端错配。
只检查结果是否非零没有帮助,1 当然非零,却小得灾难性;只检查分配是否成功还会把信号倒过来,极小请求最容易成功。真正有用的条件是关系式:GetCuSize() 返回的容量必须覆盖已验证字段允许的最大压缩单元字节数,随后每次区段读取还要在当前偏移下落入该容量。
受检实现可以直接表达意图,无需依赖优化器的偶然表现。代码可以在位移前将指数与 std::numeric_limits<UInt32>::digits 比较,可以选用具有明确上限的更宽类型,也可以把已计算的读取末端与已分配缓冲区比较。上游选择更早的格式检查,同时排除不受支持的 NTFS 几何;下游无论采用哪种形式,都要用类型和数值域给出证明。
4.2 x86 与 x64 从同一份 1 字节输入请求走向不同岔路
InitAndSeek() 在 _inBuf 之后立刻做第二次分配。输出缓存保存两份已解压压缩单元,因为 kNumCacheChunksLog 为 1,kNumCacheChunks 是值为 2 的 size_t 表达式。请求大小为 kNumCacheChunks << _chunkSizeLog;指数依旧是 32,左操作数位宽这次跟随平台的 size_t。
32 位构建中,第二次位移同样越过操作数位宽。公开调试记录观察到相同的位移计数掩码行为,输出分配变成 2 字节。两个极小分配都能完成,初始化继续,数据流在没有资源关口的情况下走向区段读取。公告把所测 32 位路径描述为:特制结构一旦被接受,就会必然抵达溢出。
64 位构建中,把 size_t 数值 2 左移 32 是有定义操作,结果为 8,589,934,592 字节,也就是 8 GiB;输入计算仍然是 32 位未定义位移,并在所测二进制文件中得到 1。于是出现极端的一对请求:压缩输入要 1 字节,两格解压缓存要 8 GiB。
调用顺序决定现场可见结果。输入缓冲区先分配,超大输出分配紧跟其后,区段读取尚未发生。分配器拒绝 8 GiB 时,7-Zip 抛出分配失败并停止,可用性受到影响,输入越界写还没有开始;请求成功时,初始化继续,同一块 1 字节输入对象才进入读取路径。
这里的“成功”是一项环境观察,由地址空间可用性、进程上限、分配器策略、页面文件策略、内存压力、安全产品与超额分配行为共同决定。公开调试记录在 64 GiB 机器上确认该路径,并把内存充裕系统视为现实候选;调查结论以实际分配结果与提交状态为准,已安装内存只是一项输入。
这条分岔能够解释同一资产群中貌似不一致的遥测:32 位归档工作节点可能在写入或间接调用附近崩溃;提交上限较紧的 64 位桌面版可能只报内存不足;高容量分析服务可以预留输出缓存,随后遭遇堆越界写。它们都是同一输入与同一源缺陷的不同停止点。
| 构建属性 | _inBuf 表达式 | _outBuf 表达式 | 读取前的已观察关卡 |
|---|---|---|---|
x86,32 位 size_t | 32 位数左移 32;观察结果 1 | 32 位数左移 32;观察结果 2 | 两个极小分配均完成 |
x64,64 位 size_t | 仍为 32 位位移;观察结果 1 | 64 位 2 << 32;有定义结果 8 GiB | 读取前必须成功预留大缓存 |
| 其他编译器或优化 | 未定义输入表达式可能表现不同,不存在可移植的固定塌缩值 | 即使症状改变,源仍不安全 | |
崩溃分组应保留这条分支信息。收集进程架构、可执行文件或模块摘要、已知编译器来源、异常或分配状态、申请大小、提交峰值、页面文件状态,以及最后完成的函数。若只按最终异常代码分组,同一批事件会被拆进“内存不足”“访问违例”“非法控制转移”三个桶。

回归矩阵把架构、优化级别和编译器分开记录,并为两条外观相似的表达式标出提升后的左操作数类型、指数范围、目标类型与下游用途。测试不得用无限精度整数或 64 位调试器表达式替代 UInt32;每一行都保存 sizeof(UInt32)、sizeof(size_t)、优化设置和实际结果。
反汇编或中间表示可以解释所测构建为什么得到 1 和 2,却不能把未定义源码变成安全源码。UBSan 证明位移规则被违反,ASan 观察越界,优化发布版说明部署行为;三者必须绑定同一源码与样本,并明确各自回答的问题。
8 GiB 请求描述两格缓存乘以理论 4 GiB 单元,与提取文件的可见大小属于不同量。遥测要区分申请、地址空间保留、已提交后备存储与驻留工作集,并保存分配器结果、进程限额及首次源读取时间;申请事件可以在驻留工作集增长之前失败。
修复判据不依赖任何未定义结果:无论架构、编译器、分配器或可用内存,指数 22 都必须在 GetCuSize() 和两次分配之前被拒绝。只要测试抵达位移或分配,即使最终无害退出,也说明补丁属性缺失;1 字节和 8 GiB 只是已知构建的症状,不是验收条件。
5 第一轮磁盘读取先到,LZNT1 还来不及反对
5.1 一次缓存未命中把还原后的区段变成写入长度
归档调用者开始读取提取文件时,CInStream::Read() 先处理普通数据流语义:到达逻辑大小就返回文件末尾,把申请的数量缩到剩余已初始化数据内,并对 InitializedSize 之后的范围填零。这些检查保护调用者目标与文件逻辑范围,却没有描述内部压缩缓冲区。
方法随后用 _virtPos >> _chunkSizeLog 计算缓存标记。两格输出缓存中若已有对应压缩单元,它就把所需片段复制给调用者并返回。易损路径从缓存未命中开始,此时对象必须根据物理区段组装压缩单元。
CompressionUnit 决定单元包含多少虚拟簇。代码先把当前位置向下对齐到单元起点,在区段列表上进行二分查找,再扫描范围内是否出现空白区段。压缩单元中夹有空白,表示数据采用压缩形式;整个单元都没有物理数据时,按稀疏零值处理;只要存在物理数据,就进入输入缓冲区路径。
数据流逐条走过对齐后单元内的物理连续段。它利用物理簇与 BlockSizeLog 算出源字节偏移,必要时寻址,统计连续簇数,把范围截在压缩单元末端内,再通过 compressed = (size_t)numChunks << BlockSizeLog 把数量换成字节。
当一枚簇的指数为 28,相关平台上的 compressed 就是 256 MiB。目标参数为 _inBuf + offs,其中 offs 从 0 开始,每完成一段连续段就继续增长。ReadStream_FALSE() 接收计算出的长度,却没有任何比较去确认 offs + compressed 小于最初交给 _inBuf.Alloc() 的容量。
源长度与目标容量由相关字段经两条不同表达式推导。读取长度采用平台宽度的 size_t 左移 28,运算定义明确;分配采用 32 位数左移 32,在所测二进制文件中得到 1。两条机器指令都能按各自代码生成完成,结果却相差 268,435,455 字节。
ReadStream_FALSE() 是一个“完整读取”包装函数。它反复调用源数据流,按已处理字节数向前移动目标,从剩余请求中减去相同数量,只有完整交付才返回成功。通用约定默认调用者已提供足够可写内存;辅助函数既看不到分配对象,也没有容量参数,因此无从检查 _inBuf + offs 是否仍有足够空间。
通用数据流层与 Windows 文件层可能把大请求拆成更小操作。固定标签源码中的 StreamUtils.cpp 按已处理字节数循环,FileIO.cpp 在调用 ReadFile 前还会限制 Windows 单次文件读取。公开调试会话在所测发布路径看到 64 KiB 迭代;具体分段读取大小会随包装函数与平台改变,但第一块成功写入的数据早已超过 1 字节。
只有所有物理连续段都复制完,代码才会调用 Lznt1Dec()。它把 _inBuf 与累计 offs 当作压缩输入,选择一格输出缓存并写入缓存标签。畸形压缩令牌可能在这里被拒绝,可堆已经接收区段字节;强化解压器无法修复更早的容量失败。
这段时间顺序给出一条清晰复核断言:所有写入 _inBuf + offs 的路径,都需要等价于 offs <= capacity 且 compressed <= capacity - offs 的不变量。上游通过限制输入,让容量计算本身安全;在写入附近保留显式受检加法断言,还能提供纵深防御。
5.2 堆中相邻对象把尺寸错配推向控制流风险
只有受控源字节抵达安全敏感的相邻对象,越界写入才会从崩溃升级成更严重后果。本案的源就是 NTFS 镜像:物理区段内容未经转换,被直接复制到过小缓冲区。攻击者能够在卷结构与交付路径允许范围内选择这些字节。
公开调试会话检查了一份相关函数代码生成与官方发布一致的 Windows 优化构建。那次会话中,堆把 CInStream 对象放在 _inBuf 后方 304 字节;第一次观察到的读取操作穿过对象,改变虚函数表指针,稍后的数据流调用又使用了已受损的分派状态。这组观察支持公告中“可能任意代码执行”的结论。
304 字节距离只是观察,不是稳定的 ABI 保证。CByteBuffer 存储与 CInStream 对象分属不同分配;堆实现、分配历史、进程架构、调试或发布标志、已加载模块与观测探针都会改变顺序。另一些运行可能先撞到未分配内存、保护页、其他缓冲区或分配器元数据。
Windows ReadFile 要求输出范围在操作期间可写。公开公告指出,未映射或受保护页面会在相应边界令调用失败,改变利用稳定性与崩溃位置;在此之前成功复制的前缀仍可能破坏相邻的已提交内存,1 字节容量约束始终被违反。
现代缓解机制又增加若干分支。ASLR 改变位置,控制流保护可能拒绝部分受损间接调用,分配器检查会在元数据破坏时终止,终端产品也可能截获崩溃。这些控制影响解析器违反内存约束之后的结果,真正修复仍要从源头移除非法状态。
取证采集应在反复测试改变堆之前开始。保存原始稀疏文件,同时记录逻辑大小与已分配大小;计算密码学摘要,保留来源记录;稀疏属性对调查有意义时,采用能保存该元数据的方式复制。持续在生产进程中打开样本会制造新崩溃,也会破坏有价值的时间上下文。
收集 Windows 错误报告事件、应用日志、小型转储或策略允许的完整转储、异常代码、故障模块、调用栈、寄存器状态、进程架构、内存提交压力,以及 7z.dll 或同类模块的加载路径与摘要。Linux 和 macOS 工作节点也应按各自核心转储与模块元数据收集同类信息。
贴近源码的分诊查询会覆盖多组症状:GetCuSize() 的未定义位移报告、InitAndSeek() 发出的超大分配申请、NTFS 数据流缓冲区读取期间的故障、数据流对象的非法虚拟分派,以及 7-Zip 进程之后出现的子进程活动。没有一项能单独覆盖全部事件,必须与处理器选择和输入摘要关联。
如果进程处理文件后仍继续运行,就按疑似代码执行事件的标准开展调查:检查子进程、令牌或凭据访问、外连、新写可执行文件、持久化变更、异常模块加载与安全控制篡改,并与准确处理时间对齐。内存故障证明发生过暴露,只有利用后的行为证据才能证明失陷,两者要分别记录。

最安全的实验室证明在相邻对象受损前停止:记录型分配器与源数据流桩证明 26.00 形成容量/长度错配并在读取前中止,同一结构交给 26.01 或 26.02 则在 CHeader::Parse() 被拒绝。无需演练受损间接调用,就能同时证明成因与修复;随后运营问题自然转向哪些工作流会选中这份解析器。
ISequentialInStream 允许短读取,ReadStream() 会前移目标继续尝试,因此第一次少读只能改变崩溃顺序,不能恢复安全。网络后端、过滤器和主机 API 可能选择不同块大小,安全桩要记录每轮已处理字节与目标偏移,并断言所有成功写入的累计末端始终不超过容量。
转储分析把地址还原成分配角色:_inBuf 起点与大小、源范围、附近堆块、CInStream 地址和受损虚调用目标。事件再按逻辑输入摘要、父容器摘要与解析器构建去重,同时保留每次执行结果;这样既不假定 304 字节距离处处成立,也不会因改名或稀疏展开割裂同一对象。
可复用加固让 I/O 辅助函数接收同时携带指针与容量的可写跨度,使用受检累计偏移,并在向任何适配器索取字节前拒绝超界末端。安全回放还可只生成物理偏移、簇数、字节长度、目标偏移和累计末端的读取计划,不读取区段内容;26.00 的计划会显出 256 MiB 请求,修复版则在头部闸门处根本无法形成计划。早期几何上限仍是主要修复,容量感知传输负责把未来误算挡在生产端与接收端的交接点。
6 无论文件叫什么,NTFS 处理器都可能找到它
6.1 扩展名只是提示,偏移 3 的签名才是持久身份
在 NtfsHandler.cpp 末尾,7-Zip 把格式注册为 NTFS,扩展名提示写作 ntfs img。同一注册记录又给出一段签名:N、T、F、S 加 4 个空格,从偏移 3 开始;注册表中的格式标识符为 0xD9。这些细节把解析器接入归档框架的发现逻辑。
扩展名可以让某个处理器排在候选队列前面,却无法取消内容识别。公开验证确认了 7-Zip 的回退行为:外层文件名建议的格式打开失败后,框架会按签名优先级尝试其余处理器。文件可以披着另一种归档、文档或无扩展名外衣,首个候选拒绝后仍进入 NTFS。
所以拦截 .ntfs 与 .img 只能算便利过滤器,能抓住诚实命名,挡不住对抗式或偶然改名。发送方提供的 MIME,或只看文件名的分类器,也有同样缺陷。更持久的检测特征是字节签名,加上从内容推导出的几何。
签名匹配只证明 NTFS 处理器成为候选。触发链还需要 CHeader::Parse() 接受其余引导字段、MFT 定位与解析成功、文件暴露兼容的非驻留压缩数据流、区段重建完成,并由测试或提取操作读取该流。文件到达对应“已交付”;任务、处理器与数据流读取关联后才对应“已执行易损解析”。
这一区分能让事件时间线更准确。邮件或下载遥测说明对象何时进入环境;文件打开与处理器遥测说明 7-Zip 何时检查它;测试或提取任务说明压缩数据流何时可能被读;崩溃、分配或子进程证据才显示操作走到多深。把这些时间戳全压成“文件已见”,会丢掉因果顺序。
嵌套交付还会增加一层。NTFS 镜像可能放在 ZIP、7z、ISO、安装器或软件包内;外层容器被提取时,只是把内层镜像写到磁盘,不一定立刻调用 NTFS 处理器。后续递归扫描器、用户操作或流水线阶段才可能打开内层对象。防守方应保留父容器与子对象的归档关系,以及每个处理器被选中时所在的任务阶段。
内容扫描的第一道防线不能继续复用易损解析器。一个很小的独立读取器只需读取引导签名、扇区大小字段、每簇扇区数编码,再用带范围检查的算术求出簇指数。它可以在遍历 MFT、按声明几何分配或读取压缩数据流之前,就标出 ClusterSizeLog > 21。
轻量结果证明签名与异常几何,作为早期分诊信号使用。镜像摘要、逻辑大小与已分配大小、来源、已选处理器,以及压缩单元 4 的非驻留数据属性分别补足后续阶段;在这些事实尚未齐全时,隔离仍可先控制高风险输入。

6.2 真实资产还包括库、工作节点、插件与私有副本
Windows 软件包会留下文件管理器、命令行程序、外壳集成与 7z.dll,7-Zip Extra 还提供独立组件;复制到脚本或应用旁的副本脱离中央安装器。Linux 与 macOS 的 7zz、构建缓存、容器层、上传网关、安全产品、桌面应用与静态集成同样属于范围,即使进程名和产品版本都不含 7-Zip。
盘点分三层:软件包数据说明管理系统预期存在什么,文件系统与镜像层说明真实对象在哪里,运行时模块或进程遥测说明哪一份对象实际处理输入。每个副本记录负责人、业务功能、输入来源、执行身份、架构、路径、摘要、签名方、软件包或容器来源、最近加载时间,以及测试或提取是否自动发生。
版本字符串只负责缩小搜索范围。第一章的源码不变量与实际加载对象才决定修复状态:Windows 采集代表性进程的模块路径和摘要,容器同时检查镜像层与运行实例,临时工作节点把任务调用绑定到构建清单和产物摘要。供应商回移还要提供头部上限、拒绝阶段和边界测试,产品自己的版本号无法替代这些证据。
历史查询同时搜索 7-Zip 可执行文件与加载该库的宿主应用,再关联邮件、下载、浏览器、移动介质、上传、产物和构建记录。名称显示 ZIP、RAR、7z、ISO 或未知格式的对象,只要内容命中 NTFS 也进入查询;嵌套对象保留父子摘要、工作节点、处理器选择以及写出、列出、测试、提取的真实动作。
内容事件至少保存 8 字节签名、推导出的 ClusterSizeLog、原始与解析后文件名、父容器与对象摘要、稀疏文件的逻辑/已分配大小、用户或服务身份、命令或 API、已选处理器和结果。独立预筛器另记缺失签名、头部截断、扇区编码错误、受支持几何、簇指数超限或推导错误,使隔离原因能够与崩溃遥测对齐。
第一章处置图是这部分的唯一执行清单。例外只覆盖准确解析器和输入范围,写明负责人、隔离路线、容量、禁用动作与到期日;补偿服务必须已修复,并与易损节点的凭据和可写存储分开。无害识别语料覆盖正常命名、改后缀、无后缀、截断签名、偶然相似字节和仅写出未打开的嵌套成员,用于反复验证回退路由而不触及危险几何。
7 26.01 在引导扇区解析阶段拒绝危险几何
7.1 一个上限变化,让不可能的卷无法进入任何下游路径
官方 26.00 至 26.01 对比只包含发布提交 8c63d71ff886bda90c86db28466287f977374237。与本案有关的 NtfsHandler.cpp 代码片段只有一行删除、一行新增:第 122–134 行刚算出 ClusterSizeLog,条件便从 > 30 改成 > 21。
ClusterSizeLog = SectorSizeLog + sectorsPerClusterLog;
if (ClusterSizeLog > 21)
return false;
拒绝发生在第一次引导扇区解析,此时 NumClusters 尚未驱动深层遍历,MFT 尚未作为数据流打开,数据属性尚未分组,区段尚未重建,CInStream 也未分配缓冲区。上限因此支配物理偏移、记录推导、区段核算、数据流构造和缓存规划,超限几何无法进入卷状态。
算术余量非常直接。唯一受支持的压缩指数是 4,于是最大 GetCuSize() 指数为 25;32 位位移返回 33,554,432 字节,即 32 MiB;两格输出缓存表达式请求 64 MiB。两项计算在 32/64 位构建中都有定义,输入缓冲区也能覆盖处理器模型下的完整压缩单元。
修复还把物理偏移、卷大小、记录计算、已分配大小对齐、区段总量和直接寻址带回更接近受支持 NTFS 的取值域。7-Zip 当前官方版本历史已在 26.01 项下明确列出 CVE-2026-48095;26.02 仍是 2026 年 7 月 26 日的官方最新版,其固定提交继续执行同一上限。部署结论最终由第 6 章的加载路径、摘要和软件包/构建映射确认。
7.2 跨字段算术不变量必须经得住补丁移植与格式演进
上游修复建立了一条紧凑不变量:任何可接受的 ClusterSizeLog,与 IsCompressionUnitSupported() 允许的任一压缩单元相加,位移指数都低于 32。这句话连接两处解析阶段和一个带类型的表达式,正是复核人员判断补丁移植是否等价时需要保留的审查单位。
等价回移从 CHeader::Parse() 开始,核对两种每簇扇区数编码、形成 ClusterSizeLog 的加法与有效上限,再沿所有调用路径确认后续赋值无法覆盖结果。数据流侧继续核对 GetCuSize() 左操作数、_chunkSizeLog、分配器和输出缓存表达式的真实类型,并证明拒绝能传回列出、测试和提取操作。对象摘要只能绑定产物;这些取值域、类型和控制流属性才定义等价修复。
无害边界样本使用有效的 512 或 4096 字节引导扇区,只改变产生簇指数 20、21、22 的编码,不放入可用 MFT 或压缩文件。其余头部正确时,20 与 21 可进入下一有效阶段,22 必须命中簇上限分支;探针同时证明 MFT 未打开、CInStream 未创建、分配与读取均未发生。模拟构造器再把可接受簇值与 CompressionUnit 0/4 交叉,记录指数、输入容量、输出请求与读取尝试。
兼容性需要独立测试语料:采用常见 4 KiB 簇、由 Windows 创建的 NTFS 镜像,可获得时的现代大簇卷,驻留与非驻留文件、稀疏数据流、碎片化区段、未压缩数据、LZNT1 压缩数据、备用数据流与临界大小文件。只有正常镜像仍能正确列出、测试和提取,安全拒绝才可用于生产。
运行时检查器再提供独立报警。所有可接受算术组合上 UBSan 应保持安静;ASan 不应看到输入/输出缓冲区外写入;分配失败注入应证明未完成初始化的数据流能干净回收;返回短读取的桩数据源应给出 S_FALSE,不进入未初始化内容。

未来变化要自动触发重新复核。只要 GetCuSize()、压缩指数验证、每簇扇区数解码、缓存槽数量、分配器或读取长度类型变化,就重新计算完整取值域与内存成本;CI 枚举所有可接受组合,证明位移有定义且最大写入末端不越过容量。常量 21 是当前证明的一部分,不是格式演进后的永久答案。
生产验收还要测资源预算:受支持上限下,一枚压缩输入单元可达 32 MiB,两格输出缓存可请求 64 MiB。金丝雀从普通语料与仅含头部的边界样本开始,记录加载摘要、处理器选择、拒绝原因、峰值私有字节、分配失败、时长、队列、重试与输出,再逐步扩大流量。长期进程排空后才算完成替换;兼容性或容量失败时只切换到另一组已核修复节点。
8 验证早期拒绝并关闭真实暴露面
8.1 安全验证观察分歧,不执行越界写
验证分三层。固定源码复核确认上限支配所有数据流入口;头部单元测试确认受支持边界与下一指数;记录型分配器和读取桩复现 26.00 的算术关系,并在拟写入末端超过容量时停止。三层分别回答源码属性、控制流与容量关系,整个过程不向声明范围外复制字节。
每个结果绑定仓库对象或源软件包、编译器与版本、优化参数、架构、分配器、运行时检查器和产物摘要。易损对照组在 CHeader::Parse()、CMftRec::GetStream()、CInStream::InitAndSeek() 与 ReadStream_FALSE() 前记录原始字段、指数、操作数位宽、输入/输出请求、offs、numChunks 和拟定读取长度;UBSan 报告指数 32,模拟分配器与读取桩分别记录 1 字节和 256 MiB。
修复对照组使用同一有效头部上下文,确认 22 在 CHeader::Parse() 停止,21 可进入下一有效阶段,MFT、CInStream、分配和读取探针呈现预期分歧。畸形签名、非法扇区大小、截断头部和非法记录大小分别使用独立样本并记录拒绝分支,避免无关错误制造误通过。
生产金丝雀只使用无害样本:常规 NTFS 镜像执行列出、测试、提取并核对文件摘要;仅含头部的超限几何样本确认早期拒绝。完整业务周期监测加载模块、崩溃、分配峰值、延迟、重试和隔离结果。结论同时写明覆盖范围:“拒绝”绑定上限分支,“历史无命中”绑定实际查询的工作节点、内容识别和留存窗口。
最终验证包保留源码差异或供应商证明、产物与加载摘要、架构、边界日志、常规语料、运行时检查结果、监测窗口、例外、负责人和独立复核结论。后续应用更新重新带回旧解析器时,这组固定对象与边界行为提供直接比对。
8.2 关闭条件同时覆盖资产、历史与安全回退
第一章处置图已经给出所有者、动作和退出条件;收尾只核对这些条件是否在真实资产上成立。每份解析器都有负责人、输入来源、当前加载摘要、目标构建、临时控制和验证状态。旧二进制文件、失效例外与临时队列在对应业务恢复后移除。
历史调查把对象分为“仅交付”“已执行易损解析”“疑似或确认失陷”。内容到达支持第一类;测试或提取任务与易损加载对象的关联支持第二类;独立的子进程、文件、凭据或网络行为才把状态推进到第三类。查询从对象与父容器连接任务、进程、加载模块和后续遥测,并明确每段缺失数据与留存边界。
出现解析器后行为时,隔离主机或工作负载,保存易失与持久产物,轮换服务身份可访问的凭据,使令牌失效,从可信来源重建,并检查可疑任务产生的下游缓存和产物。升级解析器只改变后续执行,无法撤销已经发生的动作。
兼容性或容量事件触发撤回功能、降低并发或切换到预先核验的修复资源池。错误率、队列时长、内存上限和客户影响阈值在部署前确定,应急软件包与资源池提前完成摘要、签名和容量演练;任何自动回退都排除 26.00 及其他身份不明对象。
关闭报告公开可核分母:已知实例、监测期实际运行实例、加载摘要已核修复实例、隔离例外与负责人未决项,并同时报告模块遥测覆盖率、超限几何拒绝、意外 NTFS 处理器选择、常规任务成功、分配失败、重试与队列。旧摘要重新执行、例外过期、新集成缺少上限证明或监测覆盖跌破约定下限时,任务自动重开。

最后一次无害演练同时提交改过名的仅含头部对象与普通 NTFS 镜像,核对预筛原因、隔离路线、22 的早期拒绝、正常文件摘要、实际加载模块、对象到进程的关联和安全回退。完成后,卷几何、分配容量与生产端长度从第一枚扇区到最后一次读取都受同一组可复核约束。
研究记录
9证据、对象与来源
下面保留本文实际使用的标识、时间和原始材料,便于继续调查。
9.1研究对象
报告涉及的产品、行为者、技术、受影响对象和控制点。
7-Zip NTFS 压缩数据流输入缓冲区分配过小导致堆越界写
GitHub CNA 受影响范围;同时盘点嵌入式和改名后的解析器副本
提交 8c63d71ff886bda90c86db28466287f977374237 修改簇日志上限
标签 f9d78aff31a5f2521ae7ddbdc97c4a8855808959 继续保留修复后的上限
NTFS 头部必须在数据流构造前拒绝不支持的几何
内容回退识别可在 .ntfs 与 .img 之外选中该处理器
9.2事件时间
- 私下报告提交
GitHub Security Lab 通过项目私有 SourceForge 渠道提交压缩数据流分配过小问题。
- 7-Zip 26.01 发布
发布提交 8c63d71ff886bda90c86db28466287f977374237 将接受的簇大小指数上限从 30 降至 21。
- GHSL-2026-140 公开
研究公开算术路径、架构相关的分配行为、调试器观察、处理器回退方式与已测影响。
- CVE 记录发布
GitHub CNA 分配 CVE-2026-48095,CVSS 3.1 为 8.8,并将 26.00 及更早版本列为受影响。
- 7-Zip 26.02 发布
当前公开版本继续保留 ClusterSizeLog <= 21 源码属性。
- SOSEC 复核当前版本与源码边界
SOSEC 重新核对官方当前版本、三个固定提交、精确源码行、NTFS 文档与安全边界测试。
9.3来源与材料
- GitHub Security Lab GHSL-2026-140 技术公告https://securitylab.github.com/advisories/GHSL-2026-140_7-Zip/
- CVE.org 的 CVE-2026-48095 记录https://www.cve.org/CVERecord?id=CVE-2026-48095
- 26.00 固定提交中的易损位移与分配,第 680–697 行https://github.com/ip7z/7zip/blob/839151eaaad24771892afaae6bac690e31e58384/CPP/7zip/Archive/NtfsHandler.cpp#L680-L697
- 26.01 固定提交中的头部修复,第 122–134 行https://github.com/ip7z/7zip/blob/8c63d71ff886bda90c86db28466287f977374237/CPP/7zip/Archive/NtfsHandler.cpp#L122-L134
- 7-Zip 官方 26.00 至 26.01 对比https://github.com/ip7z/7zip/compare/26.00...26.01
- 7-Zip 26.01 发布与修复提交https://github.com/ip7z/7zip/commit/8c63d71ff886bda90c86db28466287f977374237
- 当前 26.02 固定提交保留头部修复,第 122–134 行https://github.com/ip7z/7zip/blob/f9d78aff31a5f2521ae7ddbdc97c4a8855808959/CPP/7zip/Archive/NtfsHandler.cpp#L122-L134
- 7-Zip 26.01 官方发布公告https://sourceforge.net/p/sevenzip/discussion/45797/thread/555e132ba4/
- 7-Zip 官方版本历史https://www.7-zip.org/history.txt
- 7-Zip 官方当前版本下载页https://www.7-zip.org/download.html
- 26.00 固定提交的完整读取循环,第 54–77 行https://github.com/ip7z/7zip/blob/839151eaaad24771892afaae6bac690e31e58384/CPP/7zip/Common/StreamUtils.cpp#L54-L77
- 26.00 固定提交的 Windows 分块读取,第 469–489 行https://github.com/ip7z/7zip/blob/839151eaaad24771892afaae6bac690e31e58384/CPP/Windows/FileIO.cpp#L469-L489
- Microsoft NTFS 概览与受支持簇大小https://learn.microsoft.com/en-us/windows-server/storage/file-server/ntfs-overview
- Microsoft MS-FSA 的 NTFS 簇与压缩单元产品行为https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fsa/4e3695bd-7574-4f24-a223-b4679c065b63
- Microsoft FILE_COMPRESSION_INFORMATIONhttps://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_compression_information
- C++ 工作草案的位移运算规则https://eel.is/c++draft/expr.shift
- Clang 未定义行为检查器文档https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
- Clang AddressSanitizer 文档https://clang.llvm.org/docs/AddressSanitizer.html
- Microsoft ReadFile 文档https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
- MITRE CWE-787 越界写https://cwe.mitre.org/data/definitions/787.html
- MITRE CWE-190 整数溢出或环绕https://cwe.mitre.org/data/definitions/190.html