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 请求;只改源码,未提交二进制。
116 lines
5.8 KiB
C
116 lines
5.8 KiB
C
/* 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;
|
||
}
|