diff --git a/patch_v2/meow_sign/README.md b/patch_v2/meow_sign/README.md index e90aa85..46bc393 100644 --- a/patch_v2/meow_sign/README.md +++ b/patch_v2/meow_sign/README.md @@ -130,3 +130,16 @@ static const uint8_t g_meow_pub[MEOW_PUB_KEY_COUNT][32] = { | `../meow_verify/meow_signing_pub.h` | **固件内嵌公钥数组**(仅公钥,可入库) | | `../meow_verify/meow_sig.h` | 签名块格式 + 被签消息构造(verifier/signer 共用) | | `.keys/` | 私钥目录(**已 gitignore,绝不入库**) | + +## 8. 端到端实走(真机已验证,2026-08-31) + +> 完整「产签→验签→真机安装→导出保留签名」的可复现步骤、逐命令预期输出、6 个失败矩阵项、常见错误速查与本次修复,见 +> `devdocs/plan/MPK_SIGNATURE_DESIGN.md` 的「产签实走记录 / 操作步骤(端到端)」节。此处只记三个**最容易踩的坑**: + +1. **`.mpk` 文件内文件须在 tar 根**:`app.json`/`meow`/资源须并列于 tar 根(**无** `/` 顶层包裹目录)。 + 打包用 `tar -C .`(导出生成本就是 `tar -C src .`),否则设备安装报 `解压/校验 app.json 失败`。 +2. **包名 = 文件名茎**:`meow_sign sign` 从 `.mpk` 文件名茎推导包名并写入 footer;`.mpk` 必须 == `app.json.package`。 + 改文件名/包名 → 验签 `package mismatch`。 +3. **`exec` 可为绝对路径,导出须相对化**:真实 app 的 `app.json.exec` 是绝对路径(`launcher` 直接 `execve`)。 + `nyai export` 须把 `exec` 相对化到包目录(已修 `nyai.c` 的 `exec_rel_to_pkg`)再存在性检查/白名单打包; + 否则所有真实 app 导出报「exec 文件 … 不存在或不可执行」。 diff --git a/patch_v2/meowpkg/nyai b/patch_v2/meowpkg/nyai index 73cb408..c2599fe 100755 Binary files a/patch_v2/meowpkg/nyai and b/patch_v2/meowpkg/nyai differ diff --git a/patch_v2/meowpkg/nyai.c b/patch_v2/meowpkg/nyai.c index 11793fd..5b35b98 100644 --- a/patch_v2/meowpkg/nyai.c +++ b/patch_v2/meowpkg/nyai.c @@ -840,7 +840,7 @@ static int install_mpk_file(const char *mpk_path) rm_rf(stg); if (mkdir_p(stg)!=0) { fprintf(stderr,"nyai: install: 无法建临时目录 %s\n", stg); return 1; } char cmd[1024]; - snprintf(cmd,sizeof cmd,"gzip -dc '%s' | tar -x -C '%s' 2>/dev/null && [ -f '%s/app.json' ]", mpk_path, stg, stg); + snprintf(cmd,sizeof cmd,"gzip -dc '%s' 2>/dev/null | tar -x -C '%s' 2>/dev/null && [ -f '%s/app.json' ]", mpk_path, stg, stg); printf("Preparing to unpack %s ...\n", pkg); if (system(cmd)!=0) { fprintf(stderr,"nyai: install: 解压/校验 app.json 失败(%s 可能损坏或非 .mpk)\n", mpk_path); @@ -1065,6 +1065,36 @@ static int sanitize_member(const char *in, char *out, int n) snprintf(out,n,"%s", p); return 0; } + +/* 把 app.json 的 exec 路径相对化到包目录下(导出前存在性检查 + 白名单打包用)。 + * 约定(MEOW_APP_SDK 现有惯例,launcher 以绝对路径直接 execve): + * - exec 以本包目录 src/ 为前缀 → 取其后相对部分(同一 meow_root,如真机 src=.../MeowAPPs/os.meow.hello → hello); + * - exec 含 "/MeowAPPs//" 段 → 取该段后的相对路径(根路径与 src 不一致时仍可定位,如沙箱/CI 测试); + * - exec 为相对路径(无前导 /)→ 沿用 sanitize_member 语义(剥 ./、禁 ../、禁 shell 元字符); + * - 绝对路径但定位不到包目录段 → 非本包程序,返回 -1(拒绝导出)。 + * 返回 0 成功,-1 非法。 */ +static int exec_rel_to_pkg(const char *src, const char *exec, const char *pkg, char *out, int n) +{ + if (!exec || !*exec) return -1; + const char *rel = NULL; + size_t sl = strlen(src); + if (strncmp(exec, src, sl) == 0 && exec[sl] == '/') { + rel = exec + sl + 1; + } else if (pkg && *pkg) { + char marker[272]; snprintf(marker, sizeof marker, "/MeowAPPs/%s/", pkg); + const char *m = strstr(exec, marker); + if (m) rel = m + strlen(marker); + } + if (rel) { + size_t rl = strlen(rel); + if (rl == 0 || rl >= (size_t)n) return -1; + if (has_dotdot_comp(rel) || unsafe_path(rel)) return -1; + memcpy(out, rel, rl); out[rl] = 0; + return 0; + } + if (exec[0] != '/') return sanitize_member(exec, out, n); + return -1; +} /* 从 JSON 数组串(含 '[' ']')提取字符串成员;返回项数,负=格式错 */ static int json_array_strings(const char *arr, char items[][256], int max) { @@ -1147,9 +1177,9 @@ static int cmd_export(const char *pkg, const char *out) char appj[BUF]; snprintf(appj,sizeof appj,"%s/app.json", src); appmeta m; if (load_app_meta(appj,&m)!=0) { fprintf(stderr,"nyai: export: %s/app.json 不合法(缺 name 或 exec)\n", pkg); return 1; } - /* 导出前体检:exec 文件存在且可执行 */ + /* 导出前体检:exec 文件存在且可执行。exec 可为绝对(包目录下,相对化)或相对路径。 */ char exec_rel[BUF]; - if (sanitize_member(m.exec, exec_rel, sizeof exec_rel)!=0) { + if (exec_rel_to_pkg(src, m.exec, pkg, exec_rel, sizeof exec_rel)!=0) { fprintf(stderr,"nyai: export: exec 字段非法: %s\n", m.exec); return 1; } char exepath[BUF]; snprintf(exepath,sizeof exepath,"%s/%s", src, exec_rel); @@ -1246,7 +1276,7 @@ static int cmd_upgrade(const char *pkg, const char *file) if (rename(dst,bak)!=0) { fprintf(stderr,"nyai: upgrade: 备份失败: %s\n", strerror(errno)); return 1; } if (mkdir_p(dst)!=0) { rename(bak,dst); return 1; } char cmd[1024]; - snprintf(cmd,sizeof cmd,"gzip -dc '%s' | tar -x -C '%s' 2>/dev/null && [ -f '%s/app.json' ]", mpk,dst,dst); + snprintf(cmd,sizeof cmd,"gzip -dc '%s' 2>/dev/null | tar -x -C '%s' 2>/dev/null && [ -f '%s/app.json' ]", mpk,dst,dst); printf("Upgrading %s ...\n", pkg); if (system(cmd)!=0) { fprintf(stderr,"nyai: upgrade: 解压失败,回滚\n"); rm_rf(dst); rename(bak,dst); return 1; } char appj[BUF]; snprintf(appj,sizeof appj,"%s/app.json", dst); diff --git a/patch_v2/meowpkg/nyai_host b/patch_v2/meowpkg/nyai_host index 7620043..42fa0d2 100755 Binary files a/patch_v2/meowpkg/nyai_host and b/patch_v2/meowpkg/nyai_host differ