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:
1 parent
952a126477
commit
7b32d775c0
12 files changed
+812
-71
No files matched your search
@@ -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];
|
||||
|
||||
Reference in new issue
Block a user