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 请求;只改源码,未提交二进制。
This commit is contained in:
AskaEth committed 2026-11-12 08:50:10 +08:00
1 parent 952a126477
commit 7b32d775c0
12 files changed
+812 -71

No files matched your search

+107 -5
View File
@@ -11,6 +11,9 @@
* 只读观测(wlan_connected()/hci_powered() 读 /sys 链路态)仍在 net.c;此处只接权威操作。
*/
#include "meow_launcher_internal.h"
/* M64 P1-1:SSID/PSK 校验与 wpa 字面量转义。与 netmgrd 共用同一头文件——回退路径只修一处
* 等于没修(netmgrd 不可达时攻击者正是走这里),两边必须同一套规则。 */
#include "../meow_netmgrd/nm_wifi_args.h"
#define NETMGR_SOCK_DEFAULT "/usr/data/meow_net.sock"
/* NETMGR_UNREACH 定义在 meow_launcher_internal.h(测试/调用方共用) */
@@ -424,6 +427,88 @@ static int nm_direct_wpa(char *out, int cap, const char *args, int tmo)
return l;
}
/* ---- M64 P1-1:外部可控字符串(SSID/PSK)只走这一条,execv 传 argv,不经过 shell ----
* 修复前:nm_direct_wpa("set_network %d ssid '\"%s\"'", id, ssid) → popen(sh -c)。
* 恶意 AP 广播 SSID=`a' ; id ; '` 即可 root 执行。现在引号/分号/反引号只是 argv 里的字节。 */
static int nm_wpa_bin(char *out, size_t cap)
{
static const char *dirs[] = { "/sbin", "/usr/sbin", "/bin", "/usr/bin", NULL };
for (int i = 0; dirs[i]; i++) {
snprintf(out, cap, "%s/wpa_cli", dirs[i]);
if (access(out, X_OK) == 0) return 0;
}
return -1;
}
static long nm_mono_ms(void)
{
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
return tp.tv_sec * 1000L + tp.tv_nsec / 1000000L;
}
/* 执行 `wpa_cli -i wlan0 <args...>`(execv,无 shell)。args 以 NULL 结尾。
* 返回 stdout 字节数(-1=起不来),tmo 内有界收割,语义对齐 nm_direct_wpa。 */
static int nm_direct_wpa_exec(char *out, int cap, char *const *args, int tmo)
{
if (cap > 0) out[0] = 0;
char bin[128];
if (nm_wpa_bin(bin, sizeof bin) < 0) return -1;
char *argv[10];
int n = 0;
argv[n++] = bin;
argv[n++] = "-i";
argv[n++] = "wlan0";
for (int i = 0; args[i] && n < (int)(sizeof argv / sizeof argv[0]) - 1; i++) argv[n++] = args[i];
argv[n] = NULL;
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);
int dn = open("/dev/null", O_WRONLY);
if (dn >= 0) { dup2(dn, 2); close(dn); }
close(pfd[0]); close(pfd[1]);
execv(bin, argv);
_exit(127);
}
close(pfd[1]);
int off = 0, timed = 0;
long deadline = nm_mono_ms() + (tmo > 0 ? tmo : 1);
while (off < cap - 1) {
long remain = deadline - nm_mono_ms();
if (remain <= 0) { timed = 1; break; }
struct pollfd pf = { pfd[0], POLLIN, 0 };
int pr = poll(&pf, 1, (int)remain);
if (pr < 0) { if (errno == EINTR) continue; timed = 1; break; }
if (pr == 0) { timed = 1; break; }
ssize_t rd = read(pfd[0], out + off, (size_t)(cap - 1 - off));
if (rd <= 0) break;
off += (int)rd;
}
out[off] = 0;
close(pfd[0]);
if (timed) kill(pid, SIGKILL);
int st = 0;
waitpid(pid, &st, 0);
return off;
}
/* set_network <id> <field> <字符串值>:值按 wpa_supplicant 语法加引号(转义 `"`/`\`),
* 整条命令以 argv 传入。value 必须先过 nm_wifi_ssid_ok()/nm_wifi_psk_ok()。 */
static int nm_direct_wpa_set(char *out, int cap, int id, const char *field, const char *val, int tmo)
{
char idbuf[16], quoted[128];
char *args[5];
if (nm_wpa_set_argv(idbuf, sizeof idbuf, quoted, sizeof quoted, id, field, val, args) < 0) {
if (cap > 0) out[0] = 0;
return -1;
}
return nm_direct_wpa_exec(out, cap, args, tmo);
}
static int nm_direct_save_config(void)
{
char out[128];
@@ -436,6 +521,15 @@ static int nm_direct_save_config(void)
static int nm_direct_connect_ex(const char *ssid, const char *psk, int hidden, char *err, int elen)
{
char out[1024]; char args[256];
/* M64 P1-1:回退路径自己再校验一次(不依赖调用方;两条路径同一套规则)。 */
if (!nm_wifi_ssid_ok(ssid)) {
if (err) snprintf(err, elen, "ssid 非法(长度 1..32 字节,禁控制字符)");
return MEOW_ERR_INVAL;
}
if (!nm_wifi_psk_ok(psk)) {
if (err) snprintf(err, elen, "psk 非法(≤63 字符口令或 64 位 hex,禁控制字符)");
return MEOW_ERR_INVAL;
}
int id = -1;
if (nm_direct_wpa(out, sizeof out, "list_networks", 1500) > 0) {
char *ln = strtok(out, "\n");
@@ -472,13 +566,12 @@ static int nm_direct_connect_ex(const char *ssid, const char *psk, int hidden, c
id = atoi(out);
if (id < 0) { if (err) snprintf(err, elen, "add_network 无 id"); return MEOW_ERR_INVAL; }
created = 1;
snprintf(args, sizeof args, "set_network %d ssid '\"%s\"'", id, ssid);
nm_direct_wpa(out, sizeof out, args, 1500);
if (psk && psk[0]) { snprintf(args, sizeof args, "set_network %d psk '\"%s\"'", id, psk); nm_direct_wpa(out, sizeof out, args, 1500); }
/* M64 P1-1:ssid/psk 走 execv(nm_direct_wpa_set),不再拼 popen(sh -c)。 */
nm_direct_wpa_set(out, sizeof out, id, "ssid", ssid, 1500);
if (psk && psk[0]) { nm_direct_wpa_set(out, sizeof out, id, "psk", psk, 1500); }
else { snprintf(args, sizeof args, "set_network %d key_mgmt NONE", id); nm_direct_wpa(out, sizeof out, args, 1500); }
} else if (psk && psk[0]) {
snprintf(args, sizeof args, "set_network %d psk '\"%s\"'", id, psk);
nm_direct_wpa(out, sizeof out, args, 1500);
nm_direct_wpa_set(out, sizeof out, id, "psk", psk, 1500);
}
if (hidden) { snprintf(args, sizeof args, "set_network %d scan_ssid 1", id); nm_direct_wpa(out, sizeof out, args, 1500); }
snprintf(args, sizeof args, "enable_network %d", id); nm_direct_wpa(out, sizeof out, args, 1500);
@@ -519,6 +612,15 @@ static int nm_direct_connect_ex(const char *ssid, const char *psk, int hidden, c
int nm_wifi_connect_ex(const char *ssid, const char *psk, int hidden, char *err, int elen)
{
if (!ssid || !ssid[0]) { if (err) snprintf(err, elen, "ssid 缺失"); return MEOW_ERR_INVAL; }
/* M64 P1-1:IPC 路径与直连回退同一套校验(netmgrd 侧还会再校验一次)。 */
if (!nm_wifi_ssid_ok(ssid)) {
if (err) snprintf(err, elen, "ssid 非法(长度 1..32 字节,禁控制字符)");
return MEOW_ERR_INVAL;
}
if (!nm_wifi_psk_ok(psk)) {
if (err) snprintf(err, elen, "psk 非法(≤63 字符口令或 64 位 hex,禁控制字符)");
return MEOW_ERR_INVAL;
}
char arg[132];
snprintf(arg, sizeof arg, "%s,%s", ssid, psk ? psk : ""); /* 带空 psk → netmgrd 复用既有网络 */
char text[192];