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
@@ -51,6 +51,7 @@
|
||||
#include <linux/inet_diag.h>
|
||||
|
||||
#include "meow_ipc.h" /* 帧格式/消息号 + MSG_NET_*(0x00E0-0x00EF) + m_net_*(号段 0x00E0-0x00EF) */
|
||||
#include "nm_wifi_args.h" /* M64 P1-1:SSID/PSK 参数校验 + wpa 字面量转义(与 launcherd 回退共用) */
|
||||
|
||||
/* ---------------- 路径(全部可经 env 覆盖,宿主/qemu 桩测试用) ---------------- */
|
||||
static char g_net_dir[128] = "/sys/class/net"; /* MEOW_NET_DIR */
|
||||
@@ -303,8 +304,10 @@ static int reap_bounded(pid_t pid, const char *cmd, int *st)
|
||||
}
|
||||
|
||||
/* 带四态结果的执行器(res 可为 NULL,等价于旧 run_cmd)。返回 stdout 字节数(-1=起不来)。
|
||||
* argv != NULL → execv(argv)(不经过 shell,M64 P1);argv == NULL → sh -c cmd(仅常量命令)。
|
||||
* stdout 超容量时继续排空(丢弃),保证子进程不被 EPIPE/SIGPIPE 提前打断、退出码可信。 */
|
||||
static int run_cmd_ex(char *buf, int cap, int tmo_ms, const char *cmd, runcmd_res_t *res)
|
||||
static int run_exec_ex(char *buf, int cap, int tmo_ms, char *const *argv, const char *cmd,
|
||||
runcmd_res_t *res)
|
||||
{
|
||||
runcmd_res_t lr;
|
||||
runcmd_res_t *r = res ? res : &lr;
|
||||
@@ -312,6 +315,7 @@ static int run_cmd_ex(char *buf, int cap, int tmo_ms, const char *cmd, runcmd_re
|
||||
r->exit_code = -1;
|
||||
r->status = RUNCMD_EXEC;
|
||||
if (cap > 0) buf[0] = 0;
|
||||
const char *label = argv ? argv[0] : cmd;
|
||||
|
||||
int op[2], ep[2];
|
||||
if (pipe(op) < 0) return -1;
|
||||
@@ -322,7 +326,11 @@ static int run_cmd_ex(char *buf, int cap, int tmo_ms, const char *cmd, runcmd_re
|
||||
dup2(op[1], 1);
|
||||
dup2(ep[1], 2); /* M49:stderr 不再丢 /dev/null,失败时留一段进错误文案 */
|
||||
close(op[0]); close(op[1]); close(ep[0]); close(ep[1]);
|
||||
execl("/bin/sh", "sh", "-c", cmd, (char *)0);
|
||||
if (argv) {
|
||||
execv(argv[0], argv);
|
||||
} else {
|
||||
execl("/bin/sh", "sh", "-c", cmd, (char *)0);
|
||||
}
|
||||
_exit(127);
|
||||
}
|
||||
close(op[1]); close(ep[1]);
|
||||
@@ -370,7 +378,7 @@ static int run_cmd_ex(char *buf, int cap, int tmo_ms, const char *cmd, runcmd_re
|
||||
|
||||
if (timed_out) kill(pid, SIGKILL); /* 已经超时:不再多等 300ms */
|
||||
int st = 0;
|
||||
int reaped = reap_bounded(pid, cmd, &st);
|
||||
int reaped = reap_bounded(pid, label, &st);
|
||||
r->bytes = off;
|
||||
r->truncated = trunc;
|
||||
if (reaped) {
|
||||
@@ -388,7 +396,19 @@ static int run_cmd_ex(char *buf, int cap, int tmo_ms, const char *cmd, runcmd_re
|
||||
/* 兼容外壳:返回读到的字节数(-1=起不来),语义与 P1 之前一致(调用点无需改动)。 */
|
||||
static int run_cmd(char *buf, int cap, int tmo_ms, const char *cmd)
|
||||
{
|
||||
return run_cmd_ex(buf, cap, tmo_ms, cmd, NULL);
|
||||
return run_exec_ex(buf, cap, tmo_ms, NULL, cmd, NULL);
|
||||
}
|
||||
|
||||
/* 带四态结果的兼容外壳(res 可为 NULL)。 */
|
||||
static int run_cmd_ex(char *buf, int cap, int tmo_ms, const char *cmd, runcmd_res_t *res)
|
||||
{
|
||||
return run_exec_ex(buf, cap, tmo_ms, NULL, cmd, res);
|
||||
}
|
||||
|
||||
/* argv 形态(M64 P1:外部可控字符串只走这里,永不拼 shell)。 */
|
||||
static int run_argv(char *buf, int cap, int tmo_ms, char *const *argv)
|
||||
{
|
||||
return run_exec_ex(buf, cap, tmo_ms, argv, NULL, NULL);
|
||||
}
|
||||
|
||||
/* 失败时的一句话原因(有界;stderr 有料就带上,最多 120B,避免报文过长)。 */
|
||||
@@ -3147,7 +3167,27 @@ static const char *str_capsafe(const char *s, char *tmp, size_t cap)
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/* wpa_cli 单命令封装:拼 PATH 前缀 + 有界收割(RUNCMD_TMO_MS)。返回 run_cmd 读到的字节数。 */
|
||||
/* 在固件标准目录里解析可执行文件(等价旧 `PATH=/bin:/sbin:/usr/bin:/usr/sbin` 前缀,
|
||||
* 但不再让 shell 去查 PATH)。g_* 可被 MEOW_* 环境变量覆盖为绝对路径(调试用)。 */
|
||||
static int resolve_bin(const char *name, char *out, size_t cap)
|
||||
{
|
||||
if (!name || !name[0]) return -1;
|
||||
if (strchr(name, '/')) {
|
||||
if (access(name, X_OK) < 0) return -1;
|
||||
snprintf(out, cap, "%s", name);
|
||||
return 0;
|
||||
}
|
||||
static const char *dirs[] = { "/bin", "/sbin", "/usr/bin", "/usr/sbin", NULL };
|
||||
for (int i = 0; dirs[i]; i++) {
|
||||
snprintf(out, cap, "%s/%s", dirs[i], name);
|
||||
if (access(out, X_OK) == 0) return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* wpa_cli 单命令封装:拼 PATH 前缀 + 有界收割(RUNCMD_TMO_MS)。返回 run_cmd 读到的字节数。
|
||||
* args 只允许**编译期常量**(scan/list_networks/status/...)。任何来自 IPC/用户/AP 广播的
|
||||
* 字符串必须走 wpa_exec()(execv,无 shell)——M64 P1-1 之前正是这里把 SSID 拼进了 sh -c。 */
|
||||
static int wpa_cmd(char *out, int cap, const char *args)
|
||||
{
|
||||
char cmd[512];
|
||||
@@ -3155,6 +3195,34 @@ static int wpa_cmd(char *out, int cap, const char *args)
|
||||
return run_cmd(out, cap, RUNCMD_TMO_MS, cmd);
|
||||
}
|
||||
|
||||
/* 执行 wpa_cli(execv 传 argv,不经过 shell)。args 以 NULL 结尾、不含 wpa_cli 本身。
|
||||
* 返回 stdout 字节数(-1=二进制找不到/起不来)。
|
||||
* `' ; ` $ & | < >` 在这里只是 argv 里的普通字节——这条路径上不存在 shell 解析。 */
|
||||
static int wpa_exec(char *out, int cap, char *const *args)
|
||||
{
|
||||
char bin[256];
|
||||
if (resolve_bin(g_wpa_cli, bin, sizeof bin) < 0) { if (cap > 0) out[0] = 0; return -1; }
|
||||
char *argv[12];
|
||||
int n = 0;
|
||||
argv[n++] = bin;
|
||||
for (int i = 0; args[i] && n < (int)(sizeof argv / sizeof argv[0]) - 1; i++) argv[n++] = args[i];
|
||||
argv[n] = NULL;
|
||||
return run_argv(out, cap, RUNCMD_TMO_MS, argv);
|
||||
}
|
||||
|
||||
/* set_network <id> <field> <值>:字符串字段按 wpa_supplicant 语法加双引号(纯正确性转义,
|
||||
* 与 shell 无关),整条命令以 argv 传入。val 必须先过 nm_wifi_ssid_ok/nm_wifi_psk_ok。 */
|
||||
static int wpa_set_string(char *out, int cap, int id, const char *field, const char *val)
|
||||
{
|
||||
char idbuf[16], quoted[160];
|
||||
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 wpa_exec(out, cap, args);
|
||||
}
|
||||
|
||||
/* WiFi 设置页 bug4-②(2026-09-15):list_networks/scan_results 对非 ASCII 字节输出 \xNN 转义
|
||||
* (中文 SSID)——还原为原始字节串再与输入 SSID 做同表示比较;不还原则中文 SSID 永不命中
|
||||
* 既有网络 → 新建同名 open 记录顶影原记录。net.c wpa_decode_ssid / netmgr_ipc.c nm_decode_ssid
|
||||
@@ -3265,6 +3333,16 @@ static void wifi_connect_ex(const char *arg, ctl_result_t *r, int hidden)
|
||||
str_capsafe(ssid, tmp_ssid, sizeof tmp_ssid);
|
||||
str_capsafe(psk, tmp_psk, sizeof tmp_psk);
|
||||
if (!tmp_ssid[0]) { ctl_sets(r, MEOW_ERR_INVAL, "connect: ssid 缺失"); return; }
|
||||
/* M64 P1-1:SSID/PSK 是外部可控串(附近 AP 广播 / 用户输入)。长度与控制字符在这里
|
||||
* 收口;执行侧已改 execv 传 argv(wpa_set_string),shell 元字符不再有语义。 */
|
||||
if (!nm_wifi_ssid_ok(tmp_ssid)) {
|
||||
ctl_sets(r, MEOW_ERR_INVAL, "connect: ssid 非法(长度 1..32 字节,禁控制字符)");
|
||||
return;
|
||||
}
|
||||
if (!nm_wifi_psk_ok(tmp_psk)) {
|
||||
ctl_sets(r, MEOW_ERR_INVAL, "connect: psk 非法(≤63 字符口令或 64 位 hex,禁控制字符)");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 找既有网络:list_networks → 匹配 ssid(剥引号 + \xNN 还原后同表示比较);命中则复用其 id。 */
|
||||
char cmd[256];
|
||||
@@ -3304,16 +3382,19 @@ static void wifi_connect_ex(const char *arg, ctl_result_t *r, int hidden)
|
||||
id = atoi(out);
|
||||
if (id < 0) { ctl_sets(r, MEOW_ERR_INVAL, "connect: add_network 无 id"); return; }
|
||||
created = 1;
|
||||
snprintf(cmd, sizeof cmd, "set_network %d ssid '\"%s\"'", id, tmp_ssid);
|
||||
wpa_cmd(out, sizeof out, cmd);
|
||||
if (tmp_psk[0]) snprintf(cmd, sizeof cmd, "set_network %d psk '\"%s\"'", id, tmp_psk);
|
||||
else snprintf(cmd, sizeof cmd, "set_network %d key_mgmt NONE", id);
|
||||
wpa_cmd(out, sizeof out, cmd);
|
||||
/* M64 P1-1:ssid/psk 走 execv 传 argv(wpa_set_string),不再拼 `sh -c`。
|
||||
* 修复前是 `set_network %d ssid '\"%s\"'` —— 单引号里套双引号,SSID 带 `'` 即逸出。 */
|
||||
wpa_set_string(out, sizeof out, id, "ssid", tmp_ssid);
|
||||
if (tmp_psk[0]) {
|
||||
wpa_set_string(out, sizeof out, id, "psk", tmp_psk);
|
||||
} else {
|
||||
snprintf(cmd, sizeof cmd, "set_network %d key_mgmt NONE", id);
|
||||
wpa_cmd(out, sizeof out, cmd);
|
||||
}
|
||||
} else if (tmp_psk[0]) {
|
||||
/* bug4-①/bug1-④:命中既有网络且调用方带了新 psk → 先更新凭据再连
|
||||
* (旧记录密码错误可经扫描路径重输修复;psk 为空则不动既有凭据)。 */
|
||||
snprintf(cmd, sizeof cmd, "set_network %d psk '\"%s\"'", id, tmp_psk);
|
||||
wpa_cmd(out, sizeof out, cmd);
|
||||
wpa_set_string(out, sizeof out, id, "psk", tmp_psk);
|
||||
}
|
||||
if (hidden) {
|
||||
/* bug2:隐藏 AP 必须 scan_ssid=1,supplicant 才会主动发 Probe Request(命中既有也要补)。 */
|
||||
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,181 @@
|
||||
/* 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;
|
||||
}
|
||||
Reference in new issue
Block a user