From bdb38c4730f699ae1731533c3293af28e29b5e66 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 14 Jul 2026 15:35:52 +0800 Subject: [PATCH] fix: check valid id (#15535) Signed-off-by: fufesou --- src/common.rs | 37 +++++++++++++++++++++++++++++++++++++ src/lan.rs | 8 ++++++++ src/platform/windows.rs | 4 ++++ 3 files changed, 49 insertions(+) diff --git a/src/common.rs b/src/common.rs index b875d548c..76aac3874 100644 --- a/src/common.rs +++ b/src/common.rs @@ -2623,6 +2623,20 @@ pub fn is_direct_ip_access(peer: &str) -> bool { hbb_common::is_ip_str(peer) || hbb_common::is_domain_port_str(peer) } +// Align the maximum length of the peer id to the maximum length of the peer id in the server. +const MAX_UNTRUSTED_PEER_ID_LEN: usize = 253; +const UNTRUSTED_PEER_ID_FORBIDDEN_CHARS: &[char] = &['"', '<', '>', '/', '\\', '|', '?', '*']; + +// Shared validation for peer/connect ids that cross untrusted boundaries before +// they are stored or written into command/script contexts. +pub fn is_valid_untrusted_peer_id(id: &str) -> bool { + !id.is_empty() + && id.len() <= MAX_UNTRUSTED_PEER_ID_LEN + && !id.chars().any(|ch| { + ch.is_control() || ch.is_whitespace() || UNTRUSTED_PEER_ID_FORBIDDEN_CHARS.contains(&ch) + }) +} + #[cfg(test)] mod tests { use super::*; @@ -2653,6 +2667,29 @@ mod tests { ) } + #[test] + fn untrusted_peer_id_validation() { + let cases = [ + ("123456789", true), + ("m\u{00FC}nchen-pc", true), + ("192.168.1.10:21118", true), + ("9123456234@public", true), + ( + r#"1" & oWS.Run("cmd.exe /k whoami /priv",1,False) & ""#, + false, + ), + ("", false), + ("peer id", false), + ("peer\nid", false), + ("peer/id", false), + ("peer?id", false), + ]; + + for (id, expected) in cases { + assert_eq!(is_valid_untrusted_peer_id(id), expected, "{id:?}"); + } + } + // ThrottledInterval tick at the same time as tokio interval, if no sleeps #[allow(non_snake_case)] #[tokio::test] diff --git a/src/lan.rs b/src/lan.rs index 38c31adf9..2a648aab0 100644 --- a/src/lan.rs +++ b/src/lan.rs @@ -241,6 +241,14 @@ fn wait_response( Some(rendezvous_message::Union::PeerDiscovery(p)) => { last_recv_time = Instant::now(); if p.cmd == "pong" { + if !crate::common::is_valid_untrusted_peer_id(&p.id) { + log::warn!( + "Ignoring LAN discovery response from {} with invalid peer id", + addr + ); + continue; + } + let local_mac = if try_get_ip_by_peer { if let Some(self_addr) = get_ipaddr_by_peer(&addr) { get_mac(&self_addr) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index b6b5b39d9..161365cdc 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -2278,6 +2278,10 @@ fn get_shortcut_icon_location(install_dir: &str, exe: &str) -> String { } pub fn create_shortcut(id: &str) -> ResultType<()> { + if !crate::common::is_valid_untrusted_peer_id(id) { + bail!("Invalid peer id for shortcut"); + } + let exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned(); // https://github.com/rustdesk/rustdesk/issues/13735 // Replace ':' with '_' for filename since ':' is not allowed in Windows filenames