fix: a refused privacy-mode request no longer turns off another connection's (#16507)

* fix: a refused privacy-mode request no longer turns off another connection's

`Connection::turn_on_privacy`'s error arm tore down whatever privacy mode was
active, whoever owned it. With connection A in privacy mode, connection B's
request is correctly refused with OCCUPIED, and the error arm then called
`turn_off_privacy_to_msg(INVALID_PRIVACY_MODE_CONN_ID, ..)`. That id bypasses
the ownership check in `check_off_conn_id`, so A's privacy mode went away: the
screen was un-blanked and local input un-hooked. The call passes `state: None`,
so no `set_privacy_mode_state` goes out either and A was never told, leaving its
client showing privacy mode as on. `check_privacy_mode_changed` doesn't cover
it, as it only sends PrvOnByOther while a privacy mode is still active.

Pass `self.inner.id`, as the sibling error path above already does.
`check_off_conn_id` then cleans up after this connection when it owns privacy
mode, and refuses when another connection does.

The bypass itself stays: `ipc::Data::Close`, `reset_all` and `TurnOnGuard` need
an unconditional teardown. Its contract is now noted on `check_off_conn_id`,
since that is what the buggy call site got wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Steven Storie <sstangle73@gmail.com>

* fix: disconnect other remote sessions when privacy mode starts

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: exclude privacy disconnect hook from iOS builds

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: validate privacy mode before disconnecting other sessions

Signed-off-by: fufesou <linlong1266@gmail.com>

* chore

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: Steven Storie <sstangle73@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
Steven Storie
2026-10-10 16:02:29 +08:00
committed by GitHub
co-authored by Claude Opus 5 fufesou
parent 001b0e9617
commit 67090d3aa1
2 changed files with 41 additions and 4 deletions
+28
View File
@@ -75,6 +75,11 @@ pub trait PrivacyMode: Sync + Send {
Ok(false)
}
/// `INVALID_PRIVACY_MODE_CONN_ID` deliberately bypasses the ownership check, meaning
/// "turn it off whoever owns it": shutdown (`ipc::Data::Close`), `reset_all`, and
/// `TurnOnGuard`, which undoes a half-finished turn-on however far it got. A caller
/// acting for one connection must pass that connection's own id, or it can turn off
/// privacy mode belonging to another.
#[inline]
fn check_off_conn_id(&self, conn_id: i32) -> ResultType<()> {
let pre_conn_id = self.pre_conn_id();
@@ -289,6 +294,29 @@ fn turn_on_privacy_sync(impl_key: &str, conn_id: i32) -> Option<ResultType<bool>
Some(privacy_mode_lock.as_mut()?.turn_on_privacy(conn_id))
}
#[cfg(not(target_os = "ios"))]
pub(crate) fn close_other_remote_connections_for_privacy(owner_id: i32) {
use crate::server::{AuthConnType, AUTHED_CONNS};
let privacy_mode_lock = PRIVACY_MODE.lock().unwrap();
// Ownership can change while the caller validates the capturer.
if privacy_mode_lock.as_ref().map(|mode| mode.pre_conn_id()) != Some(owner_id) {
return;
}
for connection in AUTHED_CONNS.lock().unwrap().iter() {
if connection.conn_type != AuthConnType::Remote || connection.conn_id == owner_id {
continue;
}
if let Err(err) = connection.sender.send(crate::ipc::Data::Close) {
hbb_common::log::debug!(
"Failed to close connection {} for privacy mode: {}",
connection.conn_id,
err
);
}
}
}
#[inline]
pub fn turn_off_privacy(conn_id: i32, state: Option<PrivacyModeState>) -> Option<ResultType<()>> {
Some(
+13 -4
View File
@@ -1145,6 +1145,14 @@ impl Connection {
},
Some(data) = rx_from_authed.recv() => {
match data {
ipc::Data::Close => {
conn.send(crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::PrvOnByOther,
String::new(),
)).await;
conn.on_close("privacy mode", false).await;
break;
}
#[cfg(all(target_os = "windows", feature = "flutter"))]
ipc::Data::PrinterData(data) => {
if Self::permission(keys::OPTION_ENABLE_REMOTE_PRINTER, &conn.control_permissions) {
@@ -5142,6 +5150,7 @@ impl Connection {
5_000,
);
if err_msg.is_empty() {
privacy_mode::close_other_remote_connections_for_privacy(self.inner.id);
crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::PrvOnSucceeded,
impl_key,
@@ -5168,10 +5177,10 @@ impl Connection {
Some(Err(e)) => {
log::error!("Failed to turn on privacy mode. {}", e);
if privacy_mode::is_in_privacy_mode() {
let _ = Self::turn_off_privacy_to_msg(
privacy_mode::INVALID_PRIVACY_MODE_CONN_ID,
String::new(),
);
// Use this connection's ID so a failed request cannot disable another
// connection's privacy mode: INVALID_PRIVACY_MODE_CONN_ID would bypass
// the ownership check (see `check_off_conn_id`).
let _ = Self::turn_off_privacy_to_msg(self.inner.id, String::new());
}
crate::common::make_privacy_mode_msg_with_details(
back_notification::PrivacyModeState::PrvOnFailed,