/* test_mp_argsafe.c — M64 P1-2 / P1-3 宿主回归:App 安装路径与包名、按键时长与键码红线。 * * 纯宿主测试:不连设备、不启面板服务、不发 /api/key/ 请求。 * * 编译运行(在 patch_v2/meowpanel 下): * cc -O1 -Wall -Wextra -I src test_mp_argsafe.c -o ~/tmp/test_mp_argsafe * ~/tmp/test_mp_argsafe * * 反向控制:同一套用例分别喂给 * ① 「修复前实现」复刻(legacy_pkg_accepts = mp_apps.c:228 旧判定;legacy_path_build = * mp_apps.c:168 旧拼后再 popen)→ 必须放行/注入成功; * ② 修复后实现(mp_pkg_ok/mp_path_unsafe/mp_key_clamp_ms/mp_key_forbidden)→ 必须拒绝。 */ #include #include #include #include #include "mp_argsafe.h" static int g_fail; #define CHECK(cond, ...) do { \ if (!(cond)) { printf(" FAIL: "); printf(__VA_ARGS__); printf("\n"); g_fail = 1; } \ else { printf(" PASS: "); printf(__VA_ARGS__); printf("\n"); } \ } while (0) /* 修复前 mp_apps.c:118/208/228 的包名判定:只拒 `/` 与 `:`。 */ static int legacy_pkg_accepts(const char *pkg) { return pkg && !strchr(pkg, '/') && !strchr(pkg, ':'); } /* 修复前 mp_apps.c:168 的拼法 + mp_util.c:run_cmd 的 popen。 */ static void legacy_path_build(const char *mpk, char *out, size_t cap) { snprintf(out, cap, "PATH=/bin:/sbin:/usr/bin:/usr/sbin '%s' install '%s' 2>&1", "/usr/bin/nyai", mpk); } static int run_shell(const char *cmd, char *out, int cap) { out[0] = 0; FILE *f = popen(cmd, "r"); if (!f) return -1; size_t off = fread(out, 1, (size_t)cap - 1, f); out[off] = 0; return pclose(f); } int main(void) { printf("== M64 P1-2 反向控制:包名 ==================================================\n"); /* 审计给出的真实恶意串。 */ static const char *bad_pkgs[] = { "x';id;'", "MeowAPPs/..", "..", ".", "..foo", "foo.", "OS.MEOW.READER", "os.meow.reader/../x", "a b", "" }; for (unsigned i = 0; i < sizeof bad_pkgs / sizeof bad_pkgs[0]; i++) { const char *p = bad_pkgs[i]; int old_acc = legacy_pkg_accepts(p); int new_acc = mp_pkg_ok(p); CHECK(!new_acc, "拒绝包名 `%s`(修复前:%s)", p, old_acc ? "放行 → 可注入" : "也拒"); } /* 反向控制的正向对照:旧判定确实放行了这两条。 */ CHECK(legacy_pkg_accepts("x';id;'") == 1, "反向控制:修复前放行 `x';id;'`(DELETE /api/apps/x';id;')"); /* P2-7 的穿越面是裸 `..`(拼接后 = MEOWAPPS_DIR/.. );`MeowAPPs/..` 含 `/` 反而被旧判定挡住。 */ CHECK(legacy_pkg_accepts("..") == 1, "反向控制:修复前放行裸 `..`(MEOWAPPS_DIR/.. 目录穿越)"); static const char *good_pkgs[] = { "os.meow.reader", "os.meow.app-1", "a_b-c.d", "reader" }; for (unsigned i = 0; i < sizeof good_pkgs / sizeof good_pkgs[0]; i++) CHECK(mp_pkg_ok(good_pkgs[i]) == 1, "接受合法包名 `%s`", good_pkgs[i]); printf("== M64 P1-2 反向控制:安装路径 ==============================================\n"); { /* 审计原串(含 `>` 重定向,写文件的那半句在宿主上不执行——只做字符串判定)。 */ const char *audit = "/tmp/a';id>/tmp/pwn;'.mpk"; CHECK(mp_path_unsafe(audit) == 1, "拒绝审计原路径 `%s`", audit); CHECK(mp_path_unsafe("/usr/data/mnt/sd_0/MeowOS/tmp/upload.mpk") == 0, "接受面板自己生成的上传临时路径"); CHECK(mp_path_unsafe("/sdcard/MeowApps/reader-v1.mpk") == 0, "接受普通路径"); /* 执行级证明:结构同审计串、但注入体换成 `id`(不写任何文件)。 * ① 修复前:值进 shell → `id` 执行。 */ const char *pwn = "/tmp/a';id;'.mpk"; char cmd[512], out[4096]; legacy_path_build(pwn, cmd, sizeof cmd); printf(" 修复前命令:%s\n", cmd); run_shell(cmd, out, sizeof out); CHECK(strstr(out, "uid=") != NULL, "修复前被注入:路径里的 `id` 执行(stdout 含 uid=)"); /* ② 修复后:同一串被 mp_path_unsafe 拒掉,且永远不会到达 shell。 */ CHECK(mp_path_unsafe(pwn) == 1, "修复后同一串在调用 execv 之前就被拒"); } printf("== M64 P1-3:press_ms clamp ================================================\n"); CHECK(mp_key_clamp_ms(-5) == 0, "-5 → 0"); CHECK(mp_key_clamp_ms(0) == 0, "0 → 0(注入侧按 80ms tap 节奏处理)"); CHECK(mp_key_clamp_ms(80) == 80, "80 → 80"); CHECK(mp_key_clamp_ms(1000) == 1000, "1000 → 1000(上限)"); CHECK(mp_key_clamp_ms(3000) == 1000, "3000 → 1000(前端旧 `long` 默认值被收口)"); CHECK(mp_key_clamp_ms(8000) == 1000, "8000 → 1000(设备端 forceoff_ms 缺省值:一条请求即强制关机)"); CHECK(mp_key_clamp_ms(2147483647) == 1000, "INT_MAX → 1000"); printf("== M64 P1-3:键码红线 ======================================================\n"); /* 修复前 mp_key.c:78 只拦 158/172/32/28/96。 */ static const int codes[] = { 116, 158, 172, 32, 28, 96 }; for (unsigned i = 0; i < sizeof codes / sizeof codes[0]; i++) CHECK(mp_key_forbidden(codes[i]) == 1, "禁止键码 %d", codes[i]); CHECK(116 == 116 && !(116 == 158 || 116 == 172 || 116 == 32 || 116 == 28 || 116 == 96), "反向控制:修复前的红线**放行** 116(KEY_POWER)"); static const int allowed[] = { 114, 115, 163, 164, 165 }; for (unsigned i = 0; i < sizeof allowed / sizeof allowed[0]; i++) CHECK(mp_key_forbidden(allowed[i]) == 0, "允许键码 %d(音量/上下曲/播放)", allowed[i]); printf(g_fail ? "\nRESULT: FAIL\n" : "\nRESULT: ALL PASS\n"); return g_fail ? 1 : 0; }