mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-09 23:58:10 +03:00
fix(ipc): harden local IPC authorization and portable-service bootstrap flow (#14671)
* fix(ipc): harden ipc access Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): full cmd path, comments, simple refactor Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): portable service, ipc exit Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): Remove unused logs Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): Use SetEntriesInAclW instead of icacls Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): Comments Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): check is_reparse_point Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): shmem name, no fallback Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): Simple refactor Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): better exit and clear Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): portable service, better exit Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): comments, id -u Signed-off-by: fufesou <linlong1266@gmail.com> * fix: comments linux headless, rx desktop ready Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): magic number Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): update deps Signed-off-by: fufesou <linlong1266@gmail.com> * Update Cargo.lock * Update Cargo.lock * fix(ipc): harden ipc, test `identity_unavailable` Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): portable service, check dir of shmem Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): macos, better check exe allowed Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): update hbb_common Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): update hbb_common Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): harden ipc, better active uid for uinput Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): harden portable service token validation Compare portable service IPC tokens in constant time and document the CSPRNG source used for one-time token generation. Clarify Windows IPC authorization comments around canonical path matching and partial peer identity lookup. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): simple refactor Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): harden portable service token handling Generate the portable service IPC token directly from OsRng, keep token comparison in the IPC layer as a fixed-length byte-wise check, and document the malformed-frame behavior for protected service IPC. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ipc): comments Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
@@ -22,8 +22,6 @@ use crate::{
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
||||
use cidr_utils::cidr::IpCidr;
|
||||
#[cfg(target_os = "linux")]
|
||||
use hbb_common::platform::linux::run_cmds;
|
||||
#[cfg(target_os = "android")]
|
||||
use hbb_common::protobuf::EnumOrUnknown;
|
||||
use hbb_common::{
|
||||
@@ -4983,6 +4981,9 @@ pub fn remove_pending_switch_sides_uuid(id: &str, uuid: &uuid::Uuid) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
// IPC bootstrap summary:
|
||||
// - Resolve target CM socket (headless/non-headless, optional UID-scoped path on Linux).
|
||||
// - Start CM when missing, then bridge bidirectional messages between this task and CM IPC.
|
||||
async fn start_ipc(
|
||||
mut rx_to_cm: mpsc::UnboundedReceiver<ipc::Data>,
|
||||
tx_from_cm: mpsc::UnboundedSender<ipc::Data>,
|
||||
@@ -4997,10 +4998,19 @@ async fn start_ipc(
|
||||
}
|
||||
sleep(1.).await;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
let headless_cm = crate::is_server()
|
||||
&& crate::platform::is_headless_allowed()
|
||||
&& linux_desktop_manager::is_headless();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let headless_cm = false;
|
||||
let mut stream = None;
|
||||
if let Ok(s) = crate::ipc::connect(1000, "_cm").await {
|
||||
stream = Some(s);
|
||||
} else {
|
||||
if !headless_cm {
|
||||
if let Ok(s) = crate::ipc::connect(1000, "_cm").await {
|
||||
stream = Some(s);
|
||||
}
|
||||
}
|
||||
if stream.is_none() {
|
||||
#[allow(unused_mut)]
|
||||
#[allow(unused_assignments)]
|
||||
let mut args = vec!["--cm"];
|
||||
@@ -5010,75 +5020,123 @@ async fn start_ipc(
|
||||
|
||||
// Cm run as user, wait until desktop session is ready.
|
||||
#[cfg(target_os = "linux")]
|
||||
if crate::platform::is_headless_allowed() && linux_desktop_manager::is_headless() {
|
||||
if headless_cm {
|
||||
let mut username = linux_desktop_manager::get_username();
|
||||
loop {
|
||||
if !username.is_empty() {
|
||||
break;
|
||||
}
|
||||
// `_rx_desktop_ready` is used as a wake-up signal from desktop/session state changes
|
||||
// (for example wait_desktop_cm_ready paths). It is not itself a proof of CM readiness.
|
||||
// TODO:
|
||||
// When `_rx_desktop_ready` is closed, `recv()` returns
|
||||
// `None` immediately and this loop may spin if `username` remains empty.
|
||||
// Keep behavior unchanged for now; if field reports appear, handle `Ok(None)` by
|
||||
// breaking/returning to avoid hot-looping.
|
||||
let _res = timeout(1_000, _rx_desktop_ready.recv()).await;
|
||||
username = linux_desktop_manager::get_username();
|
||||
}
|
||||
let uid = {
|
||||
let output = run_cmds(&format!("id -u {}", &username))?;
|
||||
let username_for_cmd = username.clone();
|
||||
let mut uid_cmd = hbb_common::tokio::process::Command::new("id");
|
||||
// TODO:
|
||||
// Keep current behavior for now to minimize change risk.
|
||||
// If usernames starting with '-' are observed in the field, prefer:
|
||||
// `id -u -- <username>` to avoid option-parsing ambiguity.
|
||||
// Already verified that `id -u -- <username>` works as expected on macOS and Ubuntu 24.04.
|
||||
uid_cmd.arg("-u").arg(&username_for_cmd).kill_on_drop(true);
|
||||
let output = timeout(10_000, uid_cmd.output())
|
||||
.await
|
||||
.map_err(|_| anyhow!("Timed out querying uid for {}", username))?
|
||||
.map_err(|e| anyhow!("Failed to run `id -u {}`: {}", username, e))?;
|
||||
if !output.status.success() {
|
||||
bail!("Failed to query uid for {}", username);
|
||||
}
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
let output = output.trim();
|
||||
if output.is_empty() || !output.parse::<i32>().is_ok() {
|
||||
bail!("Invalid username {}", &username);
|
||||
if output.parse::<u32>().is_err() {
|
||||
bail!("Invalid uid {}", output);
|
||||
}
|
||||
output.to_string()
|
||||
};
|
||||
user = Some((uid, username));
|
||||
args = vec!["--cm-no-ui"];
|
||||
}
|
||||
let run_done;
|
||||
if crate::platform::is_root() {
|
||||
let mut res = Ok(None);
|
||||
for _ in 0..10 {
|
||||
#[cfg(not(any(target_os = "linux")))]
|
||||
{
|
||||
log::debug!("Start cm");
|
||||
res = crate::platform::run_as_user(args.clone());
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
log::debug!("Start cm");
|
||||
res = crate::platform::run_as_user(
|
||||
args.clone(),
|
||||
user.clone(),
|
||||
None::<(&str, &str)>,
|
||||
);
|
||||
}
|
||||
if res.is_ok() {
|
||||
break;
|
||||
}
|
||||
log::error!("Failed to run cm: {res:?}");
|
||||
sleep(1.).await;
|
||||
}
|
||||
if let Some(task) = res? {
|
||||
super::CHILD_PROCESS.lock().unwrap().push(task);
|
||||
}
|
||||
run_done = true;
|
||||
} else {
|
||||
run_done = false;
|
||||
}
|
||||
if !run_done {
|
||||
log::debug!("Start cm");
|
||||
super::CHILD_PROCESS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(crate::run_me(args)?);
|
||||
}
|
||||
for _ in 0..20 {
|
||||
sleep(0.3).await;
|
||||
if let Ok(s) = crate::ipc::connect(1000, "_cm").await {
|
||||
#[cfg(target_os = "linux")]
|
||||
let cm_uid: Option<u32> = match &user {
|
||||
Some((uid, _)) => Some(
|
||||
uid.parse::<u32>()
|
||||
.map_err(|_| anyhow!("Invalid uid {}", uid))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Some(uid) = cm_uid {
|
||||
if let Ok(s) = crate::ipc::connect_for_uid(1000, uid, "_cm").await {
|
||||
stream = Some(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if stream.is_none() {
|
||||
bail!("Failed to connect to connection manager");
|
||||
let run_done;
|
||||
if crate::platform::is_root() {
|
||||
let mut res = Ok(None);
|
||||
for _ in 0..10 {
|
||||
#[cfg(not(any(target_os = "linux")))]
|
||||
{
|
||||
log::debug!("Start cm");
|
||||
res = crate::platform::run_as_user(args.clone());
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
log::debug!("Start cm");
|
||||
res = crate::platform::run_as_user(
|
||||
args.clone(),
|
||||
user.clone(),
|
||||
None::<(&str, &str)>,
|
||||
);
|
||||
}
|
||||
if res.is_ok() {
|
||||
break;
|
||||
}
|
||||
log::error!("Failed to run cm: {res:?}");
|
||||
sleep(1.).await;
|
||||
}
|
||||
if let Some(task) = res? {
|
||||
super::CHILD_PROCESS.lock().unwrap().push(task);
|
||||
}
|
||||
run_done = true;
|
||||
} else {
|
||||
run_done = false;
|
||||
}
|
||||
if !run_done {
|
||||
log::debug!("Start cm");
|
||||
super::CHILD_PROCESS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(crate::run_me(args)?);
|
||||
}
|
||||
for _ in 0..20 {
|
||||
sleep(0.3).await;
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Some(uid) = cm_uid {
|
||||
if let Ok(s) = crate::ipc::connect_for_uid(1000, uid, "_cm").await {
|
||||
stream = Some(s);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Ok(s) = crate::ipc::connect(1000, "_cm").await {
|
||||
stream = Some(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if stream.is_none() {
|
||||
bail!("Failed to connect to connection manager");
|
||||
}
|
||||
|
||||
let _res = tx_stream_ready.send(()).await;
|
||||
let mut stream = stream.ok_or(anyhow!("none stream"))?;
|
||||
|
||||
Reference in New Issue
Block a user