diff --git a/Cargo.lock b/Cargo.lock index e3f95bc26..e3e40ec06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3749,6 +3749,7 @@ dependencies = [ "toml 0.7.8", "tungstenite", "url", + "users 0.11.0", "uuid", "webpki-roots 1.0.4", "webrtc", @@ -7231,7 +7232,6 @@ dependencies = [ "tray-icon", "ttf-parser", "url", - "users 0.11.0", "uuid", "virtual_display", "wallpaper", diff --git a/Cargo.toml b/Cargo.toml index 801ab8cdf..0b63a8167 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -176,7 +176,6 @@ evdev = { git="https://github.com/rustdesk-org/evdev" } dbus = "0.9" dbus-crossroads = "0.5" pam = { git="https://github.com/rustdesk-org/pam" } -users = { version = "0.11" } x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true} x11rb = {version = "0.12", features = ["all-extensions"], optional = true} percent-encoding = {version = "2.3", optional = true} diff --git a/libs/hbb_common b/libs/hbb_common index 8b0e25867..fa157108b 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 8b0e25867375ba9e6bff548acf44fe6d6ffa7c0e +Subproject commit fa157108be16b9ce58852a69c2186a3ced3c559b diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 569c20f9f..d5a5edac0 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1,21 +1,20 @@ use super::{gtk_sudo, CursorData, ResultType}; use desktop::Desktop; -use hbb_common::config::keys::OPTION_ALLOW_LINUX_HEADLESS; pub use hbb_common::platform::linux::*; use hbb_common::{ allow_err, anyhow::anyhow, bail, - config::Config, + config::{keys::OPTION_ALLOW_LINUX_HEADLESS, Config}, libc::{c_char, c_int, c_long, c_void}, log, message_proto::{DisplayInfo, Resolution}, regex::{Captures, Regex}, + users::{get_user_by_name, os::unix::UserExt}, }; use std::{ cell::RefCell, ffi::{OsStr, OsString}, - os::unix::ffi::OsStrExt, path::{Path, PathBuf}, process::{Child, Command}, string::String, @@ -26,7 +25,6 @@ use std::{ time::{Duration, Instant}, }; use terminfo::{capability as cap, Database}; -use users::{get_user_by_name, os::unix::UserExt}; use wallpaper; type Xdo = *const c_void; @@ -1714,26 +1712,57 @@ pub fn run_cmds_privileged(cmds: &str) -> bool { crate::platform::gtk_sudo::run(vec![cmds]).is_ok() } +/// Spawn the current executable after a delay. +/// +/// # Security +/// The executable path is safely quoted using `shell_quote()` to prevent +/// command injection vulnerabilities. The `secs` parameter is a u32, so it +/// cannot contain malicious input. +/// +/// # Arguments +/// * `secs` - Number of seconds to wait before spawning pub fn run_me_with(secs: u32) { - let exe = std::env::current_exe() - .unwrap_or("".into()) - .to_string_lossy() - .to_string(); - // We use `CMD_SH` instead of `sh` to suppress some audit messages on some systems. - std::process::Command::new(CMD_SH.as_str()) + let exe = match std::env::current_exe() { + Ok(path) => path, + Err(e) => { + log::error!("Failed to get current exe: {}", e); + return; + } + }; + + // SECURITY: Use shell_quote to safely escape the executable path, + // preventing command injection even if the path contains special characters. + let exe_quoted = shell_quote(&exe.to_string_lossy()); + + // Spawn a background process that sleeps and then executes. + // The child process is automatically orphaned when parent exits, + // and will be adopted by init (PID 1). + Command::new(CMD_SH.as_str()) .arg("-c") - .arg(&format!("sleep {secs}; {exe}")) + .arg(&format!("sleep {secs}; exec {exe_quoted}")) .spawn() .ok(); } fn switch_service(stop: bool) -> String { - let home = std::env::var("HOME").unwrap_or_default(); + // SECURITY: Use trusted home directory lookup via getpwuid instead of $HOME env var + // to prevent confused-deputy attacks where an attacker manipulates environment variables. + let home = get_home_dir_trusted() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default(); Config::set_option("stop-service".into(), if stop { "Y" } else { "" }.into()); - if home != "/root" && !Config::get().is_empty() { - let p = format!(".config/{}", crate::get_app_name().to_lowercase()); + if !home.is_empty() && home != "/root" && !Config::get().is_empty() { + let app_name_lower = crate::get_app_name().to_lowercase(); let app_name0 = crate::get_app_name(); - format!("cp -f {home}/{p}/{app_name0}.toml /root/{p}/; cp -f {home}/{p}/{app_name0}2.toml /root/{p}/;") + let config_subdir = format!(".config/{}", app_name_lower); + + // SECURITY: Quote all paths to prevent shell injection from paths containing + // spaces, semicolons, or other special characters. + let src1 = shell_quote(&format!("{}/{}/{}.toml", home, config_subdir, app_name0)); + let src2 = shell_quote(&format!("{}/{}/{}2.toml", home, config_subdir, app_name0)); + let dst = shell_quote(&format!("/root/{}/", config_subdir)); + + format!("cp -f {} {}; cp -f {} {};", src1, dst, src2, dst) } else { "".to_owned() } @@ -1787,7 +1816,15 @@ fn check_if_stop_service() { } pub fn check_autostart_config() -> ResultType<()> { - let home = std::env::var("HOME").unwrap_or_default(); + // SECURITY: Use trusted home directory lookup via getpwuid instead of $HOME env var + // to prevent confused-deputy attacks where an attacker manipulates environment variables. + let home = match get_home_dir_trusted() { + Some(p) => p.to_string_lossy().to_string(), + None => { + log::warn!("Failed to get trusted home directory for autostart config check"); + return Ok(()); + } + }; let app_name = crate::get_app_name().to_lowercase(); let path = format!("{home}/.config/autostart"); let file = format!("{path}/{app_name}.desktop"); diff --git a/src/platform/linux_desktop_manager.rs b/src/platform/linux_desktop_manager.rs index 6e21321da..03f1f6250 100644 --- a/src/platform/linux_desktop_manager.rs +++ b/src/platform/linux_desktop_manager.rs @@ -4,7 +4,12 @@ use crate::client::{ LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LOGIN_MSG_DESKTOP_XORG_NOT_FOUND, LOGIN_MSG_DESKTOP_XSESSION_FAILED, }; -use hbb_common::{allow_err, bail, log, rand::prelude::*, tokio::time}; +use hbb_common::{ + allow_err, bail, log, + rand::prelude::*, + tokio::time, + users::{get_user_by_name, os::unix::UserExt, User}, +}; use pam; use std::{ collections::HashMap, @@ -18,7 +23,6 @@ use std::{ }, time::{Duration, Instant}, }; -use users::{get_user_by_name, os::unix::UserExt, User}; lazy_static::lazy_static! { static ref DESKTOP_RUNNING: Arc = Arc::new(AtomicBool::new(false));