Files
MeowOS-For-R3P2/patch_v2/meowpanel/test_mp_argsafe.c
T
AskaEth 7b32d775c0 fix(security): M64 P1 三条 root 命令注入 — 外部串改 execv 传 argv、电源键下线、press_ms clamp
P1-1 Wi-Fi SSID/PSK 注入(netmgrd + launcherd 回退两处同步):
- 不再拼 `set_network %d ssid '"%s"'` 交给 sh -c;新增 wpa_exec()/nm_direct_wpa_exec()
  以 fork+execv 传 argv(含 resolve_bin 解析绝对路径,替代 `PATH=...` 前缀)。
- 共用 nm_wifi_args.h:nm_wifi_ssid_ok/n_wifi_psk_ok/nm_wpa_quote/nm_wpa_set_argv,
  两份实现同一套规则(只修一处等于没修)。
- 约束层:SSID 1..32B、禁控制字符;PSK ≤63 或 64 hex、禁控制字符;`"`/`\` 按
  wpa_supplicant 语法转义。shell 元字符不再需要拒绝——execv 下没有 shell 解析。

P1-2 MeowPanel App 安装/导出/卸载注入:
- install/export/remove 改 run_argv()(fork+execv,PATH 只在子进程 setenv)。
- 包名统一 mp_pkg_ok()(仅 [a-z0-9._-],拒 `..`,与 nyai.c valid_pkg 同规则)→ 修 P2-7。
- `{path}` 追加 mp_path_unsafe()(与 mp_fonts/nyai 同一拒绝集)作第二道。

P1-3 /api/key/* 电源键:
- keycode_to_device 删除 KEY_POWER;116 进红线(403),前端遥控板电源/长按电源按钮移除。
- press_ms 全局 clamp 0..1000(单键与批量),`long` 缺省取上限。
- 按下→等待→抬起改独立线程(inject_key_async),不再阻塞单线程 8080 服务。

测试(宿主,反向控制):test_nm_wifi_args.c / test_mp_argsafe.c —— 同一套审计恶意串
(`a' ; id ; '`、`x';id;'`、`/tmp/a';id>/tmp/pwn;'.mpk`、`MeowAPPs/..`)在修复前实现下
注入成功(stdout 含 uid=)/放行,在修复后实现下不执行/拒绝。另含「两处实现同步」检查。

未刷机、未连设备、未发 /api/key 请求;只改源码,未提交二进制。
2026-11-12 08:50:10 +08:00

116 lines
5.8 KiB
C
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.
/* test_mp_argsafe.c — M64 P1-2 / P1-3 宿主回归:App 安装路径与包名、按键时长与键码红线。
*
* 纯宿主测试:不连设备、不启面板服务、不发 /api/key/<code> 请求。
*
* 编译运行(在 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}