mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-09 22:11:02 +03:00
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:
@@ -91,11 +91,15 @@ lazy_static::lazy_static! {
|
||||
static ref WAKELOCK_KEEP_AWAKE_OPTION: Arc::<Mutex<Option<bool>>> = Default::default();
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
const SWITCH_SIDES_UUID_TTL: Duration = Duration::from_secs(10);
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
|
||||
static ref PENDING_SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
|
||||
static ref PENDING_SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid, bool)>>> = Default::default();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -3085,7 +3089,7 @@ impl Connection {
|
||||
SWITCH_SIDES_UUID
|
||||
.lock()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.0.elapsed() < Duration::from_secs(10));
|
||||
.retain(|_, v| v.0.elapsed() < SWITCH_SIDES_UUID_TTL);
|
||||
let uuid_old = SWITCH_SIDES_UUID.lock().unwrap().remove(&lr.my_id);
|
||||
if let Ok(uuid) = uuid::Uuid::from_slice(_s.uuid.to_vec().as_ref()) {
|
||||
if let Some((_instant, uuid_old)) = uuid_old {
|
||||
@@ -3825,17 +3829,18 @@ impl Connection {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::SwitchSidesRequest(s)) => {
|
||||
if let Ok(uuid) = uuid::Uuid::from_slice(&s.uuid.to_vec()[..]) {
|
||||
crate::server::insert_pending_switch_sides_uuid(
|
||||
if crate::server::insert_pending_switch_sides_uuid(
|
||||
self.lr.my_id.clone(),
|
||||
uuid.clone(),
|
||||
);
|
||||
crate::run_me(vec![
|
||||
"--connect",
|
||||
&self.lr.my_id,
|
||||
"--switch_uuid",
|
||||
uuid.to_string().as_ref(),
|
||||
])
|
||||
.ok();
|
||||
) {
|
||||
crate::run_me(vec![
|
||||
"--connect",
|
||||
&self.lr.my_id,
|
||||
"--switch_uuid",
|
||||
uuid.to_string().as_ref(),
|
||||
])
|
||||
.ok();
|
||||
}
|
||||
self.on_close("switch sides", false).await;
|
||||
return false;
|
||||
}
|
||||
@@ -6139,23 +6144,40 @@ pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn insert_pending_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
pub fn insert_pending_switch_sides_uuid(id: String, uuid: uuid::Uuid) -> bool {
|
||||
let mut uuids = PENDING_SWITCH_SIDES_UUID.lock().unwrap();
|
||||
uuids.retain(|_, (instant, _)| instant.elapsed() < Duration::from_secs(10));
|
||||
uuids.insert(id, (tokio::time::Instant::now(), uuid));
|
||||
uuids.retain(|_, (instant, _, _)| instant.elapsed() < SWITCH_SIDES_UUID_TTL);
|
||||
if uuids.get(&id).map(|(_, stored_uuid, _)| stored_uuid) == Some(&uuid) {
|
||||
return false;
|
||||
}
|
||||
uuids.insert(id, (tokio::time::Instant::now(), uuid, false));
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn remove_pending_switch_sides_uuid(id: &str, uuid: &uuid::Uuid) -> bool {
|
||||
pub fn has_pending_switch_sides_uuid(id: &str, uuid: &uuid::Uuid) -> bool {
|
||||
let mut uuids = PENDING_SWITCH_SIDES_UUID.lock().unwrap();
|
||||
uuids.retain(|_, (instant, _)| instant.elapsed() < Duration::from_secs(10));
|
||||
if uuids.get(id).map(|(_, stored_uuid)| stored_uuid == uuid) == Some(true) {
|
||||
uuids.remove(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
uuids.retain(|_, (instant, _, _)| instant.elapsed() < SWITCH_SIDES_UUID_TTL);
|
||||
uuids
|
||||
.get(id)
|
||||
.map(|(_, stored_uuid, claimed)| stored_uuid == uuid && !*claimed)
|
||||
== Some(true)
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn claim_pending_switch_sides_uuid(id: &str, uuid: &uuid::Uuid) -> bool {
|
||||
let mut uuids = PENDING_SWITCH_SIDES_UUID.lock().unwrap();
|
||||
uuids.retain(|_, (instant, _, _)| instant.elapsed() < SWITCH_SIDES_UUID_TTL);
|
||||
// Keep claimed entries until expiry so replaying a request cannot launch another connection.
|
||||
if let Some((_, stored_uuid, claimed)) = uuids.get_mut(id) {
|
||||
if stored_uuid == uuid && !*claimed {
|
||||
*claimed = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -7094,6 +7116,26 @@ mod test {
|
||||
#[allow(unused)]
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[test]
|
||||
fn test_pending_switch_sides_uuid_is_claimed_once() {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
let other_uuid = uuid::Uuid::new_v4();
|
||||
assert!(insert_pending_switch_sides_uuid(id.clone(), uuid.clone()));
|
||||
|
||||
assert!(!insert_pending_switch_sides_uuid(id.clone(), uuid.clone()));
|
||||
assert!(has_pending_switch_sides_uuid(&id, &uuid));
|
||||
assert!(!has_pending_switch_sides_uuid(&id, &other_uuid));
|
||||
assert!(!claim_pending_switch_sides_uuid("other-peer", &uuid));
|
||||
assert!(!claim_pending_switch_sides_uuid(&id, &other_uuid));
|
||||
assert!(claim_pending_switch_sides_uuid(&id, &uuid));
|
||||
assert!(!has_pending_switch_sides_uuid(&id, &uuid));
|
||||
assert!(!claim_pending_switch_sides_uuid(&id, &uuid));
|
||||
assert!(!insert_pending_switch_sides_uuid(id, uuid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn login_scope_latches_session_scope_across_login_retries() {
|
||||
let port_forward = |host: &str| {
|
||||
|
||||
Reference in New Issue
Block a user