fix(daemon): M65 P1 三条 + P2-1 — popen 超时/共享缓冲、bt scan 有界收割与毫秒节流
来自 M59 内存与资源安全审计(这 3 条 P1 很可能就是 2026-11-11 18:44 设备用户态崩塌的 成因:当时 sshd 连 fork 都做不到、ICMP 仍应答)。执行顺序按审计要求 P1-3 → P2-1 → P1-1 → P1-2。 P1-1 launcherd net.c:popen_read_tmo —— 对持续输出的子进程永不超时 + 堆无限增长 - 新增 meow_launcherd/popen_tmo.h:读循环的 deadline 移到**循环顶部**,输出加 256KB 硬 上限(超限返回 OVERFLOW,调用方 kill 子进程并按失败返回 NULL,绝不静默截断),缓冲交给 调用方或由本函数持有(无 static)。 - net.c 收尸改为 popen_tmo_reap_bounded(300ms + SIGKILL + 300ms):旧实现 SIGKILL 之后是 无条件 waitpid(…,0),子进程卡 D 态照样钉死 UI 线程。调用面(malloc 返回值 + free)不变, 14 个调用点零改动。 P1-2 audiod popen_read_tmo 的 static char buf[4096] 被 4 个线程共用 - 改成调用方传缓冲(`char *popen_read_tmo(tmo, cmd, out, cap)`),函数内无共享状态、不加锁 —— M55 刚把音量写路径做成无锁,音频线程上不能拿锁。7 个调用点各用自己的栈缓冲。 - 顺带修掉旧循环的 `remain` 不递减问题:旧写法每次 poll 都能重等满 tmo,最坏总时长 ≈ 4095 次 × 1500ms;现在总时长就是一个 tmo。 P1-3 netmgrd bt_scan_on/off 走无超时 system()(单线程主循环 + 55 个残留 bluetoothctl 的形态) - scan off:直接 run_cmd_ex(BT_SCAN_OFF_TMO_MS=600),超时如实回 MEOW_ERR_NODEV 并计入既有 bt_circuit_note。 - scan on:fork 一个 setsid 的 scan worker(在飞上限 1),worker 内部用 run_cmd_ex 跑同一条 pipeline(超时 + 输出上限);父进程只记账,bt_scan_reap() 在主循环每拍收割,过 deadline 杀**整个进程组**(pipeline 的 bluetoothctl 是孙进程,只杀 sh 会留孤儿)。 - 超时计入既有熔断(连 3 次 → 30s 内不再 fork);熔断中 scan on 不再起进程。 - 不在主循环里同步跑整条 pipeline:它要 ~5s(sleep 1 + dwell 4),同步跑等于每 7s 把 DNS 代理停 5s,是另一种挂死。 P2-1 扫描节流失效 7000 秒(mono_now() 返回秒,却与 7000 毫秒比较) - 节流改用 64 位毫秒时钟(bt_scan_now_ms / 函数指针钩子供宿主单测注入可控时钟),抽成纯 函数 bt_scan_throttle(now_ms);实际节流从 ~1.94 小时恢复到 7s。 测试(每条都有反向控制) - 新增 meow_launcherd/test_popen_tmo.c(已接入 build_launcher.sh test): 持续输出且永不退出的生产者 → 新循环 300ms 到点返回,旧循环 1500ms 内不返回; `yes`+64KB 上限 → 立刻 OVERFLOW;同一份 300KB 有界输入 → 旧循环照单全收 307200 字节、 新循环在上限处报 OVERFLOW;两线程交错读 → 旧 static 缓冲 A 读到 B 的内容,调用方缓冲各 读各的;收尸 313ms 有界且无残留子进程。 - meow_netmgrd/test_bt_ctl.c 增补:7s / 6.999s / 7.1s 边界(含走真实入口 bt_scan_on 的可控 时钟用例,秒/毫秒单位回归会在这里翻车);wedge 桩(scan 一律 exec sleep 60)下 scan off 611ms 有界返回且 ECHILD、scan on 的 worker 1200ms 窗口到期被整组收割、孙进程 全收(ECHILD)、超时进熔断并最终熔断拒绝 fork。 验证:netmgrd 宿主回归 79 PASS / 0 FAIL;test_bt_ctl ALL PASS;launcherd `build_launcher.sh test` ALL TESTS PASS(含新增用例);meow_netmgrd mipsel 交叉编译通过; audiod host 构建通过,test_audiod_host 的失败集合与改动前**逐条相同**(41 条,T8 SEGV 是 测试自身 strstr(q8,"rms=")+4 的既有缺陷,见 M59 审计 §5)。 仅源码:无二进制、未改 scripts/build/fw_content.conf。
This commit is contained in:
1 parent
1936cf7b91
commit
1ccb3c7a93
7 files changed
+839
-86
No files matched your search
@@ -11,6 +11,10 @@
|
||||
*
|
||||
* 用法:cc -Wall -Wextra -I../meow_launcherd test_bt_ctl.c -o test_bt_ctl && ./test_bt_ctl
|
||||
*/
|
||||
/* M65:扫描窗口在单测里缩短 —— pipeline 的 dwell 取 0(整条 ~1s 结束),超时压到 1200ms。
|
||||
* 这样既能验「超时强杀 + 不漏孙进程」,又不用为每个用例等生产的 6.5s。 */
|
||||
#define BT_SCAN_TMO_MS 1200
|
||||
#define BT_SCAN_DWELL_S 0
|
||||
#define main meow_netmgrd_prod_main_unused
|
||||
#include "meow_netmgrd.c"
|
||||
#undef main
|
||||
@@ -31,6 +35,37 @@ static void wf(const char *p, const char *s)
|
||||
if (f) { fputs(s, f); fclose(f); }
|
||||
}
|
||||
|
||||
/* ---- M65 单测辅助:可控时钟 + 在飞 scan worker 的确定性收尾 ---- */
|
||||
static long long g_fake_now_ms = 0;
|
||||
static long long fake_clock_ms(void) { return g_fake_now_ms; }
|
||||
|
||||
/* 立刻收掉在飞 scan worker(整组杀 + 阻塞收尸):单测要确定性,不等生产的 ~5s 窗口。 */
|
||||
static void kill_scan_worker(void)
|
||||
{
|
||||
if (g_bt_scan_pid > 0) {
|
||||
kill(-g_bt_scan_pid, SIGKILL);
|
||||
kill(g_bt_scan_pid, SIGKILL);
|
||||
int st = 0;
|
||||
while (waitpid(g_bt_scan_pid, &st, 0) < 0 && errno == EINTR) { }
|
||||
}
|
||||
g_bt_scan_pid = 0;
|
||||
g_bt_scan_deadline = 0;
|
||||
}
|
||||
|
||||
/* 等 worker 由 bt_scan_reap() 自己收完(超时/正常都算),返回耗时 ms;超时返回 -1。 */
|
||||
static long drain_scan_worker(int max_ms)
|
||||
{
|
||||
long t0 = (long)bt_scan_now_ms();
|
||||
while (g_bt_scan_pid > 0) {
|
||||
bt_scan_reap();
|
||||
if (g_bt_scan_pid <= 0) break;
|
||||
if ((long)bt_scan_now_ms() - t0 > max_ms) return -1;
|
||||
struct timespec ts = { 0, 20 * 1000000L };
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
return (long)bt_scan_now_ms() - t0;
|
||||
}
|
||||
|
||||
/* ---- 真机原始字节样本(含 ANSI ESC / \001\002 标记 / \r\033[K / 提示符) ---- */
|
||||
static const char *S_NEW_PUDDING =
|
||||
"\033[0;94m[bluetooth]\033[0m# \r\033[K"
|
||||
@@ -113,6 +148,9 @@ int main(void)
|
||||
"#!/bin/sh\n"
|
||||
"S='%s/state'\n"
|
||||
"catf() { [ -f \"$1\" ] || return 0; while IFS= read -r l || [ -n \"$l\" ]; do echo \"$l\"; done < \"$1\"; }\n"
|
||||
/* M65:scan 的 wedge 桩。交互形态(无参数,scan pipeline 喂 stdin)与 `scan off`
|
||||
* 两种入口都要挂住;用 exec 让 sleep 就是那个进程本身(杀掉父 shell 不会留孤儿)。 */
|
||||
"if [ $# -eq 0 ] && [ -f \"$S/hang_scan\" ]; then exec sleep 60; fi\n"
|
||||
"case \"$1\" in\n"
|
||||
" paired-devices) catf \"$S/paired_list.txt\"; exit 0;;\n"
|
||||
" devices) catf \"$S/dev_list.txt\"; exit 0;;\n"
|
||||
@@ -146,6 +184,12 @@ int main(void)
|
||||
" echo yes > \"$S/paired_$2\"; echo 'Pairing successful'; exit 0;;\n"
|
||||
" remove)\n"
|
||||
" echo gone > \"$S/paired_$2\"; echo no > \"$S/conn_$2\"; echo 'Device has been removed'; exit 0;;\n"
|
||||
/* M65:scan 的 wedge 桩 —— 真的挂住(exec 让 sleep 就是那个进程本身,模拟 bluetoothctl
|
||||
* 卡在 HCI/D-Bus 里;若改成子进程 sleep,杀父进程会留下孤儿,断言就不诚实了)。 */
|
||||
" scan)\n"
|
||||
" if [ -f \"$S/hang_scan\" ]; then exec sleep 60; fi\n"
|
||||
" if [ \"$2\" = on ]; then echo 'Discovery started'; else echo 'Discovery stopped'; fi\n"
|
||||
" exit 0;;\n"
|
||||
"esac\n"
|
||||
"exit 0\n", T);
|
||||
wf(p, sh); chmod(p, 0755);
|
||||
@@ -801,6 +845,99 @@ int main(void)
|
||||
CHECK(strstr(r.text, "scan on") != NULL);
|
||||
bt_ctl_execute("scan", "off", &r);
|
||||
CHECK(r.code == MEOW_OK);
|
||||
kill_scan_worker(); /* 1s 窗口不必等,直接收 */
|
||||
|
||||
/* ================= M65 P2-1:扫描节流单位(7s / 7.1s;可控时钟) ================= */
|
||||
/* 回归防线:旧实现 `mono_now()`(秒)比 `7000`(毫秒)→ 实际 7000 秒 ≈ 1.94 小时一次。
|
||||
* 两个入口都验:①纯判定 bt_scan_throttle(入参毫秒)②真实入口 bt_scan_on(只认
|
||||
* g_bt_scan_clock_ms 钩子 —— 若有人把时钟改回 mono_now(),钩子失效,这里的 +6999 用例
|
||||
* 会看到真实时间几乎没动 → 判「放行」→ 断言失败)。 */
|
||||
printf("== M65 P2-1: bt scan 节流按毫秒(7s 放行 / 6.999s 拒 / 7.1s 放行) ==\n");
|
||||
{
|
||||
g_bt_scan_ms = 0;
|
||||
g_bt_scan_pid = 0; g_bt_scan_deadline = 0;
|
||||
g_bt_tmo_streak = 0; g_bt_tmo_until = 0;
|
||||
|
||||
CHECK(bt_scan_throttle(1000000) == 1); /* 首次:放行 */
|
||||
CHECK(bt_scan_throttle(1000000 + 6999) == 0); /* 6.999s:拒 */
|
||||
CHECK(bt_scan_throttle(1000000 + 7000) == 1); /* 7.000s:放行(秒混毫秒会等到 7000s) */
|
||||
CHECK(bt_scan_throttle(1000000 + 7000 + 99) == 0); /* 0.099s:拒 */
|
||||
CHECK(bt_scan_throttle(1000000 + 7000 + 7100) == 1); /* 7.1s:放行 */
|
||||
|
||||
/* 真实入口:时钟走钩子 */
|
||||
g_bt_scan_clock_ms = fake_clock_ms;
|
||||
g_bt_scan_ms = 0; g_bt_scan_pid = 0; g_bt_scan_deadline = 0;
|
||||
g_fake_now_ms = 3000000;
|
||||
CHECK(bt_scan_on() == 1); /* t=3.000s:放行并起 worker */
|
||||
kill_scan_worker();
|
||||
g_fake_now_ms = 3000000 + 6999;
|
||||
CHECK(bt_scan_on() == 0); /* +6.999s:节流(不起 worker) */
|
||||
CHECK(g_bt_scan_pid <= 0);
|
||||
g_fake_now_ms = 3000000 + 7000;
|
||||
CHECK(bt_scan_on() == 1); /* +7.000s:放行 */
|
||||
kill_scan_worker();
|
||||
g_fake_now_ms = 3000000 + 7000 + 7100;
|
||||
CHECK(bt_scan_on() == 1); /* +7.1s:再放行 */
|
||||
kill_scan_worker();
|
||||
g_bt_scan_clock_ms = bt_scan_now_ms; /* 复原真时钟 */
|
||||
g_bt_scan_ms = 0;
|
||||
}
|
||||
|
||||
/* ================= M65 P1-3:scan 有界收割(不挂死、不漏子进程) ================= */
|
||||
printf("== M65 P1-3: scan on/off 对「永不返回」的 bluetoothctl 有界收割、不漏子进程 ==\n");
|
||||
{
|
||||
snprintf(p, sizeof p, "%s/state/hang_scan", T); wf(p, "1\n"); /* 桩:scan → exec sleep 60 */
|
||||
g_bt_scan_ms = 0; g_bt_scan_pid = 0; g_bt_scan_deadline = 0;
|
||||
g_bt_tmo_streak = 0; g_bt_tmo_until = 0;
|
||||
|
||||
/* ① scan off 是同步路径:旧实现 system() 会永远阻塞在这个 wedge 上。 */
|
||||
long t0 = (long)bt_scan_now_ms();
|
||||
int rc = bt_scan_off();
|
||||
long el = (long)bt_scan_now_ms() - t0;
|
||||
printf(" [info] bt_scan_off 滞回 %ldms (上限 %dms)\n", el, BT_SCAN_OFF_TMO_MS);
|
||||
CHECK(rc == -1); /* 如实报失败,不假成功 */
|
||||
CHECK(el >= BT_SCAN_OFF_TMO_MS - 50); /* 确实等到了超时点 */
|
||||
CHECK(el < BT_SCAN_OFF_TMO_MS + 1200); /* 但有界(旧 system() 无界) */
|
||||
{ int st2 = 0; errno = 0;
|
||||
CHECK(waitpid(-1, &st2, WNOHANG) == -1 && errno == ECHILD); } /* 无残留子进程 */
|
||||
|
||||
/* ② scan on:worker 在飞,主循环不被阻塞;过 deadline 整组强杀(含 pipeline 孙进程)。 */
|
||||
g_bt_scan_ms = 0;
|
||||
t0 = (long)bt_scan_now_ms();
|
||||
CHECK(bt_scan_on() == 1);
|
||||
long fork_el = (long)bt_scan_now_ms() - t0;
|
||||
CHECK(fork_el < 300); /* 起扫描本身绝不阻塞 */
|
||||
CHECK(g_bt_scan_pid > 0);
|
||||
CHECK(bt_scan_on() == 0); /* 在飞上限 1:不重复 fork */
|
||||
long dr = drain_scan_worker(BT_SCAN_TMO_MS + 3000);
|
||||
el = (long)bt_scan_now_ms() - t0; /* 从起扫描算起,含 fork + 窗口 */
|
||||
printf(" [info] scan worker 从起扫描到收割 %ldms(drain 轮询 %ldms;窗口 %dms)\n",
|
||||
el, dr, BT_SCAN_TMO_MS);
|
||||
CHECK(dr >= 0); /* 有界收完(没有永久卡住) */
|
||||
CHECK(el >= BT_SCAN_TMO_MS); /* 是「等到超时才收」而不是立刻杀 */
|
||||
CHECK(el < BT_SCAN_TMO_MS + 2500);
|
||||
CHECK(g_bt_scan_pid == 0); /* 槽位已释放 */
|
||||
CHECK(g_bt_tmo_streak >= 1); /* 超时计入既有熔断计数 */
|
||||
{ int st2 = 0; errno = 0;
|
||||
CHECK(waitpid(-1, &st2, WNOHANG) == -1 && errno == ECHILD); } /* 孙进程也被整组收掉 */
|
||||
|
||||
/* ③ 熔断联动:再来一次超时就把 streak 顶到上限 → bt_circuit_open(),不再 fork。 */
|
||||
g_bt_tmo_streak = BT_TMO_STREAK_MAX - 1;
|
||||
g_bt_tmo_until = 0;
|
||||
g_bt_scan_ms = 0;
|
||||
CHECK(bt_scan_on() == 1);
|
||||
CHECK(drain_scan_worker(BT_SCAN_TMO_MS + 3000) >= 0);
|
||||
CHECK(g_bt_tmo_streak >= BT_TMO_STREAK_MAX);
|
||||
CHECK(bt_circuit_open() == 1);
|
||||
g_bt_scan_ms = 0;
|
||||
CHECK(bt_scan_on() == -1); /* 熔断中:不再 fork(不烧进程槽) */
|
||||
CHECK(g_bt_scan_pid <= 0);
|
||||
|
||||
/* 复原:摘掉 wedge 钩子,熔断器清零 */
|
||||
{ char ub[320]; snprintf(ub, sizeof ub, "rm -f '%s/state/hang_scan'", T); (void)!system(ub); }
|
||||
g_bt_tmo_streak = 0; g_bt_tmo_until = 0; g_bt_scan_ms = 0;
|
||||
bt_scan_off(); /* 桩恢复正常:现在能返回 0 */
|
||||
}
|
||||
|
||||
printf("== bt_write_authorize(系统身份 + MeowOS 才放行写级) ==\n");
|
||||
caller_t other; memset(&other, 0, sizeof other); other.kind = CALL_OTHER;
|
||||
|
||||
Reference in new issue
Block a user