fix(sdk): M26 r1 P1 — ILP32 尺寸算术溢出(畸形 BMP 经公开入口 SIGSEGV)+ 加 S17 回归

评审在 ARM32(ILP32,与 mipsel 同数据模型)实测:54 字节 BMP 头声明 w=2^27、bpp=32
时 w*bpp 回绕成 0 → rowsize=0 → malloc(0) → 采样索引 row+sx*4 越界读 → SIGSEGV,
经 meow_img_load_file_dim 端到端可触发;同一文件在 LP64 宿主返回 NULL(不溢出),
合并前走 stb 也是 NULL。即这是相对旧路径的鲁棒性回归。

修复(只加闸,正常路径数值不变):

- meow_png_stream.c(P1 本体)
  · 新增 ILP32 守卫 MZP_MAX_DIM=1<<20 与 mzp_bytes_ok()(unsigned long long 验算,
    超过 SIZE_MAX 即拒)
  · BMP:声明尺寸先升 int/long long 校验(w/h 单边 ≤1<<20;h=INT_MIN 用
    -(long long)hi 取绝对值,不再有 -h 溢出);rowsize 用 64 位算并验;
    行偏移 off / 采样下标 sx 改 long long,sx 夹在 [0,w-1];按行 fseek 前加
    off > LONG_MAX 拒绝;目标 RGBA 字节数 64 位验后再 malloc
  · PNG:IHDR 尺寸原已 ≤65535,补上 tw*th*4 的 64 位校验(32768²×4 = 2^32 会回绕),
    emit_row 的 sy/sx 与输出行偏移改 64 位并夹紧
- meow_jpeg.c(顺手审计同类 32 位算术,评审要求「不要只修点名那处」)
  · mj_layout 返回 unsigned long long(单平面/三平面之和都可能越 2^32),
    越界返回 MJ_LAYOUT_BAD → mj_sof 各处重新布局后都检查
  · target_bytes 与分量 plane malloc 改 64 位验算;目标图字节超 MJ_PEAK_CAP 直接拒
  · mj_output:输出缓冲 64 位验;y*vh / x*vw 采样改 long long 并夹紧
  · meow_img_target_dims / mj_pick_n 的 (long)h*maxdim、8*maxdim 改 long long

- test_meow_img_stream.c:新增 S17(10 组畸形 BMP 声明尺寸 + 2 个合法正例 +
  PNG 回绕点),断言「后端拒绝 + 公开入口不崩(越界尺寸另断言 NULL 且不分配)」;
  文件头补 ILP32 交叉编译命令(宿主 LP64 看不到 32 位回绕,测试注释里明写这一点)

验证:同一测试在 zig arm-linux-musleabi(sizeof(long)=4)下 S1-S17 全绿(206 checks,
exit 0);把后端换回 r1 提交的旧实现,同一 ARM32 二进制 exit 139(说明 S17 真能抓);
宿主 LP64 S1-S17 全绿、B1-B9 全绿;静态库 --verify 仍 78 成员 / 全 MIPS32r2。

同步提醒:这些行与公开仓 ~/Proj/MeowOS-SDK/sdk/{meow_png_stream.c,meow_jpeg.c} 逐字节
同源,同一补丁必须由控制塔同步过去,否则立刻产生双边漂移。
This commit is contained in:
AskaEth committed 2026-11-06 21:27:03 +08:00
1 parent ee5796747d
commit 281f4153ee
3 files changed
+300 -47

No files matched your search

+67 -22
View File
@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h> /* 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;