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
+67
-22
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user