fix(client): allow switch-sides back-connection in incoming-only mode (#15780)

* fix(client): allow switch-sides back-connection in incoming-only mode

"Switch sides" makes the controlled client run `--connect <peer>
--switch_uuid <uuid>`, which Client::_start rejected outright in
incoming-only custom clients, so the feature silently dropped the
session and never switched.

Exempt exactly that back-connection: a default-conn session carrying a
switch uuid may proceed. The uuid is then verified against the local
server process in handle_hash(); if it is missing there (forged or
expired), an incoming-only client now aborts with an error instead of
falling through to password login, so the outgoing-connection
restriction cannot be bypassed with a crafted --switch_uuid.

Fixes rustdesk/rustdesk#11200 (discussion)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(client): validate switch-back grants before connecting

  - check pending peer/UUID grants before bypassing incoming-only mode
  - close rejected switch-back connections and suppress retries
  - keep grant consumption in handle_hash and test non-consuming checks

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(client): prevent switch-back UUID reuse

  - claim pending switch-back grants before connecting
  - retain claimed grants to reject duplicate requests
  - bind authorization to the peer ID and UUID
  - use a shared TTL for switch-back grants

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(client): defer switch UUID consumption until authentication

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(client): reject repeated hash login in incoming-only mode

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
RustDesk
2026-08-10 16:07:12 +08:00
committed by GitHub
parent 594e63805c
commit d407db9fae
3 changed files with 169 additions and 29 deletions

View File

@@ -312,6 +312,14 @@ pub enum DataPortableService {
CmShowElevation(bool),
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum SwitchSidesUuidAction {
Check,
Consume,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum Data {
@@ -387,7 +395,7 @@ pub enum Data {
SwitchSidesRequest(String),
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
SwitchSidesUuid(String, String, Option<bool>),
SwitchSidesUuid(String, String, SwitchSidesUuidAction, Option<bool>),
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
SwitchSidesBack,
@@ -1050,14 +1058,21 @@ async fn handle(data: Data, stream: &mut Connection) {
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Data::SwitchSidesUuid(uuid, id, None) => {
Data::SwitchSidesUuid(uuid, id, action, None) => {
let allowed = uuid
.parse::<uuid::Uuid>()
.map(|uuid| crate::server::remove_pending_switch_sides_uuid(&id, &uuid))
.map(|uuid| match action {
SwitchSidesUuidAction::Check => {
crate::server::has_pending_switch_sides_uuid(&id, &uuid)
}
SwitchSidesUuidAction::Consume => {
crate::server::claim_pending_switch_sides_uuid(&id, &uuid)
}
})
.unwrap_or(false);
allow_err!(
stream
.send(&Data::SwitchSidesUuid(uuid, id, Some(allowed)))
.send(&Data::SwitchSidesUuid(uuid, id, action, Some(allowed)))
.await
);
}