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),本轮
**未做真机复核**,仅宿主验证。
358 lines
17 KiB
Bash
Executable File
358 lines
17 KiB
Bash
Executable File
#!/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
|