fix: linux, home (#13879)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-12-23 15:43:31 +08:00
committed by GitHub
parent eba847e62e
commit 6a701f1420
5 changed files with 61 additions and 21 deletions

2
Cargo.lock generated
View File

@@ -3749,6 +3749,7 @@ dependencies = [
"toml 0.7.8", "toml 0.7.8",
"tungstenite", "tungstenite",
"url", "url",
"users 0.11.0",
"uuid", "uuid",
"webpki-roots 1.0.4", "webpki-roots 1.0.4",
"webrtc", "webrtc",
@@ -7231,7 +7232,6 @@ dependencies = [
"tray-icon", "tray-icon",
"ttf-parser", "ttf-parser",
"url", "url",
"users 0.11.0",
"uuid", "uuid",
"virtual_display", "virtual_display",
"wallpaper", "wallpaper",

View File

@@ -176,7 +176,6 @@ evdev = { git="https://github.com/rustdesk-org/evdev" }
dbus = "0.9" dbus = "0.9"
dbus-crossroads = "0.5" dbus-crossroads = "0.5"
pam = { git="https://github.com/rustdesk-org/pam" } 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} x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true}
x11rb = {version = "0.12", features = ["all-extensions"], optional = true} x11rb = {version = "0.12", features = ["all-extensions"], optional = true}
percent-encoding = {version = "2.3", optional = true} percent-encoding = {version = "2.3", optional = true}

View File

@@ -1,21 +1,20 @@
use super::{gtk_sudo, CursorData, ResultType}; use super::{gtk_sudo, CursorData, ResultType};
use desktop::Desktop; use desktop::Desktop;
use hbb_common::config::keys::OPTION_ALLOW_LINUX_HEADLESS;
pub use hbb_common::platform::linux::*; pub use hbb_common::platform::linux::*;
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
anyhow::anyhow, anyhow::anyhow,
bail, bail,
config::Config, config::{keys::OPTION_ALLOW_LINUX_HEADLESS, Config},
libc::{c_char, c_int, c_long, c_void}, libc::{c_char, c_int, c_long, c_void},
log, log,
message_proto::{DisplayInfo, Resolution}, message_proto::{DisplayInfo, Resolution},
regex::{Captures, Regex}, regex::{Captures, Regex},
users::{get_user_by_name, os::unix::UserExt},
}; };
use std::{ use std::{
cell::RefCell, cell::RefCell,
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
os::unix::ffi::OsStrExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Child, Command}, process::{Child, Command},
string::String, string::String,
@@ -26,7 +25,6 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use terminfo::{capability as cap, Database}; use terminfo::{capability as cap, Database};
use users::{get_user_by_name, os::unix::UserExt};
use wallpaper; use wallpaper;
type Xdo = *const c_void; 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() 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) { pub fn run_me_with(secs: u32) {
let exe = std::env::current_exe() let exe = match std::env::current_exe() {
.unwrap_or("".into()) Ok(path) => path,
.to_string_lossy() Err(e) => {
.to_string(); log::error!("Failed to get current exe: {}", e);
// We use `CMD_SH` instead of `sh` to suppress some audit messages on some systems. return;
std::process::Command::new(CMD_SH.as_str()) }
};
// 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("-c")
.arg(&format!("sleep {secs}; {exe}")) .arg(&format!("sleep {secs}; exec {exe_quoted}"))
.spawn() .spawn()
.ok(); .ok();
} }
fn switch_service(stop: bool) -> String { 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()); Config::set_option("stop-service".into(), if stop { "Y" } else { "" }.into());
if home != "/root" && !Config::get().is_empty() { if !home.is_empty() && home != "/root" && !Config::get().is_empty() {
let p = format!(".config/{}", crate::get_app_name().to_lowercase()); let app_name_lower = crate::get_app_name().to_lowercase();
let app_name0 = crate::get_app_name(); 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 { } else {
"".to_owned() "".to_owned()
} }
@@ -1787,7 +1816,15 @@ fn check_if_stop_service() {
} }
pub fn check_autostart_config() -> ResultType<()> { 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 app_name = crate::get_app_name().to_lowercase();
let path = format!("{home}/.config/autostart"); let path = format!("{home}/.config/autostart");
let file = format!("{path}/{app_name}.desktop"); let file = format!("{path}/{app_name}.desktop");

View File

@@ -4,7 +4,12 @@ use crate::client::{
LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LOGIN_MSG_DESKTOP_XORG_NOT_FOUND, LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LOGIN_MSG_DESKTOP_XORG_NOT_FOUND,
LOGIN_MSG_DESKTOP_XSESSION_FAILED, 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 pam;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@@ -18,7 +23,6 @@ use std::{
}, },
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use users::{get_user_by_name, os::unix::UserExt, User};
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref DESKTOP_RUNNING: Arc<AtomicBool> = Arc::new(AtomicBool::new(false)); static ref DESKTOP_RUNNING: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));