diff --git a/sdk/meow_jpeg.c b/sdk/meow_jpeg.c index 0330333..82d3bc4 100644 --- a/sdk/meow_jpeg.c +++ b/sdk/meow_jpeg.c @@ -36,10 +36,10 @@ void meow_img_target_dims(int w, int h, int maxdim, int *tw, int *th) { if (w >= h) { *tw = maxdim; - *th = (int)((long)h * maxdim / w); + *th = (int)((long long)h * maxdim / w); /* 64 位:ILP32 上 h*maxdim 会溢出 */ } else { *th = maxdim; - *tw = (int)((long)w * maxdim / h); + *tw = (int)((long long)w * maxdim / h); } if (*tw < 1) *tw = 1; if (*th < 1) *th = 1; @@ -390,21 +390,25 @@ static int mj_ceil_div(int a, int b) { return (a + b - 1) / b; } * 这种图上 stb 旧路径的峰值本来就受控,直接交回它,档位对外表现不变。 */ static int mj_pick_n(int longside, int maxdim) { - int n = (8 * maxdim + longside - 1) / longside; + long long n = ((long long)8 * maxdim + longside - 1) / longside; /* 64 位:maxdim 可到 INT_MAX */ if (n < 1) n = 1; if (n > 7) return 0; - return n; + return (int)n; } -/* 按各分量当前的 n 计算块网格与平面尺寸,返回平面总字节。 */ -static unsigned mj_layout(mj_img *img) +/* 按各分量当前的 n 计算块网格与平面尺寸,返回平面总字节。 + * 返回 64 位:ILP32 上单平面 pw*ph 与 3 个平面之和都可能越过 2^32。 + * MJ_LAYOUT_BAD 表示该乘积在 32 位 size_t 上无法表示(调用方直接拒绝)。 */ +#define MJ_LAYOUT_BAD ((unsigned long long)-1) +static unsigned long long mj_layout(mj_img *img) { - unsigned total = 0; + unsigned long long total = 0; int i; for (i = 0; i < img->ncomp; i++) { mj_comp *c = &img->c[i]; int sw = mj_ceil_div(img->width * c->h, img->hmax); int sh = mj_ceil_div(img->height * c->v, img->vmax); + unsigned long long plane; c->ow = mj_ceil_div(sw, 8); c->oh = mj_ceil_div(sh, 8); c->pw = (img->mcus_x * c->h > c->ow ? img->mcus_x * c->h : c->ow) * c->n; @@ -413,7 +417,9 @@ static unsigned mj_layout(mj_img *img) c->vh = mj_ceil_div(sh * c->n, 8); if (c->vw > c->pw) c->vw = c->pw; if (c->vh > c->ph) c->vh = c->ph; - total += (unsigned)c->pw * (unsigned)c->ph; + plane = (unsigned long long)c->pw * (unsigned long long)c->ph; + if (plane > (unsigned long long)(size_t)-1) return MJ_LAYOUT_BAD; + total += plane; } return total; } @@ -421,7 +427,7 @@ static unsigned mj_layout(mj_img *img) static int mj_sof(mj_img *img, int marker, const unsigned char *d, int len, int maxdim) { int i, n, longside, base_n; - unsigned int plane_bytes, target_bytes; + unsigned long long plane_bytes, target_bytes; int tw, th; if (marker != 0xC0 && marker != 0xC1 && marker != 0xC2) return 0; /* 算术/无损/差分 */ @@ -456,10 +462,14 @@ static int mj_sof(mj_img *img, int marker, const unsigned char *d, int len, int img->mcus_y = mj_ceil_div(img->height, 8 * img->vmax); meow_img_target_dims(img->width, img->height, maxdim, &tw, &th); - target_bytes = (unsigned)tw * (unsigned)th * 4u; + /* 64 位算目标图字节:ILP32 上 tw*th*4 在 tw=th=32768 时正好回绕成 0, + * 会让下面的预算闸失效并去 malloc(0)(M26 r1 顺手审计)。 */ + target_bytes = (unsigned long long)(unsigned)tw * (unsigned long long)(unsigned)th * 4u; + if (target_bytes > MJ_PEAK_CAP) return 0; for (i = 0; i < n; i++) img->c[i].n = base_n; plane_bytes = mj_layout(img); + if (plane_bytes == MJ_LAYOUT_BAD) return 0; /* 4:4:4 的色度平面与亮度等大(正常 4:2:0 只 1/4):亮度平面超过 400KB 时色度降一档, * 视觉近无损,省下的正是色度要多占的两份。 */ @@ -467,6 +477,7 @@ static int mj_sof(mj_img *img, int marker, const unsigned char *d, int len, int plane_bytes / 3 > 400u * 1024u) { for (i = 1; i < n; i++) if (img->c[i].n > 1) img->c[i].n >>= 1; plane_bytes = mj_layout(img); + if (plane_bytes == MJ_LAYOUT_BAD) return 0; } /* 预算 2.4MB(平面 + 目标图):超了先降色度(亮度保持清晰),再整体降档。 @@ -474,13 +485,17 @@ static int mj_sof(mj_img *img, int marker, const unsigned char *d, int len, int if (plane_bytes + target_bytes > 2400u * 1024u) { int ch = 0; for (i = 1; i < n; i++) if (img->c[i].n > 1) { img->c[i].n >>= 1; ch = 1; } - if (ch) plane_bytes = mj_layout(img); + if (ch) { + plane_bytes = mj_layout(img); + if (plane_bytes == MJ_LAYOUT_BAD) return 0; + } } /* 最后一道防线:异常巨图整体降档(最多降到 N=1),不至于一次吃掉几十 MB。 */ while (plane_bytes + target_bytes > MJ_PEAK_CAP && base_n > 1) { base_n >>= 1; for (i = 0; i < n; i++) if (img->c[i].n > base_n) img->c[i].n = base_n; plane_bytes = mj_layout(img); + if (plane_bytes == MJ_LAYOUT_BAD) return 0; } /* 硬闸:降到 N=1 仍超上限(畸形/超大声明尺寸,如 23 字节的 SOF 65535² → 亮度面 * 8192² = 64MiB)直接拒绝 → 返回 0 → 回退 stb(stb 对这类文件是 KB 级安全失败)。 @@ -489,7 +504,10 @@ static int mj_sof(mj_img *img, int marker, const unsigned char *d, int len, int for (i = 0; i < n; i++) { mj_comp *c = &img->c[i]; - size_t bytes = (size_t)c->pw * (size_t)c->ph; + unsigned long long bytes64 = (unsigned long long)c->pw * (unsigned long long)c->ph; + size_t bytes; + if (bytes64 == 0 || bytes64 > (unsigned long long)(size_t)-1) return 0; + bytes = (size_t)bytes64; c->plane = (unsigned char *)malloc(bytes); if (!c->plane) return 0; memset(c->plane, 128, bytes); /* 中性灰底,系数累加即 IDCT */ @@ -753,7 +771,8 @@ static void mj_scan_ac_prog(mj_img *img, const mj_scan *sc) static unsigned char *mj_output(mj_img *img, int tw, int th, int rgb_mode) { - unsigned char *out = (unsigned char *)malloc((size_t)tw * th * 4); + unsigned long long ob = (unsigned long long)tw * (unsigned long long)th * 4u; + unsigned char *out; mj_comp *cy = &img->c[0]; mj_comp *cb = img->ncomp >= 3 ? &img->c[1] : NULL; mj_comp *cr = img->ncomp >= 3 ? &img->c[2] : NULL; @@ -761,30 +780,62 @@ static unsigned char *mj_output(mj_img *img, int tw, int th, int rgb_mode) const unsigned char *pb = cb ? cb->plane : NULL; const unsigned char *pc = cr ? cr->plane : NULL; int x, y; + if (ob == 0 || ob > (unsigned long long)(size_t)-1) return NULL; + out = (unsigned char *)malloc((size_t)ob); if (!out) return NULL; for (y = 0; y < th; y++) { - int sy = (int)((long)y * cy->vh / th); + /* 64 位:ILP32 上 y*vh、x*vw 都可能越过 2^31(th/vh 各 3 万量级即可) */ + long long syl = (long long)y * cy->vh / th; + int sy = (int)(syl < 0 ? 0 : (syl >= (long long)cy->vh ? (long long)cy->vh - 1 : syl)); const unsigned char *ry = py + (size_t)sy * cy->pw; - const unsigned char *rb = pb ? pb + (size_t)((long)y * cb->vh / th) * cb->pw : NULL; - const unsigned char *rc = pc ? pc + (size_t)((long)y * cr->vh / th) * cr->pw : NULL; - unsigned char *dst = out + (size_t)y * tw * 4; + const unsigned char *rb = NULL, *rc = NULL; + if (pb) { + long long s = (long long)y * cb->vh / th; + if (s >= (long long)cb->vh) s = (long long)cb->vh - 1; + if (s < 0) s = 0; + rb = pb + (size_t)s * cb->pw; + } + if (pc) { + long long s = (long long)y * cr->vh / th; + if (s >= (long long)cr->vh) s = (long long)cr->vh - 1; + if (s < 0) s = 0; + rc = pc + (size_t)s * cr->pw; + } + unsigned char *dst = out + (size_t)((unsigned long long)y * (unsigned long long)tw * 4u); for (x = 0; x < tw; x++) { - int yv = ry[(int)((long)x * cy->vw / tw)]; + long long yxl = (long long)x * cy->vw / tw; + int yv; + if (yxl < 0) yxl = 0; + if (yxl >= (long long)cy->vw) yxl = (long long)cy->vw - 1; + yv = ry[(int)yxl]; unsigned char *o = dst + (size_t)x * 4; if (!rb) { o[0] = o[1] = o[2] = (unsigned char)yv; o[3] = 255; } else if (rgb_mode) { + long long ux = (long long)x * cb->vw / tw; + long long vx = (long long)x * cr->vw / tw; + if (ux < 0) ux = 0; + if (ux >= (long long)cb->vw) ux = (long long)cb->vw - 1; + if (vx < 0) vx = 0; + if (vx >= (long long)cr->vw) vx = (long long)cr->vw - 1; o[0] = (unsigned char)yv; - o[1] = rb[(int)((long)x * cb->vw / tw)]; - o[2] = rc[(int)((long)x * cr->vw / tw)]; + o[1] = rb[(int)ux]; + o[2] = rc[(int)vx]; o[3] = 255; } else { - int u = (int)rb[(int)((long)x * cb->vw / tw)] - 128; - int v = (int)rc[(int)((long)x * cr->vw / tw)] - 128; - int r = yv + ((MJ_FIX_R * v) >> 12); - int g = yv - ((MJ_FIX_G * u + MJ_FIX_G2 * v) >> 12); - int b = yv + ((MJ_FIX_B * u) >> 12); + long long ux = (long long)x * cb->vw / tw; + long long vx = (long long)x * cr->vw / tw; + int u, v, r, g, b; + if (ux < 0) ux = 0; + if (ux >= (long long)cb->vw) ux = (long long)cb->vw - 1; + if (vx < 0) vx = 0; + if (vx >= (long long)cr->vw) vx = (long long)cr->vw - 1; + u = (int)rb[(int)ux] - 128; + v = (int)rc[(int)vx] - 128; + r = yv + ((MJ_FIX_R * v) >> 12); + g = yv - ((MJ_FIX_G * u + MJ_FIX_G2 * v) >> 12); + b = yv + ((MJ_FIX_B * u) >> 12); o[0] = (unsigned char)(r < 0 ? 0 : (r > 255 ? 255 : r)); o[1] = (unsigned char)(g < 0 ? 0 : (g > 255 ? 255 : g)); o[2] = (unsigned char)(b < 0 ? 0 : (b > 255 ? 255 : b)); diff --git a/sdk/meow_png_stream.c b/sdk/meow_png_stream.c index 4da2594..ed1a12d 100644 --- a/sdk/meow_png_stream.c +++ b/sdk/meow_png_stream.c @@ -22,6 +22,7 @@ #include #include #include +#include /* LONG_MAX:BMP 按行 seek 的 32 位偏移上限 */ /* ---------------- zlib ABI ---------------- */ #if defined(__has_include) @@ -59,6 +60,21 @@ extern const char *zlibVersion(void); # define Z_NO_FLUSH 0 #endif +/* ---------------- ILP32 尺寸算术守卫 ---------------- + * mipsel / arm-linux-musleabi 上 long 与 size_t 都是 32 位:把「声明尺寸 × bpp」这类 + * 乘积直接放在 long 里会回绕成小值或 0。实测(ARM32):54 字节 BMP 头声明 w=2^27、 + * bpp=32 → w*bpp = 2^32 = 0 → rowsize = 0 → malloc(0) → 采样索引 row + sx*4 越界读 + * → SIGSEGV;同一文件在 LP64 宿主返回 NULL,所以宿主测试天然看不到这类。 + * 因此:凡「由文件头算出来的字节数/偏移」一律升到 unsigned long long 再验, + * 超限即返回 0,由调用方回退 stb(安全失败,与 M26 之前的行为一致)。 + * 单边上限 1<<20(1,048,576):远超真实 BMP/PNG 声明尺寸,同时保证行字节数 + * (≤4 MiB)、行偏移与目标 RGBA 都不会在 32 位溢出。 */ +#define MZP_MAX_DIM (1u << 20) +static int mzp_bytes_ok(unsigned long long n) +{ + return n > 0 && n <= (unsigned long long)(size_t)-1; +} + /* ---------------- 字节源 ---------------- */ typedef struct { FILE *f; @@ -202,12 +218,15 @@ static int png_emit_row(png_ctx *c) } } if (c->next_ty < c->th) { - long sy = (long)c->next_ty * (long)c->h / c->th; - if (sy == (long)c->y) { - unsigned char *o = c->out + (size_t)c->next_ty * c->tw * 4; + long long sy = (long long)c->next_ty * (long long)c->h / c->th; /* 64 位:ILP32 上 *h 会溢出 */ + if (sy == (long long)c->y) { + unsigned char *o = c->out + + (size_t)((unsigned long long)c->next_ty * (unsigned long long)c->tw * 4u); int tx; for (tx = 0; tx < c->tw; tx++) { - int sx = (int)((long)tx * (long)c->w / c->tw); + long long sxl = (long long)tx * (long long)c->w / c->tw; + int sx = (int)(sxl < 0 ? 0 + : (sxl >= (long long)c->w ? (long long)c->w - 1 : sxl)); unsigned char px[4]; png_get_px(c, dst, sx, px); o[tx * 4] = px[0]; o[tx * 4 + 1] = px[1]; @@ -315,9 +334,14 @@ int meow_png_decode_stream(FILE *f, const unsigned char *mem, size_t len, memset(c.cur, 0, c.rowbytes); for (i = 0; i < 256; i++) c.trns_a[i] = 255; meow_img_target_dims((int)c.w, (int)c.h, maxdim, &c.tw, &c.th); - c.out = (unsigned char *)malloc((size_t)c.tw * c.th * 4); - if (!c.out) goto done; - memset(c.out, 0, (size_t)c.tw * c.th * 4); + { + /* ILP32 上 tw*th*4 会回绕(32768×32768×4 = 2^32 → 0 → malloc(0) 后越界写) */ + unsigned long long ob = (unsigned long long)c.tw * (unsigned long long)c.th * 4u; + if (!mzp_bytes_ok(ob)) goto done; + c.out = (unsigned char *)malloc((size_t)ob); + if (!c.out) goto done; + memset(c.out, 0, (size_t)ob); + } memset(&c.z, 0, sizeof c.z); if (inflateInit_(&c.z, zlibVersion(), (int)sizeof c.z) != Z_OK) goto done; c.z_ready = 1; @@ -423,16 +447,25 @@ int meow_bmp_decode_stream(FILE *f, const unsigned char *mem, size_t len, if (dibsize < 40 || dibsize > sizeof dib) return 0; /* BITMAPCOREHEADER/OS2 → stb */ memcpy(dib, dh, 4); if (!ps_read(&src, dib + 4, dibsize - 4)) return 0; - w = (long)(int)le32(dib + 4); - h = (long)(int)le32(dib + 8); bpp = le16(dib + 14); compression = le32(dib + 16); colors = (dibsize >= 36) ? le32(dib + 32) : 0; if (compression != 0) return 0; /* RLE/BITFIELDS → stb */ - if (w <= 0 || h == 0) return 0; if (bpp != 8 && bpp != 16 && bpp != 24 && bpp != 32) return 0; - topdown = h < 0; - if (h < 0) h = -h; + /* 声明尺寸过 32 位算术闸(M26 r1 P1):w×bpp 与 tx×w 在 ILP32 上会回绕成小值/负数, + * 直接导致 malloc(0) + 越界读。h=INT_MIN 时 -h 同样溢出,故升到 long long 再取绝对值。 */ + { + int wi = (int)le32(dib + 4); + int hi = (int)le32(dib + 8); + long long hll; + if (wi <= 0 || hi == 0) return 0; + topdown = hi < 0; + hll = topdown ? -(long long)hi : (long long)hi; + if ((unsigned long long)wi > MZP_MAX_DIM || hll <= 0 || + (unsigned long long)hll > MZP_MAX_DIM) return 0; /* 离奇声明尺寸 → 回退 stb */ + w = (long)wi; + h = (long)hll; + } if ((long)(w > h ? w : h) <= maxdim) return 0; /* 小图走旧入口 */ if (bpp <= 8) { @@ -446,26 +479,38 @@ int meow_bmp_decode_stream(FILE *f, const unsigned char *mem, size_t len, for (; i < 256; i++) pal[i][0] = pal[i][1] = pal[i][2] = 0; } - rowsize = (w * bpp + 31) / 32 * 4; + { + /* 行字节数 64 位算(ILP32 上 w*bpp 在 w≥2^27/bpp 时回绕成 0) */ + unsigned long long rb = ((unsigned long long)w * bpp + 31u) / 32u * 4u; + if (!mzp_bytes_ok(rb)) return 0; + rowsize = (long)rb; /* w ≤ MZP_MAX_DIM → ≤ 4 MiB */ + } meow_img_target_dims((int)w, (int)h, maxdim, &tw_, &th_); row = (unsigned char *)malloc((size_t)rowsize); - out = (unsigned char *)malloc((size_t)tw_ * th_ * 4); + { + unsigned long long ob = (unsigned long long)tw_ * (unsigned long long)th_ * 4u; + if (mzp_bytes_ok(ob)) out = (unsigned char *)malloc((size_t)ob); + } if (!row || !out) goto done; for (int ty = 0; ty < th_; ty++) { - int sy = (int)((long)ty * h / th_); - long file_row = topdown ? sy : (h - 1 - sy); - long off = (long)dataoff + file_row * rowsize; - unsigned char *o = out + (size_t)ty * tw_ * 4; + long long sy = (long long)ty * h / th_; + long long file_row = topdown ? sy : ((long long)h - 1 - sy); + long long off = (long long)dataoff + file_row * (long long)rowsize; + unsigned char *o = out + (size_t)((unsigned long long)ty * (unsigned long long)tw_ * 4u); if (src.mem) { - if (off < 0 || (size_t)off + (size_t)rowsize > src.memlen) goto done; - memcpy(row, src.mem + off, (size_t)rowsize); + if (off < 0 || + (unsigned long long)off + (unsigned long long)rowsize > (unsigned long long)src.memlen) + goto done; + memcpy(row, src.mem + (size_t)off, (size_t)rowsize); } else { - if (fseek(src.f, off, SEEK_SET) != 0) goto done; + if (off < 0 || off > (long long)LONG_MAX) goto done; /* fseek 只有 32 位偏移 */ + if (fseek(src.f, (long)off, SEEK_SET) != 0) goto done; if (fread(row, 1, (size_t)rowsize, src.f) != (size_t)rowsize) goto done; } for (int tx = 0; tx < tw_; tx++) { - int sx = (int)((long)tx * w / tw_); + long long sxl = (long long)tx * w / tw_; + int sx = (int)(sxl < 0 ? 0 : (sxl >= (long long)w ? (long long)w - 1 : sxl)); unsigned char *p = o + (size_t)tx * 4; if (bpp == 24) { const unsigned char *q = row + sx * 3; diff --git a/sdk/test_meow_img_stream.c b/sdk/test_meow_img_stream.c index 4b1534d..ab80cf8 100644 --- a/sdk/test_meow_img_stream.c +++ b/sdk/test_meow_img_stream.c @@ -12,6 +12,9 @@ * S8 内存入口 == 文件入口(逐字节),峰值同级 * S9 回退:CMYK JPEG(4 分量)、1bpp BMP、Adam7 交错 PNG → 后端拒绝,stb 仍成功; * 截断/垃圾 → NULL(不崩) + * S10-S16 缩解阶/restart/DHT/tRNS/step/低熵/progressive-restart 等专项 + * S17 32 位尺寸算术:畸形 BMP 声明尺寸(w=2^27…、24bpp 回绕点、h=INT_MIN)与 PNG + * tw*th*4 回绕点 → 一律 NULL 不崩(ILP32 崩溃回归,M26 r1 P1) * * 夹具:本文件内置生成脚本(Pillow),首次运行时写到夹具目录并执行。 * 编译(本 sdk 目录下;--wrap 用于给 malloc/free 家族打点): @@ -20,6 +23,17 @@ * -lm -lz \ * -Wl,--wrap=malloc -Wl,--wrap=calloc -Wl,--wrap=realloc -Wl,--wrap=free * 运行:./test_meow_img_stream "$PREFIX/tmp/meow_img_stream" (exit 0=PASS / 1=FAIL) + * + * ILP32 回归(S17 的真覆盖;宿主 LP64 的 long/size_t 是 64 位,看不到 32 位回绕崩溃): + * 用 zig 的 arm-linux-musleabi 目标(与 mipsel 同数据模型:long/size_t 均 4 字节), + * 需要一份 32 位 libz.a(设备上的 libz.a 就是 mipsel 32 位;本机可从 zlib 源码 + * 用同一 zig cc --target=arm-linux-musleabi -c 各 .c 后 ar 打包)。命令: + * zig cc --target=arm-linux-musleabi -static -O2 -I. -Iinclude -DMEOW_UI_HOST_TEST \ + * -o test_meow_img_stream_arm test_meow_img_stream.c meow_ui_img.c meow_jpeg.c \ + * meow_png_stream.c stb_image.c -lm \ + * -Wl,--wrap=malloc -Wl,--wrap=calloc -Wl,--wrap=realloc -Wl,--wrap=free + * ./test_meow_img_stream_arm "$PREFIX/tmp/meow_img_stream" + * (aarch64 Android 设备可直跑 32 位 ARM 静态二进制;2026-11-06 实测 S1-S17 全绿。) */ #define _GNU_SOURCE #include "meow_ui.h" @@ -566,6 +580,60 @@ static size_t build_sof_only(unsigned char *buf, int w, int h, int ncomp) return p; } +/* ---- S17 用的畸形头构造器(54 字节 BMP / 仅 IHDR 的 PNG) ---- */ +static size_t bmp_build(unsigned char *b, unsigned w, unsigned hbits, unsigned bpp) +{ + size_t off = 54; + memset(b, 0, off); + b[0] = 'B'; b[1] = 'M'; + b[2] = (unsigned char)(off & 0xFF); b[3] = (unsigned char)((off >> 8) & 0xFF); + b[10] = (unsigned char)(off & 0xFF); b[11] = (unsigned char)((off >> 8) & 0xFF); + b[14] = 40; /* BITMAPINFOHEADER */ + b[18] = (unsigned char)(w & 0xFF); b[19] = (unsigned char)((w >> 8) & 0xFF); + b[20] = (unsigned char)((w >> 16) & 0xFF); b[21] = (unsigned char)((w >> 24) & 0xFF); + b[22] = (unsigned char)(hbits & 0xFF); b[23] = (unsigned char)((hbits >> 8) & 0xFF); + b[24] = (unsigned char)((hbits >> 16) & 0xFF); b[25] = (unsigned char)((hbits >> 24) & 0xFF); + b[26] = 1; /* planes */ + b[28] = (unsigned char)(bpp & 0xFF); + return off; +} + +/* 正例对照:合法的 64×64 32bpp BMP(topdown=1 时 h 写成负数) */ +static size_t bmp_build_64(unsigned char *b, int topdown) +{ + const unsigned w = 64, h = 64, bpp = 32; + size_t n = bmp_build(b, w, topdown ? (unsigned)(-64) : h, bpp); + unsigned x, y; + b[2] = (unsigned char)((n + w * h * 4) & 0xFF); /* 文件大小(解析器不校验) */ + b[3] = (unsigned char)(((n + w * h * 4) >> 8) & 0xFF); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++, n += 4) { + b[n + 0] = (unsigned char)(x * 4); + b[n + 1] = (unsigned char)(y * 4); + b[n + 2] = 0x40; + b[n + 3] = 0xFF; + } + return n; +} + +static size_t png_ihdr_only(unsigned char *b, unsigned w, unsigned h) +{ + static const unsigned char sig[8] = { 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n' }; + size_t p = 0; + memcpy(b, sig, 8); + p = 8; + b[p++] = 0; b[p++] = 0; b[p++] = 0; b[p++] = 13; /* IHDR 长度 */ + memcpy(b + p, "IHDR", 4); p += 4; + b[p++] = (unsigned char)(w >> 24); b[p++] = (unsigned char)((w >> 16) & 0xFF); + b[p++] = (unsigned char)((w >> 8) & 0xFF); b[p++] = (unsigned char)(w & 0xFF); + b[p++] = (unsigned char)(h >> 24); b[p++] = (unsigned char)((h >> 16) & 0xFF); + b[p++] = (unsigned char)((h >> 8) & 0xFF); b[p++] = (unsigned char)(h & 0xFF); + b[p++] = 8; /* bit depth */ + b[p++] = 2; /* color type RGB */ + b[p++] = 0; b[p++] = 0; b[p++] = 0; + return p; /* 故意不带后续块 */ +} + /* ================================================================== */ int main(int argc, char **argv) { @@ -1117,6 +1185,95 @@ int main(int argc, char **argv) } } + /* ---- S17 32 位尺寸算术(ILP32 崩溃回归,M26 r1 P1) ---- + * 54 字节 BMP 头声明 w=2^27、bpp=32 时,ILP32 上 w*bpp = 2^32 → rowsize 回绕成 0 → + * malloc(0) → 采样索引 row + sx*4 越界读 → SIGSEGV。LP64 宿主不会回绕,所以本节在 + * 宿主的价值是「契约固定」:这些声明尺寸必须一律返回 NULL(且不崩);真正的 32 位 + * 覆盖用法见文件头「ILP32 回归」那条交叉编译命令。 */ + printf("[S17] 32 位尺寸算术:BMP 声明尺寸 / 采样偏移溢出(ILP32 崩溃回归)\n"); + { + static const struct { const char *tag; unsigned w, hbits, bpp; int must_null; } sc[] = { + { "w=0x07FFFFFF bpp=32(回绕点前一点)", 0x07FFFFFFu, 1u, 32u, 1 }, + { "w=0x08000000 bpp=32(2^27,旧代码崩点)", 0x08000000u, 1u, 32u, 1 }, + { "w=0x10000000 bpp=32", 0x10000000u, 1u, 32u, 1 }, + { "w=0x40000000 bpp=32", 0x40000000u, 1u, 32u, 1 }, + { "w=0x0AAAAAAA bpp=24(w*24 回绕点前)", 0x0AAAAAAAu, 1u, 24u, 1 }, + { "w=0x0AAAAAAB bpp=24(w*24 ≥ 2^32)", 0x0AAAAAABu, 1u, 24u, 1 }, + /* 合法尺寸(≤1<<20)但无像素数据:后端必须拒绝;公开入口按设计回退 stb, + * stb 对截断 BMP 的行为不受本单约束,故只断言「不崩」,不要求 NULL。 */ + { "w=1000000 bpp=32(合法尺寸但无像素数据)", 1000000u, 1u, 32u, 0 }, + { "w=0x00100000 bpp=8(单边上限 1<<20)", 0x00100000u, 1u, 8u, 0 }, + { "w=0x00100001 bpp=8(超单边上限)", 0x00100001u, 1u, 8u, 1 }, + { "h=0x80000000(-INT_MIN 溢出点)", 0x01000000u, 0x80000000u, 32u, 1 }, + }; + for (size_t i = 0; i < sizeof sc / sizeof sc[0]; i++) { + unsigned char buf[54]; + int tw = 0, th = 0; + unsigned char *px = NULL; + meow_img *im; + char name[200]; + size_t base, pk; + char *fp = path_join("evil_dims.bmp"); + FILE *f; + + bmp_build(buf, sc[i].w, sc[i].hbits, sc[i].bpp); + snprintf(name, sizeof name, "%s → 后端拒绝", sc[i].tag); + base = peak_reset(); + CHECK(meow_bmp_decode_stream(NULL, buf, sizeof buf, 720, &tw, &th, &px) == 0, name); + pk = peak_since(base); + CHECK(px == NULL, "拒绝时不返回像素缓冲"); + if (sc[i].must_null) CHECK(pk < PEAK_2500KB, "越界声明在算术前就被拒(不分配)"); + + f = fopen(fp, "wb"); + CHECK(f != NULL, "写畸形 BMP 到夹具目录"); + if (f) { fwrite(buf, 1, sizeof buf, f); fclose(f); } + + im = meow_img_load_file_dim(fp, 720); + snprintf(name, sizeof name, "%s → _dim 不崩%s", sc[i].tag, + sc[i].must_null ? "且返回 NULL" : ""); + CHECK(im == NULL || !sc[i].must_null, name); + meow_img_free(im); + + im = meow_img_load_mem_dim(buf, (int)sizeof buf, 720); + snprintf(name, sizeof name, "%s → _mem_dim 不崩%s", sc[i].tag, + sc[i].must_null ? "且返回 NULL" : ""); + CHECK(im == NULL || !sc[i].must_null, name); + meow_img_free(im); + free(fp); + } + + /* 正例对照:合法 64×64 32bpp 必须仍能解(含 h<0 的正上方行序) */ + { + unsigned char *big = (unsigned char *)malloc(54 + 64 * 64 * 4); + int tw = 0, th = 0; + unsigned char *px = NULL; + size_t n; + CHECK(big != NULL, "分配正例 BMP 缓冲"); + if (big) { + n = bmp_build_64(big, 0); + CHECK(meow_bmp_decode_stream(NULL, big, n, 32, &tw, &th, &px) == 1 && + tw == 32 && th == 32, "正例:64x64 32bpp 正序 → 32x32"); + free(px); px = NULL; tw = th = 0; + n = bmp_build_64(big, 1); + CHECK(meow_bmp_decode_stream(NULL, big, n, 32, &tw, &th, &px) == 1 && + tw == 32 && th == 32, "正例:64x64 32bpp 倒序(h<0)→ 32x32"); + free(px); + free(big); + } + } + + /* PNG:tw*th*4 在 ILP32 上回绕成 0 的坐标点(65535² 头 + maxdim=32768) */ + { + unsigned char pb[64]; + int tw = 0, th = 0; + unsigned char *px = NULL; + size_t pn = png_ihdr_only(pb, 65535, 65535); + CHECK(meow_png_decode_stream(NULL, pb, pn, 32768, &tw, &th, &px) == 0, + "PNG 65535² maxdim=32768 → 拒绝(目标图字节数会回绕)"); + CHECK(px == NULL, "PNG 拒绝时不返回像素缓冲"); + } + } + /* ---- S8 内存入口 ---- */ printf("[S8] 内存入口 meow_img_load_mem_dim\n"); {