Files
MeowOS-For-R3P2/patch_v2/meowpkg/test_nyai.sh
T
AskaEth 829d4475e8 feat(nyai): M35 包身份以已签名 app.json 清单为准 + 暂存迁到 SD 并必清理
包名/版本/依赖不再从 .mpk 文件名推导(用户定案:不对安装包文件名强依赖,
一切信息从清单取),改为解包后读 app.json.package 作唯一权威:
  - 落地目录 MeowAPPs/<pkg>、meow_verify --package <pkg>、防降级基准全用清单包名;
  - 新增 footer.package == app.json.package 一致性检查,不一致拒装并同时报两个名字;
  - valid_pkg 拒绝 "." / ".."(此前 MeowAPPs/.. 可越出安装目录并被 rm_rf);
  - index 的 Package 字段同样取自清单(缺 package 的包跳过并告警);
  - scan/export 同步;--help 与错误文案去掉「需要 <pkg>.mpk」。

暂存:MeowAPPs/.stg_<pid>_<pkg>(/usr/data NAND,仅剩 4MB)
  → <MeowROOT>/MeowOS/tmp/pkgs/<pkg>-<UTC时间戳>-<pid>-<n>/(真机即 SD 卡):
  - mkdir 原子占位 + 失败重试 + pid/序号保证唯一,时间戳只给人看;
  - SD 未挂载/不可写直接拒装(不回退 /usr/data:目标本就在 SD,且跨 fs rename 会 EXDEV);
  - 解包前后各一道防护:tar -tv 只允许普通文件/目录(拒符号链接/硬链接/设备/FIFO),
    解包后复核暂存树(非普通文件、nlink>1 一律拒);
  - 中央化 cleanup + SIGINT/SIGTERM/SIGHUP handler(先收子进程组再清)+ atexit 兜底,
    成功/验签失败/清单非法/降级/解压失败/被中断全部删除暂存目录。

真机 time() 受 zig 0.17 mipsel time64 ABI 缺陷影响返回垃圾(实测 3381223399391169494、
localtime_r=NULL),故时间戳直取内核 o32 time32(syscall 4263)并自行换算 UTC,
不依赖 libc time()/localtime_r()。

upgrade 复用 install 同一实现(清单包名必须等于目标包名、目标必须已装)。

新增 host 回归 patch_v2/meowpkg/test_nyai.sh(49 断言:改名包/真实 0.1.7 反例/
footer≠清单/os.meow 无签名/第三方无签名/恶意包名/解压失败/SIGTERM/索引路径/upgrade)。

真机验证(固件 0.8.48,二进制只放 MeowOS/tmp,未覆盖 /usr/bin/nyai):
os.meow.reader-0.1.4.bak.mpk 现在「签名校验通过 (package=os.meow.reader)」后才被
降级门禁拒绝(旧版此处是包名不符);weird-name.bin 装成 dev.m35.probe;SIGTERM
中断后暂存目录为空;Music=199164544、/usr/bin/nyai md5 a7d24553… 均未变。
2026-11-10 21:42:37 +08:00

245 lines
11 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" .; }
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 =="
# ---- 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"
# ---------------- 汇总 ----------------
printf '\n== summary: %d passed, %d failed, %d skipped ==\n' "$PASS" "$FAIL" "$SKIP"
[ "$FAIL" -eq 0 ] || exit 1
exit 0