mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-10-21 05:21:17 +00:00
fix: auto-login types the OS password only at the logon or lock screen (#16520)
* fix: auto-login types the OS password only at the logon or lock screen A remote-control login on Windows and macOS now reports is_logon_screen in platform_additions, and the client skips auto-login when the peer reports false. Peers that don't report it (older ones, Linux) keep today's behaviour, so this narrows the leak into the focused window without closing it: the value is a snapshot taken at login. Windows 7 and Server 2008 R2 report the WTS lock flag reversed, so there only LogonUI.exe tells the lock screen apart. Refs #16510, #15261 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1grQ39ByRWPUtTBdTzEBF * windows: drop the LogonUI.exe check from is_logon_screen LogonUI.exe can outlive the unlock and is matched by name only, so its presence reported a lock screen on an unlocked desktop and let the password through. Only the WTS lock flag decides now; on Windows 7 and Server 2008 R2, which report it reversed, the result is inverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1grQ39ByRWPUtTBdTzEBF * windows: count only a confirmed logon or lock screen for auto-login is_prelogin() reads a failed WTS user-name query as no user signed in, and on Windows 7 the inverted is_locked() read a failed lock-flag query as locked, so either failure let the password through. is_logon_screen now uses its own queries that keep a failed read apart, and reads LOCK and UNLOCK the other way round on Windows 7 and Server 2008 R2. The existing functions stay as they are for their callers. If the check task itself fails, the server reports false instead of leaving the key out, which old clients' peers would mean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1grQ39ByRWPUtTBdTzEBF * auto-login: ask the peer with a protobuf field instead of a JSON key The client sets the new LoginRequest.auto_login only when Auto Login would type the OS password, and the server runs the logon-screen check only then, so nothing new runs for any other login. The answer is the new PeerInfo.skip_auto_login: its default, false, is what an old or Linux server means, so those peers keep typing as before, and neither field is encoded unless set. The is_logon_screen key in platform_additions is gone. Windows is_locked() now reads the lock flag through is_session_locked_flag(), which handles the reversed LOCK/UNLOCK of Windows 7 and Server 2008 R2. Its only caller is the allow-logon-screen-password check, which on those systems accepted the permanent password on an unlocked desktop. Windows 8 and later read the same as before. The C is_session_locked() is removed and its WTS_CURRENT_SESSION guard moves into get_session_flags(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1grQ39ByRWPUtTBdTzEBF * auto-login: let the peer type the password, after checking its screen The login-time check left the whole time until the password arrived open: the screen could be unlocked during the click, the 1.2 s wait and two network trips, a second client's password landed on the desktop the first one had just unlocked, and turning Auto Login on mid-login skipped the check. Now the client still clicks and waits, then sends the password in a new Message.os_login (reusing OSLogin) to a Windows or macOS peer from 1.5.1. The peer checks for its logon or lock screen right then and types the password and Return only if it finds one; otherwise it drops them. Other peers get the keys as before, and the OS Password menu item is unchanged. The password goes out only after login, never in the login request. LoginRequest.auto_login and PeerInfo.skip_auto_login are removed, so nothing new runs unless os_login arrives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1grQ39ByRWPUtTBdTzEBF * server: name os_login in scope-violation logs A rejected os_login was logged as message.other. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1grQ39ByRWPUtTBdTzEBF --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0b5a3d74de
commit
13e1278b4c
@@ -1030,5 +1030,7 @@ message Message {
|
||||
TerminalAction terminal_action = 31;
|
||||
TerminalResponse terminal_response = 32;
|
||||
PortForwardChannel port_forward_channel = 33;
|
||||
// Auto Login: the peer types the password only if it is at its logon or lock screen.
|
||||
OSLogin os_login = 34;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4467,6 +4467,32 @@ fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
interface.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
/// Auto Login. A peer that can check for its logon or lock screen gets the password and types it
|
||||
/// itself, only if it is there right then. Keys typed from here land in whatever has focus if the
|
||||
/// screen is not locked by the time they arrive.
|
||||
pub fn auto_login(p: String, pi: &PeerInfo, interface: impl Interface) {
|
||||
if !is_peer_handling_os_login(pi) {
|
||||
input_os_password(p, true, interface);
|
||||
return;
|
||||
}
|
||||
std::thread::spawn(move || {
|
||||
activate_os(&interface, true);
|
||||
std::thread::sleep(Duration::from_millis(1200));
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_os_login(OSLogin {
|
||||
password: p,
|
||||
..Default::default()
|
||||
});
|
||||
interface.send(Data::Message(msg_out));
|
||||
});
|
||||
}
|
||||
|
||||
/// Windows and macOS peers from the 1.5.1 release handle `os_login`.
|
||||
fn is_peer_handling_os_login(pi: &PeerInfo) -> bool {
|
||||
(pi.platform == crate::common::PLATFORM_WINDOWS || pi.platform == crate::common::PLATFORM_MACOS)
|
||||
&& hbb_common::get_version_number(&pi.version) >= hbb_common::get_version_number("1.5.1")
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct LoginErrorMsgBox {
|
||||
msgtype: &'static str,
|
||||
|
||||
@@ -827,6 +827,10 @@ pub fn is_locked() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_logon_screen() -> bool {
|
||||
is_prelogin() || is_locked()
|
||||
}
|
||||
|
||||
pub fn is_root() -> bool {
|
||||
crate::username() == "root"
|
||||
}
|
||||
|
||||
@@ -604,14 +604,16 @@ extern "C"
|
||||
return rdp_or_console;
|
||||
}
|
||||
|
||||
BOOL is_session_locked(DWORD session_id)
|
||||
// WTSInfoExLevel1.SessionFlags, or WTS_SESSIONSTATE_UNKNOWN when it cannot be read.
|
||||
DWORD get_session_flags(DWORD session_id)
|
||||
{
|
||||
DWORD flags = WTS_SESSIONSTATE_UNKNOWN;
|
||||
// WTS_CURRENT_SESSION, which would query the caller's session instead.
|
||||
if (session_id == 0xFFFFFFFF) {
|
||||
return FALSE;
|
||||
return flags;
|
||||
}
|
||||
PWTSINFOEXW pInfo = NULL;
|
||||
DWORD bytes = 0;
|
||||
BOOL locked = FALSE;
|
||||
if (WTSQuerySessionInformationW(
|
||||
WTS_CURRENT_SERVER_HANDLE,
|
||||
session_id,
|
||||
@@ -619,13 +621,13 @@ extern "C"
|
||||
(LPWSTR *)&pInfo,
|
||||
&bytes)) {
|
||||
if (pInfo && pInfo->Level == 1) {
|
||||
locked = (pInfo->Data.WTSInfoExLevel1.SessionFlags == WTS_SESSIONSTATE_LOCK);
|
||||
flags = (DWORD)pInfo->Data.WTSInfoExLevel1.SessionFlags;
|
||||
}
|
||||
if (pInfo) {
|
||||
WTSFreeMemory(pInfo);
|
||||
}
|
||||
}
|
||||
return locked;
|
||||
return flags;
|
||||
}
|
||||
|
||||
uint32_t get_active_user(PWSTR bufin, uint32_t nin, BOOL rdp)
|
||||
|
||||
+42
-2
@@ -559,7 +559,6 @@ const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
|
||||
|
||||
extern "C" {
|
||||
fn get_current_session(rdp: BOOL) -> DWORD;
|
||||
fn is_session_locked(session_id: DWORD) -> BOOL;
|
||||
fn LaunchProcessWin(
|
||||
cmd: *const u16,
|
||||
session_id: DWORD,
|
||||
@@ -1263,7 +1262,7 @@ pub fn is_locked() -> bool {
|
||||
let Some(session_id) = get_current_process_session_id() else {
|
||||
return false;
|
||||
};
|
||||
unsafe { is_session_locked(session_id) == TRUE }
|
||||
is_session_locked_flag(session_id) == Some(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1277,6 +1276,47 @@ pub fn is_logon_ui() -> ResultType<bool> {
|
||||
.any(|pid| get_session_id_of_process(pid) == Some(current_sid)))
|
||||
}
|
||||
|
||||
// Auto-login types the OS password on this, so a state that cannot be read counts as unlocked.
|
||||
// `is_prelogin()` reads a failed query as no user signed in, and `is_logon_ui()` is left out
|
||||
// because a LogonUI.exe can outlive the unlock.
|
||||
pub fn is_logon_screen() -> bool {
|
||||
let Some(session_id) = get_current_process_session_id() else {
|
||||
return false;
|
||||
};
|
||||
is_session_prelogin(session_id) == Some(true)
|
||||
|| is_session_locked_flag(session_id) == Some(true)
|
||||
}
|
||||
|
||||
fn is_session_prelogin(session_id: u32) -> Option<bool> {
|
||||
extern "C" {
|
||||
fn get_session_user_info(path: *mut u16, n: u32, session_id: u32) -> u32;
|
||||
}
|
||||
let mut buff = [0u16; 256];
|
||||
// The bytes copied include the terminating null, so 0 means the query failed.
|
||||
let n = unsafe { get_session_user_info(buff.as_mut_ptr(), buff.len() as _, session_id) };
|
||||
if n == 0 {
|
||||
return None;
|
||||
}
|
||||
let username = String::from_utf16_lossy(&buff);
|
||||
let username = username.trim_end_matches('\0');
|
||||
Some(username.is_empty() || username == "SYSTEM")
|
||||
}
|
||||
|
||||
// Windows 7 and Server 2008 R2 report LOCK and UNLOCK the other way round (see WTSINFOEX_LEVEL1).
|
||||
fn is_session_locked_flag(session_id: u32) -> Option<bool> {
|
||||
extern "C" {
|
||||
fn get_session_flags(session_id: DWORD) -> DWORD;
|
||||
}
|
||||
const WTS_SESSIONSTATE_LOCK: DWORD = 0;
|
||||
const WTS_SESSIONSTATE_UNLOCK: DWORD = 1;
|
||||
let reversed = !base::platform::windows::is_windows_version_or_greater(6, 2, 0, 0, 0);
|
||||
match unsafe { get_session_flags(session_id) } {
|
||||
WTS_SESSIONSTATE_LOCK => Some(!reversed),
|
||||
WTS_SESSIONSTATE_UNLOCK => Some(reversed),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_root() -> bool {
|
||||
// https://stackoverflow.com/questions/4023586/correct-way-to-find-out-if-a-service-is-running-as-the-system-user
|
||||
unsafe { is_local_system() == TRUE }
|
||||
|
||||
@@ -2463,6 +2463,41 @@ impl Connection {
|
||||
self.tx_input.send(MessageInput::Key((msg, press))).ok();
|
||||
}
|
||||
|
||||
// Auto Login: the password and Return the client would otherwise type itself, typed only if a
|
||||
// check right before finds the logon or lock screen.
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
async fn type_auto_login_password(&self, password: String) {
|
||||
// A peer can send this at any rate, hence the throttled logs.
|
||||
let at_logon_screen = tokio::task::spawn_blocking(crate::platform::is_logon_screen)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
hbb_common::throttled_log!(
|
||||
Duration::from_secs(60),
|
||||
error,
|
||||
"Failed to check the logon screen: {}",
|
||||
e
|
||||
);
|
||||
false
|
||||
});
|
||||
if !at_logon_screen {
|
||||
hbb_common::throttled_log!(
|
||||
Duration::from_secs(60),
|
||||
info,
|
||||
"Auto login dropped, not at the logon or lock screen"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let mut key_event = KeyEvent::new();
|
||||
key_event.mode = KeyboardMode::Legacy.into();
|
||||
key_event.press = true;
|
||||
key_event.set_seq(password);
|
||||
self.input_key(key_event.clone(), false);
|
||||
key_event.set_control_key(ControlKey::Return);
|
||||
CLICK_TIME.store(get_time(), Ordering::SeqCst);
|
||||
MOUSE_MOVE_TIME.store(get_time(), Ordering::SeqCst);
|
||||
self.input_key(key_event, true);
|
||||
}
|
||||
|
||||
fn verify_h1(&self, h1: &[u8]) -> bool {
|
||||
let mut hasher2 = Sha256::new();
|
||||
hasher2.update(h1);
|
||||
@@ -3355,6 +3390,13 @@ impl Connection {
|
||||
}
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
Some(message::Union::OsLogin(login)) => {
|
||||
if self.peer_keyboard_enabled() {
|
||||
self.type_auto_login_password(login.password).await;
|
||||
}
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
Some(message::Union::Clipboard(cb)) => {
|
||||
if self.should_handle_text_clipboard_message() && self.clipboard_enabled() {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -6114,6 +6156,7 @@ impl Connection {
|
||||
Some(message::Union::TerminalAction(_)) => "terminal_action",
|
||||
Some(message::Union::TerminalResponse(_)) => "terminal_response",
|
||||
Some(message::Union::PortForwardChannel(_)) => "port_forward_channel",
|
||||
Some(message::Union::OsLogin(_)) => "os_login",
|
||||
Some(message::Union::Misc(misc)) => Self::misc_message_family(misc),
|
||||
Some(_) => "message.other",
|
||||
None => "empty",
|
||||
|
||||
@@ -1832,7 +1832,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
self.try_change_init_resolution(pi.current_display);
|
||||
let p = self.lc.read().unwrap().should_auto_login();
|
||||
if !p.is_empty() {
|
||||
input_os_password(p, true, self.clone());
|
||||
crate::client::auto_login(p, &pi, self.clone());
|
||||
}
|
||||
}
|
||||
let current = &pi.displays[pi.current_display as usize];
|
||||
|
||||
Reference in New Issue
Block a user