sdk: 修 ILP32 尺寸算术溢出 — 畸形 BMP 经公开入口 SIGSEGV(与主仓同源补丁)
meow_png_stream.c 的 BMP 路径用 32 位 long 算 rowsize=(w*bpp+31)/32*4,
w≥2^27 且 bpp=32 时 w*bpp 回绕 → rowsize=0 → malloc(0),随后采样下标
sx=(int)((long)tx*w/tw) 同样溢出成负数,row+sx*4 越界读。
实测(zig cc --target=arm-linux-musleabi,ILP32,与 mipsel 同数据模型):
修复前 w=0x07FFFFFF → r=0(安全)
w=0x08000000 → SIGSEGV(139) 只需 54 字节 BMP 头
w=0x10000000/0x40000000 → SIGSEGV
同一文件在 LP64 宿主 → NULL,宿主测试天然看不到这一类
修复前走 stb 路径 → NULL,即合并图像栈后属行为退化
改法(只加闸,合法尺寸的数值路径不变):
- MZP_MAX_DIM (1<<20) 守卫 + mzp_bytes_ok() 64 位校验
- 声明尺寸先取 int 再升 long long;h=INT_MIN 用 -(long long)hi 取绝对值
- rowsize / 行偏移 off / 采样下标 sx 全部升 64 位,sx 夹在 [0,w-1]
- fseek 前 off > LONG_MAX 拒绝;目标 RGBA 字节数 64 位验后再 malloc
- meow_jpeg.c 同类审计:mj_layout() 返回 unsigned long long + MJ_LAYOUT_BAD、
mj_sof() 的 target_bytes/plane、mj_output() 的 y*vh/x*vw、
meow_img_target_dims() 与 mj_pick_n() 的 h*maxdim 全部升 64 位
新增 S17 回归(10 组畸形 BMP + 2 个合法正例 + 1 个 PNG 回绕点)。
可证伪性:旧代码 + 新测试在 ARM32 上 exit 139,新代码 ALL PASS。
S17 的 32 位覆盖需 ARM32 交叉编译(宿主 LP64 只能固定 NULL 契约),
复现命令写在 test_meow_img_stream.c 文件头的「ILP32 回归」小节。
验证:本仓 test_meow_img_stream ALL PASS;sdk/build_sdk.sh 34 成员;
tuku 四套脚本全绿(run_tests 644 PASS / zip 106 / cbz 296 / webtoon PASS)。
This commit is contained in:
1 parent
27a3750e36
commit
342c612358
3 files changed
+300
-47
No files matched your search
@@ -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 <libz_arm.a> \
|
||||
* -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");
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user