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

+108
View File
@@ -0,0 +1,108 @@
/* nm_wifi_args.h — wpa_cli 参数安全(M64 P1-1:SSID/PSK → root 命令注入)。
*
* 修复前:SSID/PSK 被拼成 `set_network <id> ssid '"<ssid>"'`,再交给
* `execl("/bin/sh","sh","-c",cmd)` 执行(meow_netmgrd.c:3307/3154/325;同一实现复制在
* meow_launcherd/netmgr_ipc.c:475 的直连回退)。单引号里套双引号,SSID 自己带 `'` 就能
* 闭合引号——附近恶意 AP 广播 SSID=`a' ; id ; '`,用户点连接即 root 执行。
*
* 修复分两层,本头只放「可在宿主上直接单测」的纯函数:
* 1) 彻底层:调用方改用 fork+execv 传 argv(见 meow_netmgrd.c:wpa_exec /
* netmgr_ipc.c:nm_direct_wpa_exec)。不再有 shell,`' ; ` $ & | < >` 全部只是
* 普通字节,既不需要也不敢拒绝("Bob's WiFi" 是合法 SSID)。
* 2) 约束层:SSID/PSK 按 802.11 与 wpa_supplicant 语法收口(长度、控制字符);
* 拼 wpa 字面量时转义 `"` 与 `\`——这两个字符破坏的是 supplicant 的引号解析,
* 与 shell 无关,属于正确性要求。
*
* 两份实现(netmgrd 与 launcherd 回退)共用本头:只修一处等于没修——launcherd 那条
* 路径正是 netmgrd 不可达时的回退,攻击者可以制造 netmgrd 不可达(M64 任务书 P1-1)。
*/
#ifndef NM_WIFI_ARGS_H
#define NM_WIFI_ARGS_H
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#define NM_WIFI_SSID_MAX 32 /* 802.11 SSID 字节上限 */
#define NM_WIFI_PSK_MAX 63 /* WPA passphrase 上限(8..63 字符);64 位 hex PSK 另算 */
/* wpa_supplicant 的 ctrl_iface 是行协议:值里出现 \n/\r 会截断或伪造命令,
* 其它控制字符也进不了 SSID/PSK 的合法字符集。 */
static inline int nm_wifi_ctl_free(const char *s, size_t n)
{
for (size_t i = 0; i < n; i++) {
unsigned char c = (unsigned char)s[i];
if (c < 0x20 || c == 0x7f) return 0;
}
return 1;
}
static inline int nm_wifi_ssid_ok(const char *s)
{
if (!s) return 0;
size_t n = strlen(s);
if (n == 0 || n > NM_WIFI_SSID_MAX) return 0;
return nm_wifi_ctl_free(s, n);
}
/* 空串 = 开放网络(调用方按 key_mgmt NONE 处理);≤63 字节口令;64 位 hex PSK。
* 不强制 8 字符下限:WPA 会自行拒绝过短口令,而面板/设置页对「密码错」的既有文案依赖
* 把它交给 supplicant 判定(M64 只收口注入面与行协议,不改失败语义)。 */
static inline int nm_wifi_psk_ok(const char *s)
{
if (!s || !s[0]) return 1;
size_t n = strlen(s);
if (!nm_wifi_ctl_free(s, n)) return 0;
if (n <= NM_WIFI_PSK_MAX) return 1;
if (n == 64) {
for (size_t i = 0; i < n; i++) {
char c = s[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
return 0;
}
return 1;
}
return 0;
}
/* 把值拼成 wpa_supplicant 的字面量 `"..."`(ssid/psk 这两个字段要求带引号)。
* 只转义 `"` 与 `\`——它们在 supplicant 的 parse_string 里有特殊含义。
* 返回 0 成功;-1 = 含控制字符或缓冲不足。 */
static inline int nm_wpa_quote(const char *val, char *out, size_t cap)
{
if (!val || !out) return -1;
size_t n = strlen(val);
if (!nm_wifi_ctl_free(val, n)) return -1;
size_t need = 3; /* 开引号 + 闭引号 + NUL */
for (size_t i = 0; i < n; i++) need += (val[i] == '"' || val[i] == '\\') ? 2 : 1;
if (need > cap) return -1;
size_t o = 0;
out[o++] = '"';
for (size_t i = 0; i < n; i++) {
char c = val[i];
if (c == '"' || c == '\\') out[o++] = '\\';
out[o++] = c;
}
out[o++] = '"';
out[o] = 0;
return 0;
}
/* 组装 `set_network <id> <field> <"value">` 的 argv(argv[0] 由调用方补 wpa_cli 路径)。
* 生产代码(netmgrd wpa_set_string / launcherd nm_direct_wpa_set)与宿主测试共用这一份,
* 保证测试断言的就是实际下发的那条命令。返回 0 成功;-1 = 值不合法/缓冲不足。 */
static inline int nm_wpa_set_argv(char *idbuf, size_t idcap, char *quoted, size_t qcap,
int id, const char *field, const char *val, char *argv[5])
{
if (!field || !argv) return -1;
if (nm_wpa_quote(val, quoted, qcap) < 0) return -1;
snprintf(idbuf, idcap, "%d", id);
argv[0] = (char *)"set_network";
argv[1] = idbuf;
argv[2] = (char *)field;
argv[3] = quoted;
argv[4] = NULL;
return 0;
}
#endif /* NM_WIFI_ARGS_H */