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:
fufesou
2026-05-09 18:15:00 +08:00
committed by GitHub
parent 72d27c3c47
commit 9df486a689
12 changed files with 4500 additions and 249 deletions

View File

@@ -185,9 +185,13 @@ pub mod client {
pub mod service {
use super::*;
use hbb_common::lazy_static;
#[cfg(target_os = "linux")]
use parity_tokio_ipc::Connection as RawIpcConnection;
use scrap::wayland::{
pipewire::RDP_SESSION_INFO, remote_desktop_portal::OrgFreedesktopPortalRemoteDesktop,
};
#[cfg(target_os = "linux")]
use std::os::unix::io::AsRawFd;
use std::{collections::HashMap, sync::Mutex};
lazy_static::lazy_static! {
@@ -602,7 +606,10 @@ pub mod service {
}
DataKeyboard::KeyDown(enigo::Key::Raw(code)) => {
if *code < 8 {
log::error!("Invalid Raw keycode {} (must be >= 8 due to XKB offset), skipping", code);
log::error!(
"Invalid Raw keycode {} (must be >= 8 due to XKB offset), skipping",
code
);
} else {
let down_event = InputEvent::new(EventType::KEY, *code - 8, 1);
allow_err!(keyboard.emit(&[down_event]));
@@ -610,7 +617,10 @@ pub mod service {
}
DataKeyboard::KeyUp(enigo::Key::Raw(code)) => {
if *code < 8 {
log::error!("Invalid Raw keycode {} (must be >= 8 due to XKB offset), skipping", code);
log::error!(
"Invalid Raw keycode {} (must be >= 8 due to XKB offset), skipping",
code
);
} else {
let up_event = InputEvent::new(EventType::KEY, *code - 8, 0);
allow_err!(keyboard.emit(&[up_event]));
@@ -909,6 +919,35 @@ pub mod service {
});
}
#[cfg(target_os = "linux")]
fn authorize_uinput_peer(postfix: &str, stream: &RawIpcConnection) -> bool {
if !hbb_common::config::is_service_ipc_postfix(postfix) {
return true;
}
let peer_uid = ipc::peer_uid_from_fd(stream.as_raw_fd());
let active_uid = crate::platform::linux::get_active_userid_fresh()
.trim()
.parse::<u32>()
.ok();
let authorized =
peer_uid.is_some_and(|uid| ipc::is_allowed_service_peer_uid(uid, active_uid));
if !authorized {
crate::ipc::log_rejected_uinput_connection(postfix, peer_uid, active_uid);
return false;
}
if let Err(err) =
ipc::ensure_peer_executable_matches_current_by_fd(stream.as_raw_fd(), postfix)
{
log::warn!(
"Rejected connection on protected uinput ipc channel due to executable mismatch: postfix={}, err={}",
postfix,
err
);
return false;
}
true
}
/// Start uinput service.
async fn start_service<F: FnOnce(ipc::Connection) + Copy>(postfix: &str, handler: F) {
match new_listener(postfix).await {
@@ -916,6 +955,10 @@ pub mod service {
while let Some(result) = incoming.next().await {
match result {
Ok(stream) => {
#[cfg(target_os = "linux")]
if !authorize_uinput_peer(postfix, &stream) {
continue;
}
log::debug!("Got new connection of uinput ipc {}", postfix);
handler(Connection::new(stream));
}