fix: 畸形 DHT 拒绝 + 渐进式 restart 重同步(M23 r1 P1-1/P1-2)
P1-1(内存安全):mj_huff_build 构造 9 位快表前校验规范码空间 (code + bits[l] > (1<<l) → 表非法),填表时再夹一道 <512 边界;mj_dht 失败 即返回 0 → 后端拒绝 → 回退 stb。此前过订阅 DHT(如「长度 2 的码 100 个」)会把 fast[512] 下标推到 12799,28~220 字节的畸形 JPEG 就栈溢出(ASan 实测 stack-buffer-overflow,非 ASan 直接 SIGSEGV/SIGBUS)。 回归:码长 1..4 × 符号数 3..255 共 1012 组畸形 DHT 全部后端拒绝、无崩溃; 「有效 JPEG + 注入坏 DHT」走拒绝 + 回退(不花屏)。 P1-2(能力):三处扫描循环的区间重同步统一到 mj_boundary,**只在 total == rst (编码器区间长度)时重同步**。原实现一见「over && marker∈D0..D7」就重同步,但熵码 位缓冲有最多 9 位前瞻,over 会在区间真正结束前 1~2 个 MCU 置位——提前重同步会丢掉 bitbuf 里本区间剩余块的有效比特,随即失步(渐进式 DC 扫描每块仅 ~1 bit,前瞻能跨过 整个 MCU)。这正是 Pillow restart_marker_blocks=1/2/3/4/5/8/16/64/200 的渐进式 100% 回退 stb(25.8MiB)的根因。另修 mj_restart 裸扫把 FF 00(数据字节 0xFF 的填充) 里的 00 误当 marker。 回归:9 个渐进式 + restart 夹具全部 BACKEND=1、峰值 1.86MiB(与无 restart 相同)、 与同编码无 restart 文件逐字节一致(mad=0.000);baseline + restart 仍逐字节一致。 附带(评审 P2 两项,改动极小且已验证): - PNG 8bit gray/RGB 的 tRNS 透明接通(8bit 采样值在 tRNS 低字节),与 stb 逐点 alpha 一致; - 拒绝 16bit 量化表(只属 12bit 精度;收下会让 F*qt 在 mj_accim 里 int32 溢出,UB)。 - meow_img_load_file_step 的 JPEG 走真缩解后端(PNG/BMP 暂留 stb:阅读器预算模型仍是 M22「整幅物化」口径,待 M27 同步后放开)。 验证:ASan 构建全 PASS;test_meow_img_stream 111 项全 PASS;test_meow_img_budget / test_meow_img_color 全 PASS;tuku run_tests.sh rc=0(zip 106 / cbz 296,含 membudget)、 run_webtoon_tests.sh rc=0;build_sdk.sh 零警告,MIPS32 rel2 目标码。 峰值不变(修复不增加分配):2000x3000 baseline/progressive 1.86MiB、PNG 1.33MiB。 体积:meow_jpeg.o 17,284B + meow_png_stream.o 8,456B = 25.1KB。
This commit is contained in:
1 parent
e9c0749609
commit
11a4fcd730
6 files changed
+377
-54
No files matched your search
+254
-4
@@ -180,6 +180,17 @@ static const char *g_gen_py =
|
||||
" chunk(b'IDAT', zlib.compress(bytes(raw))) + chunk(b'IEND', b''))\n"
|
||||
" open(path, 'wb').write(data)\n"
|
||||
"adam7(os.path.join(out, 'adam7.png'), 1600, 2400)\n"
|
||||
"for rb in (1, 2, 3, 4, 5, 8, 16, 64, 200):\n"
|
||||
" big.save(os.path.join(out, 'prog_rst%d.jpg' % rb), 'JPEG', quality=60,\n"
|
||||
" subsampling=2, progressive=True, restart_marker_blocks=rb)\n"
|
||||
"g8 = Image.new('L', (640, 480), 0)\n"
|
||||
"dg = ImageDraw.Draw(g8)\n"
|
||||
"dg.rectangle([100, 100, 400, 300], fill=255)\n"
|
||||
"g8.save(os.path.join(out, 'gray8_trns.png'), 'PNG', transparency=0)\n"
|
||||
"r8 = Image.new('RGB', (640, 480), (255, 0, 255))\n"
|
||||
"dr = ImageDraw.Draw(r8)\n"
|
||||
"dr.rectangle([100, 100, 400, 300], fill=(10, 200, 30))\n"
|
||||
"r8.save(os.path.join(out, 'rgb8_trns.png'), 'PNG', transparency=(255, 0, 255))\n"
|
||||
"open(os.path.join(out, 'broken.jpg'), 'wb').write(b'\\xff\\xd8\\xff\\xe0' +\n"
|
||||
" bytes((i * 37 + 11) & 0xFF for i in range(256)))\n"
|
||||
"for n in sorted(os.listdir(out)):\n"
|
||||
@@ -390,6 +401,75 @@ static size_t old_dim_peak(const char *path, int maxdim)
|
||||
return peak;
|
||||
}
|
||||
|
||||
static long alpha_zero_count(const meow_img *im)
|
||||
{
|
||||
int w = meow_img_w(im), h = meow_img_h(im);
|
||||
const unsigned char *p = meow_img_pixels(im);
|
||||
long n = 0;
|
||||
if (!p) return 0;
|
||||
for (long i = 0; i < (long)w * h; i++) if (p[i * 4 + 3] == 0) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 目标图 vs 整幅参考图的 alpha 一致性:按 nearest 反向映射采样源像素,返回不一致像素数
|
||||
* (-1 = 参数错误)。PNG 的采样公式与后端一致(tx*W/tw、ty*H/th),应逐点相等。 */
|
||||
static long alpha_mismatch(const meow_img *got, const meow_img *ref)
|
||||
{
|
||||
int gw = meow_img_w(got), gh = meow_img_h(got);
|
||||
int rw = meow_img_w(ref), rh = meow_img_h(ref);
|
||||
const unsigned char *pg = meow_img_pixels(got), *pr = meow_img_pixels(ref);
|
||||
long bad = 0;
|
||||
if (!pg || !pr || gw <= 0 || gh <= 0) return -1;
|
||||
for (int y = 0; y < gh; y++) {
|
||||
int sy = (int)((long)y * rh / gh);
|
||||
for (int x = 0; x < gw; x++) {
|
||||
int sx = (int)((long)x * rw / gw);
|
||||
if (pg[((size_t)y * gw + x) * 4 + 3] != pr[((size_t)sy * rw + sx) * 4 + 3])
|
||||
bad++;
|
||||
}
|
||||
}
|
||||
return bad;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* P1-1 回归:过订阅 Huffman 表(畸形 DHT) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 构造「SOI + DHT(码长 lc 有 nsym 个符号) + EOI」的最小 JPEG(≤ 280B)。 */
|
||||
static size_t build_badhuff(unsigned char *buf, int lc, int nsym)
|
||||
{
|
||||
size_t p = 0;
|
||||
int i;
|
||||
int seglen = 2 + 1 + 16 + nsym; /* DHT 长度字段计入自身 2 字节 */
|
||||
buf[p++] = 0xFF; buf[p++] = 0xD8; /* SOI */
|
||||
buf[p++] = 0xFF; buf[p++] = 0xC4; /* DHT */
|
||||
buf[p++] = (unsigned char)(seglen >> 8);
|
||||
buf[p++] = (unsigned char)(seglen & 0xFF);
|
||||
buf[p++] = 0x00; /* Tc=0, Th=0 */
|
||||
for (i = 1; i <= 16; i++)
|
||||
buf[p++] = (unsigned char)(i == lc ? nsym : 0);
|
||||
for (i = 0; i < nsym; i++) buf[p++] = (unsigned char)i;
|
||||
buf[p++] = 0xFF; buf[p++] = 0xD9; /* EOI */
|
||||
return p;
|
||||
}
|
||||
|
||||
/* 把同一段坏 DHT 插到真实 JPEG 的 SOI 之后(其余字节原样):验证「有效文件 + 畸形
|
||||
* 码表」走的是「拒绝 + 回退」,而不是花屏/崩溃。 */
|
||||
static unsigned char *inject_badhuff(const unsigned char *orig, size_t n,
|
||||
int lc, int nsym, size_t *outn)
|
||||
{
|
||||
unsigned char seg[320];
|
||||
size_t all = build_badhuff(seg, lc, nsym);
|
||||
size_t seglen = all - 4; /* 去掉首 FFD8 与尾 FFD9 */
|
||||
size_t total = n + seglen;
|
||||
unsigned char *out = (unsigned char *)malloc(total);
|
||||
if (!out) return NULL;
|
||||
memcpy(out, orig, 2); /* SOI */
|
||||
memcpy(out + 2, seg + 2, seglen); /* 坏 DHT 段 */
|
||||
memcpy(out + 2 + seglen, orig + 2, n - 2); /* 原文件其余部分 */
|
||||
*outn = total;
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
@@ -681,22 +761,192 @@ int main(int argc, char **argv)
|
||||
}
|
||||
{
|
||||
size_t base = peak_reset();
|
||||
meow_img *got = meow_img_load_file_dim(prs, 720);
|
||||
size_t pk = peak_since(base);
|
||||
meow_img *ref = ref_thumb(prs, 720);
|
||||
int tw = 0, th = 0;
|
||||
unsigned char *px = NULL;
|
||||
int handled = meow_img_backend_file(prs, 720, &tw, &th, &px);
|
||||
meow_img *got;
|
||||
size_t pk;
|
||||
meow_img *ref;
|
||||
double corr = 0.0;
|
||||
double d = (got && ref) ? mean_abs_diff(got, ref, 64, &corr) : 1e9;
|
||||
double d;
|
||||
if (px) free(px);
|
||||
base = peak_reset();
|
||||
got = meow_img_load_file_dim(prs, 720);
|
||||
pk = peak_since(base);
|
||||
ref = ref_thumb(prs, 720);
|
||||
d = (got && ref) ? mean_abs_diff(got, ref, 64, &corr) : 1e9;
|
||||
peak_report("restart marker 2000x3000", pk);
|
||||
CHECK(handled == 1, "baseline + restart 走后端(BACKEND=1,不回退 stb)");
|
||||
CHECK(got && nonblank(got), "restart marker JPEG 解码成功");
|
||||
CHECK(d < 24.0 && corr > 0.85, "restart marker JPEG 与 stb 结构一致(RST 重同步正确)");
|
||||
CHECK(pk <= PEAK_2500KB, "restart marker JPEG 峰值 ≤ 2.5 MiB");
|
||||
meow_img_free(got);
|
||||
meow_img_free(ref);
|
||||
}
|
||||
/* baseline + restart 与同参数无 restart 文件:RST 只切分熵段,解码结果应逐字节相同 */
|
||||
{
|
||||
char *pbase = path_join("base2000.jpg");
|
||||
meow_img *a = meow_img_load_file_dim(prs, 720);
|
||||
meow_img *b = meow_img_load_file_dim(pbase, 720);
|
||||
CHECK(a && b && meow_img_w(a) == meow_img_w(b) && meow_img_h(a) == meow_img_h(b) &&
|
||||
memcmp(meow_img_pixels(a), meow_img_pixels(b),
|
||||
(size_t)meow_img_w(a) * meow_img_h(a) * 4) == 0,
|
||||
"baseline restart 与无 restart 结果逐字节一致(mad=0)");
|
||||
meow_img_free(a);
|
||||
meow_img_free(b);
|
||||
free(pbase);
|
||||
}
|
||||
free(psw);
|
||||
free(prs);
|
||||
}
|
||||
|
||||
/* ---- S11 畸形 DHT(过订阅 Huffman 表):拒绝 + 回退,不崩 ---- */
|
||||
printf("[S11] 畸形 DHT 矩阵(码长 1..4 × 符号数 3..255)\n");
|
||||
{
|
||||
unsigned char buf[320];
|
||||
int cases = 0, rejected = 0;
|
||||
int lc, nsym;
|
||||
for (lc = 1; lc <= 4; lc++) {
|
||||
for (nsym = 3; nsym <= 255; nsym++) {
|
||||
size_t n = build_badhuff(buf, lc, nsym);
|
||||
int tw = 0, th = 0;
|
||||
unsigned char *px = NULL;
|
||||
cases++;
|
||||
/* 崩溃即测试进程死;返回 1 说明过订阅表被当成合法表收下(缺陷) */
|
||||
if (meow_img_backend_mem(buf, n, 720, &tw, &th, &px) == 0) rejected++;
|
||||
if (px) free(px);
|
||||
}
|
||||
}
|
||||
{
|
||||
char name[128];
|
||||
snprintf(name, sizeof name, "过订阅 DHT 全部拒绝(%d/%d,无崩溃)", rejected, cases);
|
||||
CHECK(rejected == cases, name);
|
||||
}
|
||||
/* 有效 JPEG + 注入坏 DHT:后端必须拒绝;公共入口不得崩(stb 兜底或返回 NULL) */
|
||||
{
|
||||
size_t flen = 0, ilen = 0;
|
||||
uint8_t *real = read_whole(p_base, &flen);
|
||||
CHECK(real != NULL, "读入 baseline JPEG 用于注入");
|
||||
if (real) {
|
||||
uint8_t *inj = inject_badhuff(real, flen, 2, 100, &ilen);
|
||||
int tw = 0, th = 0;
|
||||
unsigned char *px = NULL;
|
||||
meow_img *im;
|
||||
CHECK(inj && meow_img_backend_mem(inj, ilen, 720, &tw, &th, &px) == 0,
|
||||
"有效 JPEG + 注入过订阅 DHT → 后端拒绝");
|
||||
if (px) free(px);
|
||||
im = inj ? meow_img_load_mem_dim(inj, (int)ilen, 720) : NULL;
|
||||
CHECK(im == NULL || nonblank(im), "注入坏表后公共入口不崩、不花屏");
|
||||
meow_img_free(im);
|
||||
free(inj);
|
||||
free(real);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- S12 渐进式 + restart marker(P1-2 回归) ---- */
|
||||
printf("[S12] progressive + restart marker(BACKEND 必须为 1,峰值 ≤2.5MiB)\n");
|
||||
{
|
||||
static const int rb[] = { 1, 2, 3, 4, 5, 8, 16, 64, 200 };
|
||||
meow_img *ref = meow_img_load_file_dim(p_prog, 720);
|
||||
int all_backend = 1, all_peak = 1, all_equal = 1;
|
||||
for (size_t i = 0; i < sizeof rb / sizeof rb[0]; i++) {
|
||||
char fname[48];
|
||||
char *p;
|
||||
int tw = 0, th = 0;
|
||||
unsigned char *px = NULL;
|
||||
meow_img *im;
|
||||
size_t base, pk;
|
||||
snprintf(fname, sizeof fname, "prog_rst%d.jpg", rb[i]);
|
||||
p = path_join(fname);
|
||||
if (meow_img_backend_file(p, 720, &tw, &th, &px) != 1) all_backend = 0;
|
||||
if (px) free(px);
|
||||
base = peak_reset();
|
||||
im = meow_img_load_file_dim(p, 720);
|
||||
pk = peak_since(base);
|
||||
if (!(im && nonblank(im) && pk <= PEAK_2500KB)) all_peak = 0;
|
||||
if (!(im && ref && meow_img_w(im) == meow_img_w(ref) && meow_img_h(im) == meow_img_h(ref) &&
|
||||
memcmp(meow_img_pixels(im), meow_img_pixels(ref),
|
||||
(size_t)meow_img_w(ref) * meow_img_h(ref) * 4) == 0)) {
|
||||
double c2 = 0.0;
|
||||
double d2 = (im && ref) ? mean_abs_diff(im, ref, 64, &c2) : 1e9;
|
||||
if (!(d2 < 0.1)) all_equal = 0; /* 允许极个别像素 ±1(编码器分表差异) */
|
||||
printf(" RST=%-3d 与无 restart 平均差 %.4f\n", rb[i], d2);
|
||||
}
|
||||
/* 关键正确性:与本文件自己的 stb 整幅解码比对(不同编码器脚本下 restart 文件的
|
||||
* 系数可能不同,所以拿「无 restart 文件」当基准只能作参考) */
|
||||
{
|
||||
meow_img *rr = ref_thumb(p, 720);
|
||||
double c3 = 0.0;
|
||||
double d3 = (im && rr) ? mean_abs_diff(im, rr, 64, &c3) : 1e9;
|
||||
if (!(d3 < 24.0 && c3 > 0.85)) all_peak = 0;
|
||||
printf(" RST=%-3d vs stb 平均差 %.2f 相关 %.3f\n", rb[i], d3, c3);
|
||||
meow_img_free(rr);
|
||||
}
|
||||
printf(" RST=%-3d → %dx%d peak %.2f MiB\n", rb[i],
|
||||
meow_img_w(im), meow_img_h(im), (double)pk / 1048576.0);
|
||||
free(p);
|
||||
meow_img_free(im);
|
||||
}
|
||||
CHECK(all_backend == 1, "9 个 progressive+restart 全部走后端(BACKEND=1)");
|
||||
CHECK(all_peak == 1, "9 个 progressive+restart 全部解码成功且峰值 ≤2.5MiB");
|
||||
CHECK(all_equal == 1, "与无 restart 的同编码 progressive 结果逐字节一致(mad=0)");
|
||||
meow_img_free(ref);
|
||||
}
|
||||
|
||||
/* ---- S13 8bit gray/RGB PNG 的 tRNS 透明(P2-1 回归) ---- */
|
||||
printf("[S13] PNG 8bit tRNS(gray / RGB)\n");
|
||||
{
|
||||
struct { const char *tag; const char *name; } fx[] = {
|
||||
{ "gray8 tRNS", "gray8_trns.png" },
|
||||
{ "rgb8 tRNS", "rgb8_trns.png" },
|
||||
};
|
||||
for (size_t i = 0; i < sizeof fx / sizeof fx[0]; i++) {
|
||||
char *p = path_join(fx[i].name);
|
||||
int tw = 0, th = 0;
|
||||
unsigned char *px = NULL;
|
||||
int handled = meow_img_backend_file(p, 320, &tw, &th, &px);
|
||||
meow_img *got;
|
||||
meow_img *full;
|
||||
char name[128];
|
||||
if (px) free(px);
|
||||
got = meow_img_load_file_dim(p, 320);
|
||||
full = meow_img_load_file(p); /* stb 整幅(tRNS 处理正确) */
|
||||
snprintf(name, sizeof name, "%s 走后端(BACKEND=1)", fx[i].tag);
|
||||
CHECK(handled == 1, name);
|
||||
snprintf(name, sizeof name, "%s 目标图有透明像素(%ld 个)", fx[i].tag,
|
||||
alpha_zero_count(got));
|
||||
CHECK(got && alpha_zero_count(got) > 0, name);
|
||||
snprintf(name, sizeof name, "%s alpha 与 stb 整幅逐点一致(不一致 %ld)", fx[i].tag,
|
||||
alpha_mismatch(got, full));
|
||||
CHECK(got && full && alpha_mismatch(got, full) == 0, name);
|
||||
meow_img_free(got);
|
||||
meow_img_free(full);
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- S14 M22 显式档位入口 _step:JPEG 走真缩解(PNG/BMP 待 M27 同步预算模型) ---- */
|
||||
printf("[S14] meow_img_load_file_step(M22 档位入口)JPEG 走后端\n");
|
||||
{
|
||||
size_t base = peak_reset();
|
||||
meow_img *im = meow_img_load_file_step(p_base, 720, 8);
|
||||
size_t pk = peak_since(base);
|
||||
char name[128];
|
||||
snprintf(name, sizeof name, "_step(720,8) → %dx%d(长边 ≤720,非空)",
|
||||
meow_img_w(im), meow_img_h(im));
|
||||
CHECK(im && nonblank(im) && meow_img_w(im) <= 720 && meow_img_h(im) <= 720, name);
|
||||
peak_report("_step(720, 8)", pk);
|
||||
CHECK(pk <= PEAK_2500KB, "_step 峰值 ≤2.5MiB(走后端,不整幅物化)");
|
||||
meow_img_free(im);
|
||||
base = peak_reset();
|
||||
im = meow_img_load_file_step(p_base, 720, 1);
|
||||
pk = peak_since(base);
|
||||
CHECK(im && nonblank(im) && meow_img_w(im) <= 720, "_step(720,1) 成功且长边 ≤720");
|
||||
CHECK(pk <= PEAK_2500KB, "_step(720,1) 峰值 ≤2.5MiB");
|
||||
meow_img_free(im);
|
||||
}
|
||||
|
||||
/* ---- S8 内存入口 ---- */
|
||||
printf("[S8] 内存入口 meow_img_load_mem_dim\n");
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user