Files
MeowOS-For-R3P2/patch_v2/meow_netmgrd/test_nm_wifi_args.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

182 lines
8.0 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_nm_wifi_args.c — M64 P1-1 宿主回归:SSID/PSK 参数安全 + 反向控制。
*
* 纯宿主测试:不连设备、不启服务、不发 /api/key/<code>,只用本进程 + sh + execv。
*
* 编译运行(在 patch_v2/meow_netmgrd 下):
* cc -O1 -Wall -Wextra -I. test_nm_wifi_args.c -o ~/tmp/test_nm_wifi_args
* ~/tmp/test_nm_wifi_args
*
* 反向控制:同一套用例分别喂给
* ① 「修复前实现」复刻(legacy_build:`set_network %d ssid '"%s"'` → sh -c)→ 必须注入成功;
* ② 修复后实现(nm_wifi_ssid_ok/nm_wpa_set_argv + execv 传 argv)→ 必须不执行任何 shell。
* 只有 ①成立、②不成立,才证明这套改动真的拦住了东西(而不是「本来就不会中」)。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include "nm_wifi_args.h"
static char g_self[512];
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)
/* 复刻修复前 meow_netmgrd.c:3154 + :3307 的拼法:SSID 塞进单引号内的双引号,整行交给 sh -c。
* (值是 `a' ; id ; '` 时,第一个 `'` 就把单引号闭合了。) */
static void legacy_build(const char *field, const char *val, char *out, size_t cap)
{
char args[256];
snprintf(args, sizeof args, "set_network 0 %s '\"%s\"'", field, val);
snprintf(out, cap, "PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli %s 2>/dev/null", args);
}
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);
}
/* 用 execv 跑一条 argv,子进程是本程序的 --stub-argv 形态(把收到的 argv 原样打印)。
* 这就是修复后的下发方式:wpa_cli 收到的是 argv,不经 shell。 */
static int run_argv_stub(char *const *args, char *out, int cap)
{
out[0] = 0;
int pfd[2];
if (pipe(pfd) < 0) return -1;
pid_t pid = fork();
if (pid < 0) { close(pfd[0]); close(pfd[1]); return -1; }
if (pid == 0) {
dup2(pfd[1], 1);
dup2(pfd[1], 2);
close(pfd[0]); close(pfd[1]);
char *av[10];
int n = 0;
av[n++] = (char *)"test_nm_wifi_args";
av[n++] = (char *)"--stub-argv";
for (int i = 0; args[i] && n < 9; i++) av[n++] = args[i];
av[n] = NULL;
execv(g_self, av);
_exit(127);
}
close(pfd[1]);
size_t off = 0;
for (;;) {
size_t room = (off + 1 < (size_t)cap) ? (size_t)cap - 1 - off : 0;
if (!room) break;
ssize_t rd = read(pfd[0], out + off, room);
if (rd <= 0) break;
off += (size_t)rd;
}
out[off] = 0;
close(pfd[0]);
int st = 0;
waitpid(pid, &st, 0);
return st;
}
/* 旧实现「接受」判定:str_capsafe(meow_netmgrd.c:3140)只 trim 空白,不校验字符。 */
static int legacy_ssid_accepted(const char *s)
{
return s && s[0];
}
/* 一条用例的双跑:旧实现必须中招,新实现必须只把值当字节。 */
static void case_attack(const char *label, const char *field, const char *val)
{
char cmd[512], out[4096];
printf("== %s ==\n value=%s\n", label, val);
/* ① 反向控制:修复前 */
CHECK(legacy_ssid_accepted(val), "修复前判定放行该值(无字符校验)");
legacy_build(field, val, cmd, sizeof cmd);
printf(" 修复前命令:%s\n", cmd);
run_shell(cmd, out, sizeof out);
CHECK(strstr(out, "uid=") != NULL, "修复前被注入:值里的 `id` 在 root 语境执行(stdout 含 uid=)");
/* ② 修复后 */
char idbuf[16], quoted[160];
char *args[5];
int rc = nm_wpa_set_argv(idbuf, sizeof idbuf, quoted, sizeof quoted, 0, field, val, args);
CHECK(rc == 0, "修复后可构造 argv(execv 传参,值里的元字符无 shell 语义)");
char *full[7] = { (char *)"wpa_cli", args[0], args[1], args[2], args[3], NULL };
run_argv_stub(full, out, sizeof out);
printf(" 修复后子进程收到的 argv:\n%s", out);
CHECK(strstr(out, "uid=") == NULL, "修复后没有 shell:值里的 `id` 不执行");
CHECK(strstr(out, quoted) != NULL, "值原样作为**单个** argv 元素到达(未断词)");
}
int main(int argc, char **argv)
{
if (argc > 1 && !strcmp(argv[1], "--stub-argv")) { /* execv 桩:打印收到的 argv */
for (int i = 2; i < argc; i++) printf("ARG[%d]=%s\n", i - 2, argv[i]);
return 0;
}
ssize_t l = readlink("/proc/self/exe", g_self, sizeof g_self - 1);
if (l <= 0) snprintf(g_self, sizeof g_self, "%s", argv[0]);
else g_self[l] = 0;
printf("== M64 P1-1 反向控制:修复前注入 / 修复后不注入 ==\n");
/* 审计给出的真实恶意串:附近 AP 广播的 SSID 与用户输入的 PSK。 */
case_attack("SSID 注入(审计原串)", "ssid", "a' ; id ; '");
case_attack("PSK 注入(审计原串)", "psk", "x';id;'");
printf("== 约束层:长度 / 控制字符 ==\n");
CHECK(nm_wifi_ssid_ok("a' ; id ; '") == 1, "`a' ; id ; '` 通过校验(11B<32B,是合法 SSID;靠 execv 中立化)");
CHECK(nm_wifi_ssid_ok("Home2G") == 1, "普通 SSID 通过");
CHECK(nm_wifi_ssid_ok("中文网络") == 1, "UTF-8 中文 SSID 通过(12B)");
CHECK(nm_wifi_ssid_ok("") == 0, "空 SSID 拒绝");
CHECK(nm_wifi_ssid_ok("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") == 0, "33 字节 SSID 拒绝(802.11 上限 32)");
CHECK(nm_wifi_ssid_ok("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") == 1, "32 字节 SSID 通过");
CHECK(nm_wifi_ssid_ok("a\nset_network 0 psk \"x\"") == 0, "含 \\n 的 SSID 拒绝(wpa ctrl_iface 行协议)");
CHECK(nm_wifi_ssid_ok("a\x7f" "b") == 0, "含 DEL 的 SSID 拒绝");
CHECK(nm_wifi_ssid_ok("a\tb") == 0, "含 TAB 的 SSID 拒绝");
CHECK(nm_wifi_psk_ok("") == 1, "空 PSK(开放网络)通过");
CHECK(nm_wifi_psk_ok("pass1234") == 1, "普通口令通过");
CHECK(nm_wifi_psk_ok("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") == 1,
"64 位 hex PSK 通过");
CHECK(nm_wifi_psk_ok("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz") == 0,
"64 字符非 hex 拒绝(≠ 合法 PSK)");
CHECK(nm_wifi_psk_ok("x';id;'") == 1, "`x';id;'` 通过(=合法口令字节,靠 execv 中立化)");
CHECK(nm_wifi_psk_ok("a\nb") == 0, "含 \\n 的 PSK 拒绝");
printf("== wpa_supplicant 字面量:只转义 \" 与 \\ ==\n");
{
char q[64];
CHECK(nm_wpa_quote("a\"b\\c", q, sizeof q) == 0 && !strcmp(q, "\"a\\\"b\\\\c\""),
"`a\"b\\c` → %s", q);
CHECK(nm_wpa_quote("a' ; id ; '", q, sizeof q) == 0 && !strcmp(q, "\"a' ; id ; '\""),
"shell 元字符原样保留 → %s", q);
CHECK(nm_wpa_quote("a\nb", q, sizeof q) == -1, "控制字符拒绝(不落盘、不进 ctrl_iface)");
CHECK(nm_wpa_quote("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", q, 8) == -1, "缓冲不足返回 -1");
}
printf("== 两处实现同步(只修一处等于没修)==\n");
{
static const char *files[] = { "meow_netmgrd.c", "../meow_launcherd/netmgr_ipc.c" };
for (int i = 0; i < 2; i++) {
FILE *f = fopen(files[i], "r");
if (!f) { printf(" SKIP: 读不到 %s(从 patch_v2/meow_netmgrd 下运行才检查)\n", files[i]); continue; }
char buf[400000];
size_t n = fread(buf, 1, sizeof buf - 1, f);
buf[n] = 0;
fclose(f);
CHECK(strstr(buf, "nm_wpa_set_argv") != NULL, "%s 使用共享的 nm_wpa_set_argv", files[i]);
CHECK(strstr(buf, "ssid '\"") == NULL, "%s 不再有 `ssid '\\\"` 拼法", files[i]);
}
}
printf(g_fail ? "\nRESULT: FAIL\n" : "\nRESULT: ALL PASS\n");
return g_fail ? 1 : 0;
}