/* nm_wifi_args.h — wpa_cli 参数安全(M64 P1-1:SSID/PSK → root 命令注入)。 * * 修复前:SSID/PSK 被拼成 `set_network 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 #include #include #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 <"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 */