From d3f2e33054729d17f68bf3c30307063ff1b63002 Mon Sep 17 00:00:00 2001 From: ciddwd <2117971372@qq.com> Date: Wed, 10 Jun 2026 14:50:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(remote-input):=20verify=5Fhello=20=E9=94=81?= =?UTF-8?q?=E5=AE=9A=E6=A3=80=E6=9F=A5=E4=B8=8E=E5=A4=B1=E8=B4=A5=E7=B4=AF?= =?UTF-8?q?=E8=AE=A1=E5=90=88=E5=B9=B6=E5=88=B0=E5=90=8C=E4=B8=80=E4=B8=B4?= =?UTF-8?q?=E7=95=8C=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前分两次拿锁:同一 IP 的并发握手可以都先通过锁定检查、再各自累计失败, 让计数越过 PIN_MAX_FAILS 却不触发锁定。合并后并发请求无法在检查与累计之间 穿插。PIN 比较本身无共享状态,留在锁外。 另:cancel_remote_dictation 补注释说明 double-cancel(stop 收尾后断连)下 cancel_session 经 begin_cancel_session_state 返回 None 早退,是安全 no-op (review 提到的 panic 不存在)。 验证:cargo check 通过。 --- openless-all/app/src-tauri/src/coordinator.rs | 2 ++ .../app/src-tauri/src/remote_server/mod.rs | 34 +++++++++---------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index 6d5e2aef..64099eaf 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -1045,6 +1045,8 @@ impl Coordinator { } /// 手机断连 / 点取消:丢弃本次,不落字。 + /// stop 正常收尾后的断连也会走到这里(double-cancel):此时会话已 Idle, + /// cancel_session 内部 begin_cancel_session_state 返回 None 早退,安全 no-op。 pub fn cancel_remote_dictation(&self) { cancel_session(&self.inner); self.clear_remote_source(); diff --git a/openless-all/app/src-tauri/src/remote_server/mod.rs b/openless-all/app/src-tauri/src/remote_server/mod.rs index 93a6e72f..9884027c 100644 --- a/openless-all/app/src-tauri/src/remote_server/mod.rs +++ b/openless-all/app/src-tauri/src/remote_server/mod.rs @@ -641,32 +641,32 @@ enum AuthResult { } fn verify_hello(txt: &str, state: &Arc, peer_ip: IpAddr) -> AuthResult { - // 锁定检查(按源 IP) - { - let mut guard = state.pin_fails.lock(); - if let Some((_, Some(until))) = guard.get(&peer_ip) { - if Instant::now() < *until { - return AuthResult::Locked; - } - // 锁定到期,重置该 IP - guard.remove(&peer_ip); - } - } + // PIN 比较在锁外完成(无共享状态;constant_time_eq 防计时侧信道)。 let v: serde_json::Value = match serde_json::from_str(txt) { Ok(v) => v, - Err(_) => return AuthResult::BadPin, + Err(_) => serde_json::Value::Null, // 非法 JSON 按 BadPin 计数 }; - let ok = v.get("type").and_then(|t| t.as_str()) == Some("hello") + let pin_ok = v.get("type").and_then(|t| t.as_str()) == Some("hello") && v.get("pin") .and_then(|p| p.as_str()) .map(|p| constant_time_eq(p.as_bytes(), state.pin.as_bytes())) .unwrap_or(false); - if ok { - state.pin_fails.lock().remove(&peer_ip); + + // 锁定检查与失败累计放同一临界区:之前分两次拿锁,同一 IP 的并发握手可以 + // 都先通过锁定检查再各自累计失败,让计数越过阈值却不触发锁定。 + let now = Instant::now(); + let mut guard = state.pin_fails.lock(); + if let Some((_, Some(until))) = guard.get(&peer_ip) { + if now < *until { + return AuthResult::Locked; + } + // 锁定到期,重置该 IP + guard.remove(&peer_ip); + } + if pin_ok { + guard.remove(&peer_ip); AuthResult::Ok } else { - let now = Instant::now(); - let mut guard = state.pin_fails.lock(); // 容量兜底:先丢已解锁/过期的条目,防伪造海量源 IP 撑爆表。 if guard.len() >= PIN_FAILS_MAX_ENTRIES { guard.retain(|_, (_, until)| matches!(until, Some(t) if *t > now));