Files
MeowOS-For-R3P2/patch_v2/meowpkg/test_nyai.sh
T
AskaEth cf5703b32b fix(nyai): M35 评审 r1 — 闭合 exFAT 大小写绕过(P1)+ 修 fmt_utc 1/2 月 + 成员名/炸弹纵深
P1(信任边界):is_system_pkg() 原用 strncmp,而落盘目录在 exFAT(名字比较
不区分大小写)。包内 app.json 写 "OS.MEOW.READER" 时 valid_pkg 放行、
is_system_pkg 返回 0 → 当第三方无签放行 → dst 解析到已装的
MeowAPPs/os.meow.reader → 备份后部署假包并删掉真系统 App(无需私钥)。
三层封堵:
  1) is_system_pkg 改 strncasecmp(前缀判定不再受大小写影响);
  2) valid_pkg 收紧为只收 [a-z0-9._-],并拒绝前导/尾随 '.'(现网包名全小写,
     无兼容代价;tail-dot 同时挡 exFAT/Windows 名字映射怪癖);
  3) 部署前 readdir 检查 MeowAPPs 里是否有与 pkg 仅大小写不同的已装目录
     (有则拒装并报两个名字);部署后再用 readdir 复核 on-disk 名字与清单包名
     逐字节相等,不等则回滚,杜绝「身份不明的 App 目录」。

P2-1:fmt_utc() 的 March-based 月份换算写成 +9(应为 -9),1/2 月会打成
20251901/20251931 且年份不 +1。修正为 int 运算;新增 test_fmt_utc.c
(直接 include nyai.c 测 static 函数)覆盖 epoch/负数、1/1、2/1、2/28、闰年
2/29(2024 与 2000)、3/1、12/31 23:59:59、2038-01-01、int32 上界、2100 非闰。

纵深防御(评审残留风险,一并做):
- tar 成员名净化不再依赖外部 tar:mpk_check_members 解析 tar -tv 的名字,
  拒绝绝对路径 / 含 '..' 组件 / 首字符 '-' / 原始控制字符;解包后 stg_tree_ok
  再用 readdir 的真实名字复核控制字符(tar 列名会把控制字符转义成可见文本,
  原检查会漏)。
- 解压炸弹:累加 tar -tv 的体积,超过 512MB 上限拒装,并加 statvfs
  「剩余空间 ≥ 体积 + 16MB」门禁(解包先于验签,恶意包可在验签前写满卡)。
- 顺手改掉 appmeta.package 注释里「== 文件名茎」的旧语义。

回归:test_nyai.sh 49 → 70 条断言全过(新增:fmt_utc 13 例、大写包名绕过顶替
已装系统包被拒且原 App 未变、尾点包名、与已装目录仅大小写不同、绝对路径/
../ / 首字符 '-' 成员名、控制字符成员名、700MB 解压炸弹),
`-Wall -Wextra` 零告警,二进制可重复构建。

交付:nyai 153,844 B / md5 ef7940ecf5ece993d718ac1d9019cc82(旧 4524ce6b…)。
真机 10.58.215.50 仍不可登录(22 端口 TCP 可连、15-50s 无 banner),本轮
**未做真机复核**,仅宿主验证。
2026-11-10 21:42:37 +08:00

358 lines
17 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# test_nyai.sh — nyai 宿主回归(M35:app.json 清单权威 + SD 暂存 + 无条件清理)
#
# 覆盖:
# 1 文件名/后缀与包名完全不同(weird-name.bin)仍按清单装进 MeowAPPs/<app.json.package>
# 2 真实反例:签名 os.meow.reader 包命名成 os.meow.reader-0.1.7.mpk 仍装进 MeowAPPs/os.meow.reader
# 3 footer.package ≠ app.json.package → 拒装,错误文案同时给出两个名字
# 4 os.meow.* 无签名 → 拒装
# 5 第三方无签名 → 放行(行为不变)
# 6 恶意 app.json.package(../x、a/b、.、..)→ 拒装且不落盘到目录外
# 7 成功 / 各种失败 / 解压失败 / SIGTERM 中断 → MeowOS/tmp/pkgs 无残留
# 8 索引路径:Packages 的 Package 字段取自清单(文件改名后按包名仍可装)
# 9 upgrade 复用 install 引擎:清单包名/目标包名不一致、降级都拒
#
# 依赖:宿主 cc(编 nyai_host / meow_verify_host / meow_sign)。
# 签名用例需要维护者私钥:默认 ~/.keys/meowos-signing/signing.key,
# 可用 NYAI_TEST_SIGN_KEY 覆盖;无私钥时签名用例 SKIP(不判 FAIL)。
# 真实反例包默认 ~/.cache/os.meow.reader-0.1.7-signed.mpk,可用 NYAI_TEST_REAL_MPK 覆盖。
#
# 用法:./test_nyai.sh [-v]
set -u
HERE=$(cd "$(dirname "$0")" && pwd)
ROOT=$(cd "$HERE/../.." && pwd)
VERBOSE=0
[ "${1:-}" = "-v" ] && VERBOSE=1
WORK=${NYAI_TEST_WORK:-$(mktemp -d "${TMPDIR:-/tmp}/nyai-m35.XXXXXX")}
mkdir -p "$WORK"
CLEANUP_WORK=1
[ -n "${NYAI_TEST_WORK:-}" ] && CLEANUP_WORK=0
trap '[ "$CLEANUP_WORK" = 1 ] && rm -rf "$WORK"' EXIT
PASS=0; FAIL=0; SKIP=0
ok() { PASS=$((PASS+1)); printf 'PASS %s\n' "$1"; }
bad() { FAIL=$((FAIL+1)); printf 'FAIL %s\n' "$1"; [ -n "${2:-}" ] && printf ' %s\n' "$2"; }
skip() { SKIP=$((SKIP+1)); printf 'SKIP %s (%s)\n' "$1" "${2:-}"; }
KEY=${NYAI_TEST_SIGN_KEY:-$HOME/.keys/meowos-signing/signing.key}
[ -r "$KEY" ] || KEY=""
REAL_MPK=${NYAI_TEST_REAL_MPK:-$HOME/.cache/os.meow.reader-0.1.7-signed.mpk}
# ---------------- 构建 ----------------
echo "== build =="
( cd "$HERE" && ./build_nyai.sh host >/dev/null ) || { echo "build nyai_host failed"; exit 1; }
NYAI="$HERE/nyai_host"
CC=${CC_HOST:-cc}
V="$ROOT/patch_v2/meow_verify"
mkdir -p "$WORK/tools"
"$CC" -Os -Wno-sign-compare -Wno-unterminated-string-initialization -Wno-unused-function \
-c "$V/tweetnacl.c" -o "$WORK/tools/tn.o" || { echo "build tweetnacl failed"; exit 1; }
"$CC" -Os -c "$V/sha256.c" -o "$WORK/tools/sha.o" || exit 1
"$CC" -Os -I"$V" -c "$V/meow_verify.c" -o "$WORK/tools/mv.o" || exit 1
"$CC" "$WORK/tools/mv.o" "$WORK/tools/sha.o" "$WORK/tools/tn.o" -o "$WORK/tools/meow_verify_host" || exit 1
if [ -n "$KEY" ]; then
"$CC" -Os -I"$V" -I"$ROOT/patch_v2/meow_sign" "$ROOT/patch_v2/meow_sign/meow_sign.c" \
"$WORK/tools/tn.o" "$WORK/tools/sha.o" -o "$WORK/tools/meow_sign" || exit 1
fi
VERIFY="$WORK/tools/meow_verify_host"
# ---------------- 辅助 ----------------
sb() { # sb <name> → 打印沙箱 MeowROOT
local d="$WORK/case-$1"
rm -rf "$d"
mkdir -p "$d/root/MeowAPPs" "$d/root/MeowOS/opt/nyai"
printf '%s\n' "$d/root"
}
mkpkg() { # mkpkg <dir> <package> <version_code>
rm -rf "$1"; mkdir -p "$1"
printf '{"name":"Test","package":"%s","exec":"bin","version":"0.1.%s","version_code":%s}\n' \
"$2" "$3" "$3" > "$1/app.json"
printf '#!/bin/sh\nexit 0\n' > "$1/bin"; chmod +x "$1/bin"
}
pack() { tar czf "$2" -C "$1" .; }
mk_evil_tar() { # mk_evil_tar <out.mpk> <member-name> <package>:成员名可含 ../ 或绝对路径
python3 - "$1" "$2" "$3" <<'PY'
import sys, tarfile, io, gzip
out, name, pkg = sys.argv[1], sys.argv[2], sys.argv[3]
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode='w') as t:
ti = tarfile.TarInfo(name); ti.size = 3; t.addfile(ti, io.BytesIO(b'pwn'))
j = ('{"name":"X","package":"%s","exec":"x","version":"1","version_code":1}' % pkg).encode()
tj = tarfile.TarInfo('app.json'); tj.size = len(j); t.addfile(tj, io.BytesIO(j))
open(out, 'wb').write(gzip.compress(buf.getvalue()))
PY
}
mk_bomb_tar() { # mk_bomb_tar <out.mpk>:成员头声明 700MB 的解压炸弹
python3 - "$1" <<'PY'
import sys, tarfile, gzip
ti = tarfile.TarInfo('big.bin'); ti.size = 700 * 1024 * 1024
ti.mtime = 0; ti.mode = 0o644; ti.uid = ti.gid = 0
hdr = ti.tobuf(format=tarfile.USTAR_FORMAT)
open(sys.argv[1], 'wb').write(gzip.compress(hdr + b'\0' * 1024))
PY
}
sign() { # sign <pkg> <mpk>:文件名茎必须等于包名(meow_sign 约定),签完覆盖原文件
[ -n "$KEY" ] || return 2
mkdir -p "$WORK/sign"
cp "$2" "$WORK/sign/$1.mpk"
"$WORK/tools/meow_sign" sign "$WORK/sign/$1.mpk" "$KEY" >/dev/null 2>&1 || return 1
cp "$WORK/sign/$1.mpk" "$2"
}
nyai_run() { # nyai_run <root> <args...>
local r=$1; shift
MEOWROOT="$r" MEOW_VERIFY_BIN="$VERIFY" "$NYAI" "$@"
}
tmp_clean() { # tmp_clean <root>:MeowOS/tmp/pkgs 下无任何条目
local n
n=$(find "$1/MeowOS/tmp/pkgs" -mindepth 1 2>/dev/null | wc -l | tr -d ' ')
[ "$n" = "0" ]
}
show() { [ "$VERBOSE" = 1 ] && printf '%s\n' "$1"; }
expect() { # expect <label> <cond...>
local l=$1; shift
if "$@"; then ok "$l"; else bad "$l"; fi
}
echo "== tests =="
# ---- 0 fmt_utc 边界(直接 include nyai.c 的 static 函数,2026-01/02 曾算错) ----
if "$CC" -Wall -Wextra "$HERE/test_fmt_utc.c" -o "$WORK/tools/test_fmt_utc" 2>"$WORK/fmt_utc.build.log"; then
if "$WORK/tools/test_fmt_utc" >"$WORK/fmt_utc.log" 2>&1; then
ok "0 fmt_utc 边界($(cat "$WORK/fmt_utc.log"))"
else
bad "0 fmt_utc 边界" "$(cat "$WORK/fmt_utc.log")"
fi
else
bad "0 fmt_utc 编译" "$(cat "$WORK/fmt_utc.build.log")"
fi
# ---- 1 文件名与包名完全不同 ----
r=$(sb 1); mkpkg "$WORK/b1" os.meow.demo 1; pack "$WORK/b1" "$WORK/p1.mpk"
if sign os.meow.demo "$WORK/p1.mpk"; then
cp "$WORK/p1.mpk" "$WORK/weird-name.bin"
out=$(nyai_run "$r" install "$WORK/weird-name.bin" 2>&1); rc=$?
show "$out"
expect "1a weird-name.bin(无 .mpk 后缀)按清单装入 MeowAPPs/os.meow.demo" \
test $rc -eq 0 -a -f "$r/MeowAPPs/os.meow.demo/app.json"
expect "1b 未按文件名生成目录" test ! -e "$r/MeowAPPs/weird-name"
expect "1c 成功后 MeowOS/tmp/pkgs 无残留" tmp_clean "$r"
else
skip "1 文件名解绑(签名)" "无签名私钥"
fi
# ---- 2 真实反例:reader 包改名 ----
if [ -r "$REAL_MPK" ]; then
r=$(sb 2)
cp "$REAL_MPK" "$WORK/os.meow.reader-0.1.7.mpk"
out=$(nyai_run "$r" install "$WORK/os.meow.reader-0.1.7.mpk" 2>&1); rc=$?
show "$out"
expect "2a os.meow.reader-0.1.7.mpk 仍装进 MeowAPPs/os.meow.reader(不再按文件名取包名)" \
test $rc -eq 0 -a -f "$r/MeowAPPs/os.meow.reader/app.json"
expect "2b 没有生成 MeowAPPs/os.meow.reader-0.1.7" test ! -e "$r/MeowAPPs/os.meow.reader-0.1.7"
expect "2c 成功后无暂存残留" tmp_clean "$r"
else
skip "2 真实 reader 包改名安装" "缺 $REAL_MPK"
fi
# ---- 3 footer.package ≠ app.json.package ----
if [ -n "$KEY" ]; then
r=$(sb 3); mkpkg "$WORK/b3" os.meow.mismatch-b 1; pack "$WORK/b3" "$WORK/mis-a.mpk"
# 签名时文件名茎 = 包名 → footer 写 os.meow.mismatch-a,清单里却是 os.meow.mismatch-b
sign os.meow.mismatch-a "$WORK/mis-a.mpk"
out=$(nyai_run "$r" install "$WORK/mis-a.mpk" 2>&1); rc=$?
show "$out"
expect "3a footer.package ≠ app.json.package → 拒装" test $rc -ne 0
expect "3b 错误文案含两个包名与「不一致」" \
sh -c 'printf "%s" "$1" | grep -q "签名包名(os.meow.mismatch-a) 与 app.json(os.meow.mismatch-b) 不一致"' _ "$out"
expect "3c 拒装后未落盘任何 App" test ! -e "$r/MeowAPPs/os.meow.mismatch-b" -a ! -e "$r/MeowAPPs/os.meow.mismatch-a"
expect "3d 拒装后无暂存残留" tmp_clean "$r"
else
skip "3 footer/清单不一致" "无签名私钥"
fi
# ---- 4 os.meow.* 无签名 ----
r=$(sb 4); mkpkg "$WORK/b4" os.meow.unsigned 1; pack "$WORK/b4" "$WORK/unsigned-sys.mpk"
out=$(nyai_run "$r" install "$WORK/unsigned-sys.mpk" 2>&1); rc=$?
show "$out"
expect "4a os.meow.* 无签名 → 拒装" test $rc -ne 0
expect "4b 错误文案含「必须签名」" sh -c 'printf "%s" "$1" | grep -q "必须签名"' _ "$out"
expect "4c 未落盘 + 无残留" test ! -e "$r/MeowAPPs/os.meow.unsigned"
expect "4d 拒装后无暂存残留" tmp_clean "$r"
# ---- 5 第三方无签名放行 ----
r=$(sb 5); mkpkg "$WORK/b5" dev.local.thirdparty 1; pack "$WORK/b5" "$WORK/thirdparty.mpk"
out=$(nyai_run "$r" install "$WORK/thirdparty.mpk" 2>&1); rc=$?
show "$out"
expect "5a 第三方无签名 → 放行安装(行为不变)" \
test $rc -eq 0 -a -f "$r/MeowAPPs/dev.local.thirdparty/app.json"
expect "5b 成功后无残留" tmp_clean "$r"
# ---- 6 恶意 package 名 ----
for badname in "../escape" "a/b" "." ".."; do
r=$(sb 6)
mkpkg "$WORK/b6" "$badname" 1; pack "$WORK/b6" "$WORK/evil.mpk"
out=$(nyai_run "$r" install "$WORK/evil.mpk" 2>&1); rc=$?
show "$out"
expect "6 [$badname] 拒装" test $rc -ne 0
expect "6 [$badname] 错误文案指向 app.json.package" \
sh -c 'printf "%s" "$1" | grep -q "app.json.package 非法"' _ "$out"
expect "6 [$badname] 未落盘到目录外(root/escape、root/a、沙箱父目录)" \
test ! -e "$r/escape" -a ! -e "$r/a" -a ! -e "$WORK/case-6/escape" -a ! -e "$WORK/case-6/a"
expect "6 [$badname] MeowAPPs/MeowOS 目录未被破坏" \
test -d "$r/MeowAPPs" -a -d "$r/MeowOS"
expect "6 [$badname] 无暂存残留" tmp_clean "$r"
done
# ---- 7 解压失败 / SIGTERM 中断 ----
r=$(sb 7)
printf 'this is not a gzip stream\n' > "$WORK/broken.mpk"
out=$(nyai_run "$r" install "$WORK/broken.mpk" 2>&1); rc=$?
show "$out"
expect "7a 非法包(非 gzip/tar)→ 拒装" test $rc -ne 0
expect "7b 解压失败后无暂存残留" tmp_clean "$r"
mkpkg "$WORK/b7" dev.test.slow 1
head -c 134217728 /dev/zero > "$WORK/b7/big.bin" # 128MB,拉长解包时间以便打信号
pack "$WORK/b7" "$WORK/slow.mpk"
r=$(sb 7s)
MEOWROOT="$r" MEOW_VERIFY_BIN="$VERIFY" "$NYAI" install "$WORK/slow.mpk" >"$WORK/sig.log" 2>&1 &
pid=$!
sleep 0.30
kill -TERM "$pid" 2>/dev/null
wait "$pid"; rc=$?
show "$(cat "$WORK/sig.log")"
if grep -q "收到信号" "$WORK/sig.log"; then
expect "7c SIGTERM 中断:退出码 143" test "$rc" -eq 143
expect "7d SIGTERM 中断后无暂存残留" tmp_clean "$r"
expect "7e SIGTERM 中断后未落盘 App" test ! -e "$r/MeowAPPs/dev.test.slow"
else
skip "7c-e SIGTERM 中断清理" "解包在信号前已完成(rc=$rc)"
fi
rm -f "$WORK/b7/big.bin"
# ---- 8 索引路径:Package 取自清单 ----
r=$(sb 8)
mkdir -p "$WORK/repo"
mkpkg "$WORK/b8" os.meow.idx 3; pack "$WORK/b8" "$WORK/idx.mpk"
if sign os.meow.idx "$WORK/idx.mpk"; then
cp "$WORK/idx.mpk" "$WORK/repo/zzz-renamed-file.mpk" # 文件名与包名完全不同
printf 'local %s\n' "$WORK/repo" > "$r/MeowOS/opt/nyai/sources.list"
"$NYAI" index "$WORK/repo" >"$WORK/idx.log" 2>&1
expect "8a 索引 Package 字段来自 app.json(不是文件名)" \
grep -q '^Package: os.meow.idx$' "$WORK/repo/Packages"
out=$(nyai_run "$r" install os.meow.idx 2>&1); rc=$?
show "$out"
expect "8b 按索引包名安装成功" test $rc -eq 0 -a -f "$r/MeowAPPs/os.meow.idx/app.json"
expect "8c 索引安装后无残留" tmp_clean "$r"
else
skip "8 索引路径" "无签名私钥"
fi
# ---- 9 upgrade 复用 install 引擎 ----
r=$(sb 9)
mkpkg "$WORK/b9" dev.local.up 1; pack "$WORK/b9" "$WORK/up1.mpk"
nyai_run "$r" install "$WORK/up1.mpk" >/dev/null 2>&1
mkpkg "$WORK/b9" dev.local.up 2; pack "$WORK/b9" "$WORK/up2-any-name.bin"
out=$(nyai_run "$r" upgrade dev.local.up "$WORK/up2-any-name.bin" 2>&1); rc=$?
show "$out"
expect "9a upgrade 接受任意文件名(清单包名 == 目标包名)" \
test $rc -eq 0 -a "$(grep -o '"version_code":[0-9]*' "$r/MeowAPPs/dev.local.up/app.json")" = '"version_code":2'
expect "9b upgrade 后无残留" tmp_clean "$r"
mkpkg "$WORK/b9" dev.local.up 0; pack "$WORK/b9" "$WORK/up0.mpk"
out=$(nyai_run "$r" upgrade dev.local.up "$WORK/up0.mpk" 2>&1); rc=$?
show "$out"
expect "9c 降级被拒" sh -c 'test $1 -ne 0 && printf "%s" "$2" | grep -q "不允许降级"' _ "$rc" "$out"
mkpkg "$WORK/b9" dev.local.other 3; pack "$WORK/b9" "$WORK/upother.mpk"
out=$(nyai_run "$r" upgrade dev.local.up "$WORK/upother.mpk" 2>&1); rc=$?
show "$out"
expect "9d 清单包名 ≠ upgrade 目标 → 拒" \
sh -c 'test $1 -ne 0 && printf "%s" "$2" | grep -q "与目标包名(dev.local.up) 不一致"' _ "$rc" "$out"
expect "9e 以上失败均无残留" tmp_clean "$r"
# ---- 10 评审 r1 回归:大小写绕过(P1)/ 尾点 / 名字冲突 / 成员名 / 解压炸弹 ----
# 10a 先装一个合法签名系统包,再试图用大写包名顶掉它(exFAT 名字不区分大小写)
if [ -n "$KEY" ]; then
r=$(sb 10a); mkpkg "$WORK/b10a" os.meow.demo 1; pack "$WORK/b10a" "$WORK/demo10a.mpk"
sign os.meow.demo "$WORK/demo10a.mpk"
nyai_run "$r" install "$WORK/demo10a.mpk" >/dev/null 2>&1
before=$(md5sum "$r/MeowAPPs/os.meow.demo/app.json" | cut -d' ' -f1)
mkpkg "$WORK/b10ae" "OS.MEOW.DEMO" 99; pack "$WORK/b10ae" "$WORK/evil-case.mpk"
out=$(nyai_run "$r" install "$WORK/evil-case.mpk" 2>&1); rc=$?
show "$out"
after=$(md5sum "$r/MeowAPPs/os.meow.demo/app.json" | cut -d' ' -f1)
expect "10a 大写包名 OS.MEOW.DEMO 被拒(valid_pkg 只收小写)" test $rc -ne 0
expect "10a 错误文案指向 app.json.package 非法" \
sh -c 'printf "%s" "$1" | grep -q "app.json.package 非法"' _ "$out"
expect "10a 已装 os.meow.demo 未被顶掉(app.json 未变)" test "$before" = "$after"
expect "10a 未生成大写目录" test ! -e "$r/MeowAPPs/OS.MEOW.DEMO"
expect "10a 无残留" tmp_clean "$r"
else
skip "10a 大小写绕过" "无签名私钥"
fi
# 10b 尾点包名(exFAT/Windows 尾点映射怪癖)
r=$(sb 10b); mkpkg "$WORK/b10b" "dev.test." 1; pack "$WORK/b10b" "$WORK/trailing-dot.mpk"
out=$(nyai_run "$r" install "$WORK/trailing-dot.mpk" 2>&1); rc=$?
show "$out"
expect "10b 尾点包名 dev.test. 被拒" \
sh -c 'test $1 -ne 0 && printf "%s" "$2" | grep -q "app.json.package 非法"' _ "$rc" "$out"
expect "10b 无残留" tmp_clean "$r"
# 10c 已存在仅大小写不同的目录 → 部署前拦(exFAT 上会顶掉已装 App)
r=$(sb 10c); mkdir -p "$r/MeowAPPs/DEV.TEST.CLASH"
mkpkg "$WORK/b10c" dev.test.clash 1; pack "$WORK/b10c" "$WORK/clash.mpk"
out=$(nyai_run "$r" install "$WORK/clash.mpk" 2>&1); rc=$?
show "$out"
expect "10c 与已装目录仅大小写不同 → 拒装" \
sh -c 'test $1 -ne 0 && printf "%s" "$2" | grep -q "仅大小写不同"' _ "$rc" "$out"
expect "10c 已存在的目录未被改动" test -d "$r/MeowAPPs/DEV.TEST.CLASH"
expect "10c 无残留" tmp_clean "$r"
# 10d tar 成员名净化(不依赖外部 tar 的路径处理)
if command -v python3 >/dev/null 2>&1; then
for nm in "/tmp/evil.txt:dev.evil.abs" "../evil.txt:dev.evil.dd" "-evil.txt:dev.evil.dash"; do
member=${nm%%:*}; pkg=${nm##*:}
r=$(sb "10d-$pkg")
mk_evil_tar "$WORK/evil-member.mpk" "$member" "$pkg"
out=$(nyai_run "$r" install "$WORK/evil-member.mpk" 2>&1); rc=$?
show "$out"
expect "10d [$member] 成员名非法 → 拒装" \
sh -c 'test $1 -ne 0 && printf "%s" "$2" | grep -q "包成员路径非法"' _ "$rc" "$out"
expect "10d [$member] 无残留且未落盘" test ! -e "$r/MeowAPPs/$pkg" -a -z "$(find "$r/MeowOS/tmp/pkgs" -mindepth 1 2>/dev/null)"
done
# 10e 控制字符成员名(tar -tv 可能把它转义成可见文本 → 靠解包后的 on-disk 名字复核)
r=$(sb 10e)
python3 - "$WORK/evil-ctrl.mpk" <<'PY'
import sys, tarfile, io, gzip
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode='w') as t:
ti = tarfile.TarInfo('ev\x01il.txt'); ti.size = 3; t.addfile(ti, io.BytesIO(b'pwn'))
j = b'{"name":"X","package":"dev.evil.ctrl","exec":"x","version":"1","version_code":1}'
tj = tarfile.TarInfo('app.json'); tj.size = len(j); t.addfile(tj, io.BytesIO(j))
open(sys.argv[1], 'wb').write(gzip.compress(buf.getvalue()))
PY
out=$(nyai_run "$r" install "$WORK/evil-ctrl.mpk" 2>&1); rc=$?
show "$out"
expect "10e 控制字符成员名 → 拒装" \
sh -c 'test $1 -ne 0 && printf "%s" "$2" | grep -q "控制字符"' _ "$rc" "$out"
expect "10e 无残留" tmp_clean "$r"
# 10f 解压炸弹:成员头声明 700MB(上限 512MB)
r=$(sb 10f)
mk_bomb_tar "$WORK/bomb.mpk"
out=$(nyai_run "$r" install "$WORK/bomb.mpk" 2>&1); rc=$?
show "$out"
expect "10f 解压炸弹(700MB)→ 拒装" \
sh -c 'test $1 -ne 0 && printf "%s" "$2" | grep -q "防解压炸弹"' _ "$rc" "$out"
expect "10f 无残留" tmp_clean "$r"
else
skip "10d-f 成员名/解压炸弹" "无 python3"
fi
# ---------------- 汇总 ----------------
printf '\n== summary: %d passed, %d failed, %d skipped ==\n' "$PASS" "$FAIL" "$SKIP"
[ "$FAIL" -eq 0 ] || exit 1
exit 0